bash 可以将带引号和/或转义的字符串变量扩展为单词吗?

bash 可以将带引号和/或转义的字符串变量扩展为单词吗?

我有一个bashshell 变量,其中包含由多个组成的字符串由空格分隔。字符串可以包含转义符,例如单词中转义的空格。包含空格的单词也可以被引用。

$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”命令没有被调用,这很好。

成功恢复。

相关内容