我有一个bash
shell 变量,其中包含由多个组成的字符串字由空格分隔。字符串可以包含转义符,例如单词中转义的空格。包含空格的单词也可以被引用。
$FOO
不加引号(而不是)使用的 shell 变量"$FOO"
会变成多个单词,但原始字符串中的引号和转义不起作用。
如何将字符串拆分为单词,同时考虑引用字符和转义字符?
背景
服务器提供ssh
使用文件ForceCommand
中的选项sshd_config
来强制执行脚本的受限访问,而不管向客户端提供的命令行如何ssh
。
该脚本使用变量SSH_ORIGINAL_COMMAND
(它是一个字符串,由 设定ssh
,包含提供给客户端的命令行ssh
)在继续之前设置其参数列表。所以,用户做
$ ssh some_server foo 'bar car' baz
将看到脚本执行,并且当脚本执行时它将SSH_ORIGINAL_COMMAND
设置为四个参数foo bar car baz
set -- ${SSH_ORIGINAL_COMMAND}
不是想要的结果。于是用户再次尝试:
$ ssh some_server foo bar\ car baz
相同的结果 - 第二个参数中的反斜杠需要为客户端 shell 进行转义,以便ssh
看到它。这些怎么样:
$ ssh some_server foo 'bar\ car' baz
$ ssh some_server foo bar\\ car baz
两者都有效,就像printf "%q"
引用包装器可以简化客户端引用。
客户端引用允许ssh
将正确引用的字符串发送到服务器,以便服务器接收到的SSH_ORIGINAL_COMMAND
反斜杠完好无损:foo bar\ car baz
。
但是仍然存在问题,因为set
没有考虑引用或转义。有一个解决方案:
eval set -- ${SSH_ORIGINAL_COMMAND}
但这是不可接受的。考虑
$ ssh some_server \; /bin/sh -i
非常不理想:eval
由于无法控制输入而无法使用。
需要的是eval
没有执行部分的字符串扩展能力。
答案1
使用read
:
read -a ssh_args <<< "${SSH_ORIGINAL_COMMAND}"
set -- "${ssh_args[@]}"
这会将单词解析到SSH_ORIGINAL_COMMAND
数组中ssh_args
,并将反斜杠 ( \
) 视为转义字符。然后将数组元素作为参数给出set
。它适用于ssh
像这样传递的参数列表:
$ ssh some_server foo 'bar\ car' baz
$ ssh some_server foo bar\\ car baz
Aprintf "%q" 引用 ssh 包装器允许这些:
$ sshwrap some_server foo bar\ car baz
$ sshwrap some_server foo 'bar car' baz
这是一个这样的包装示例:
#!/bin/bash
h=$1; shift
QUOTE_ARGS=''
for ARG in "$@"
do
ARG=$(printf "%q" "$ARG")
QUOTE_ARGS="${QUOTE_ARGS} $ARG"
done
ssh "$h" "${QUOTE_ARGS}"
答案2
如何引用字符串:
请参阅${parameter@operator}
中的部分https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html
- 对于单个变量:
"${var@Q}"
a
-> 'a'
, a'b
-> 'a'\''b'
。
- 对于数组,
"@{array[@]@Q}"
将引用数组中的每个元素,然后用空格连接成一个大字符串。
- 对于程序参数
$@
"${@@Q}"
(可能需要 Bash 4.4+,如果不可用,您可以使用 printf "%q" ...但您失去了引用数组中每个元素的能力)
如何将带引号的字符串取消引用回数组
我只发现 1 个安全的方法是 @eel ghEEz 指出的:
declare -a array="($QUOTED_ARGS)"
编辑 2022/08/31:传递带引号的参数以避免命令注入很重要。
更准确地说,应该是
JOINED_ARGMENTS_STRING="......"
declare -a array="(${JOINED_ARGMENTS_STRING@Q})"
样本:
cat <<'EOF' > show_args
#!/bin/bash
for arg in "$@"; do
echo "ARG_$((++i))=$arg"
done
EOF
chmod +x show_args
cat <<'EOF' > test.sh
#!/bin/bash
QUOTED_ARGS=${@@Q} # this is important!!!!!!
echo QUOTED_ARGS is "$QUOTED_ARGS"
echo de-quote QUOTED_ARGS
declare -a args="($QUOTED_ARGS)"
./show_args "${args[@]}"
EOF
chmod +x test.sh
测试:
ARGS=("a a a" "b'b'b" 'c"c"c')
./show_args "${ARGS[@]}"
你可以证明它是一个包含 3 个元素的数组
ARG_1=a a a
ARG_2=b'b'b
ARG_3=c"c"c
让我们看看引号和反引号是如何工作的
./test.sh "${ARGS[@]}"
或者
./test.sh "a a a" "b'b'b" 'c"c"c'
结果是
QUOTED_ARGS is 'a a a' 'b'\''b'\''b' 'c"c"c'
de-quote QUOTED_ARGS
ARG_1=a a a
ARG_2=b'b'b
ARG_3=c"c"c
编辑 2022/08/31:添加了注入尝试测试:
./test.sh "\$(echo test >&2)"
结果:
QUOTED_ARGS is '$(echo test >&2)'
de-quote QUOTED_ARGS
ARG_1=$(echo test >&2)
“echo test”命令没有被调用,这很好。
成功恢复。