Bash - 转义SSH命令

7
我有一组脚本,用于通过FTP下载文件,然后从服务器上删除它们。
工作原理如下:
for dir in `ls /volume1/auto_downloads/sync-complete`
do
if [ "x$dir" != *"x"* ]
then
echo "DIR: $dir"

echo "Moving out of complete"
        # Soft delete from server so they don't get downloaded again
        ssh dan@172.19.1.15 mv -v "'/home/dan/Downloads/complete/$dir'" /home/dan/Downloads/downloaded

现在$dir可能是"This is a file",这样就没问题了。
我遇到的问题是有特殊字符,例如:
- "This is (a) file" - This is a file & stuff"
这种情况容易出错。
bash: -c: line 0: syntax error near unexpected token `('
bash: -c: line 0: `mv -v '/home/dan/Downloads/complete/This is (a) file' /home/dan/Downloads/downloaded'

我不知道如何转义它,使得变量被评估并且命令得到适当的转义。我尝试过各种转义字符、文字引号、普通引号等组合。


2
rsync --remove-source-files 怎么样?http://serverfault.com/a/363925/69736。简单多了! - Colonel Panic
不清楚 dir 是如何被设置为单个字符串 'This is (a) file' 的。它应该依次设置为 4 个单独的字符串 'This'、'is'、'(a)' 和 'file'。不要解析 ls 的输出;切换目录并迭代一个 glob(详见我的答案)。 - chepner
[ "x$dir" != *"x"* ] -- this only works for pattern matching in [[, not [ - Charles Duffy
5个回答

22
如果双方都在使用bash,您可以使用printf '%q '转义参数,例如:
ssh dan@172.19.1.15 "$(printf '%q ' mv -v "/home/dan/Downloads/complete/$dir" /home/dan/Downloads/downloaded)"

1
printf 只在本地端执行,因此远程主机上不需要 bash - chepner
7
根据Bash的转义规则,%q会进行转义。如果Bash不是远程shell,则无法保证这些转义的有效性。 - Will Palmer
1
你可能还可以考虑使用 printf -v cmd_q '%q ' ...,然后 ssh user@host "$cmd_q",从而完全避免支付子shell惩罚。 - Charles Duffy
顺便提一下,在现代的bash(5.x肯定可以,也许是晚期的4.x),可以使用以下命令:ssh dan@host "mv -v /path/to/${dir@Q} /path/to/Downloads" - Charles Duffy

2

您需要引用整个表达式ssh user@host "command"

ssh dan@172.19.1.15 "mv -v /home/dan/Downloads/complete/$dir /home/dan/Downloads/downloaded"

这导致变量在远程端被评估,而不是本地。 - Dan
哦,我明白了。所以我更新我的答案,不需要转义 $ 符号。现在应该可以工作了。 - fedorqui
啊,现在我明白了,你在 ssh 命令之前有一个 echo。所以更好的做法是像 @Will Palmer 的回答中所示,将其包裹在 $() 中。 - fedorqui

0

Will Palmer的建议使用printf非常好,但我认为将文字部分放在printf的格式中更有意义。

这样,多命令一行代码更容易编写:

ssh user@host "$(printf 'mkdir -p -- %q && cd -- "$_" && tar -zx' "$DIR")"

0

我有点困惑,因为你的代码按照原样对我来说是可以工作的:

> dir='foo & bar (and) baz'
> ssh host  mv -v "'/home/dan/Downloads/complete/$dir'" /home/dan/Downloads/downloaded
mv: cannot stat `/home/dan/Downloads/complete/foo & bar (and) baz': No such file or directory

在调试时,在脚本顶部使用set -vx以查看正在发生的事情。


1
尝试使用dir="nathan's crew.txt" - Will Palmer
@Will,你的回答绝对比我的好,但我仍然困惑为什么OP在他发布的脚本中遇到了他所述的问题。 - Old Pro
1
实际问题源于通过SSH传递远程命令的方式:所有参数都被连接在一起作为单个字符串传递到远程shell中-因此,原帖示例中的双引号实际上只是为了使单引号存在于远程端。 - Will Palmer
@Will,是的,但是除非$dir包含单引号(或可能是感叹号),否则它应该可以工作。OP说它在括号上被绊倒了。 - Old Pro

0

可以使用 Python 的 shlex.quote(s) 方法来

返回字符串 s 的 shell 转义版本

文档


1
这种方法的优势在于与 printf %q 方法相比,它返回的字符串保证可以在所有符合POSIX标准的shell中使用,而不仅仅是bash。 - Charles Duffy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接