文件
sample$1.class
sample.class
scp_test.sh
scp_测试.sh
#!/bin/bash
TARGET=('sample.class' 'sample$1.class')
for dd in "${TARGET[@]}"
do
FILENAME=`basename ${dd}`
scp ${FILENAME} remote:/tmp/${dd}
done
当 shell 运行时,会在远程服务器上sample$1.class
被覆盖, 只留下 。sample.class
sample.class
$ ./scp_test.sh
sample.class 100% 0 0.0KB/s 00:00
sample$1.class 100% 0 0.0KB/s 00:00
$ ssh remote 'ls -l /tmp/'
total 01
sample.class
仅当通过 复制时才会发生这种情况scp
。本地复制使用cp
不会出现这种情况。
*编辑由于我使用的 Bash 版本低于 4.3(4.2.46),因此无法使用。我通过使用引号组合而不是普通的 来${dd@Q}
解决问题。'"${dd}"'
${dd}
答案1
初步说明
这个答案主要涉及传统的,即使用 SCP 协议的scp
传统。OpenSSH的现代版本默认使用 SFTP,当我写这篇文章时,转换还不到两个月;所以它很新。我可以告诉你你使用的是 SCP,因为如果它使用 SFTP,你就不会遇到这个问题。解决这些问题是 SCP 被 SFTP 取代的原因之一。scp
scp
scp
来自变更日志:
OpenSSH 9.0 于 2022-04-08 发布。 […]
[…]
此版本
scp(1)
默认从使用旧的 scp/rcp 协议切换到使用 SFTP 协议。旧版 scp/rcp 通过远程 shell 对远程文件名执行通配符扩展(例如
scp host:* .
)。这有副作用,即要求对scp(1)
命令行中包含的文件名中的 shell 元字符使用双引号,否则它们可能会在远程端被解释为 shell 命令。
独立于以下方面的潜在问题scp
首先:引用正确。即使不加引号,您的示例名称也可能是安全的,编写健壮的代码仍然是一种美德。在我看来,如果您可以不加引号,那么始终加引号比每次都费力思考要容易得多。带有正确加引号的变量的代码将如下所示(请注意,我暂时不会尝试解决这个问题scp
,稍后会解决):
#!/bin/bash
target=('sample.class' 'sample$1.class')
for dd in "${target[@]}"
do
filename="$(basename "${dd}")"
scp -- "${filename}" "remote:/tmp/${dd}"
done
(我也修复了名字全部大写,引入双划线万一您将以破折号开头的文件名添加到target
。我认为basename
在这种特殊情况下是无操作的,但我认为您有这样做的理由。)
如果您scp
使用 SFTP,那么上述代码将会起作用(坦率地说,我认为您的原始代码也可以使用这样的代码scp
,但仅仅是因为您使用的名称在未加引号时是“安全的”)。
问题scp
不幸的是,旧版scp
将远程路径名嵌入到了 shell 代码中,而该代码是要由远程 shell 来解释的(比较我的这个答案)。远程 shell 将解释引号、 等字符$
,[
除非它们在到达远程 shell 时被转义或引用。这意味着您需要在本地此外考虑远程 shell 的引用。您需要为本地 shell 引用和用于远程 shell。这就是引文中“要求在文件名中对 shell 元字符使用双引号”的含义。
解决方案
Bash 可以提供帮助。"${dd@Q}"
将扩展dd
并转义或引用结果,因此经过进一步的解释(在您的情况下由远程 shell 执行)结果将是您期望的单个单词(如"$dd"
本地扩展)。以下行是对旧版的修复scp
:
scp -- "${filename}" "remote:/tmp/${dd@Q}"
整个脚本如下:
#!/bin/bash
target=('sample.class' 'sample$1.class')
for dd in "${target[@]}"
do
filename="$(basename "${dd}")"
scp -- "${filename}" "remote:/tmp/${dd@Q}"
done
该解决方案不可移植,它无法在纯 . 中工作sh
。脚本中的 shebang 是#!/bin/bash
从头开始的,所以我想你不介意只在 Bash 中工作的代码。
最后说明
第一个代码片段(不带
${dd@Q}
)适用于scp
使用 SFTP(即新版本)。第二个代码片段(带${dd@Q}
)适用于scp
使用 SCP(即旧版本scp
)。没有简单的通用代码。新版本scp
支持-O
,使其行为类似于旧版本,但如果您使用 带的scp
旧版本,则它会失败,因为它会发现选项无效。无论哪种方式,您都需要知道您的是新的还是旧的,并相应地调整您的 shell 代码。目前您的是旧的(因此首先存在问题),但如果您更新它,它可能会被新的替换。已经链接的scp
-O
scp
scp
变更日志注意到不兼容性:这会产生一个潜在的不兼容性:使用 SFTP 协议时不再需要这种繁琐而脆弱的引用,尝试使用它可能会导致传输失败。我们认为删除文件名中双引号 shell 字符的需要是一个好处,并且不打算在使用 SFTP 协议时
scp(1)
引入对旧版 scp/rcp 的错误兼容性。scp(1)
侧面“问题”,一个友好的建议。将脚本命名为
scp_test.sh
不是一个好习惯。对于您的scp_test.sh
解释器来说,已经是bash
,而不是sh
。您的原始代码需要bash
并且无法在纯环境中工作sh
(因为数组)。您要重命名为 吗scp_test.bash
?如果您将脚本移植到 Python 会怎样?好的,我认为您不会对非常脚本有问题,但一般来说,您可能会遇到这种情况。然后,每个调用的工具scp_test.sh
都需要修复并scp_test.py
改为调用。将脚本命名为
scp_test
。您可以scp_test
通过检查其权限来判断它是否可执行。如果您想知道它是什么,请调用file scp_test
。现在您可以用任何您想要的语言重写脚本,甚至可以编译二进制文件,而您(或任何人/任何东西)仍然可以将其作为 运行scp_test
。