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

sta*_*fry 6 shell bash string eval bash-expansion

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

$FOO不加引号(而不是)使用的 shell 变量"$FOO"会变成多个单词,但原始字符串中的引号和转义不起作用。

如何将字符串拆分为单词,同时考虑引用字符和转义字符?

背景

服务器提供ssh使用文件ForceCommand中的选项来sshd_config强制执行脚本的受限访问,而不管向客户端提供的命令行如何ssh

该脚本使用变量SSH_ORIGINAL_COMMAND(它是一个字符串,由 设定ssh,包含提供给客户端的命令行ssh)在继续之前设置其参数列表。所以,用户做

$ ssh some_server foo 'bar car' baz
Run Code Online (Sandbox Code Playgroud)

将看到脚本执行,并且当脚本执行时它将设置为四个SSH_ORIGINAL_COMMAND参数foo bar car baz

set -- ${SSH_ORIGINAL_COMMAND}
Run Code Online (Sandbox Code Playgroud)

不是想要的结果。于是用户再次尝试:

$ ssh some_server foo bar\ car baz
Run Code Online (Sandbox Code Playgroud)

相同的结果 - 第二个参数中的反斜杠需要为客户端 shell 进行转义,以便ssh看到它。这些怎么样:

$ ssh some_server foo 'bar\ car' baz
$ ssh some_server foo bar\\ car baz
Run Code Online (Sandbox Code Playgroud)

两者都可以工作,就像可以简化客户端引用的printf "%q"引用包装器一样。

客户端引用允许ssh将正确引用的字符串发送到服务器,以便服务器接收到的SSH_ORIGINAL_COMMAND反斜杠完好无损:foo bar\ car baz

但是仍然存在问题,因为set没有考虑引用或转义。有一个解决方案:

eval set -- ${SSH_ORIGINAL_COMMAND}
Run Code Online (Sandbox Code Playgroud)

但这是不可接受的。考虑

$ ssh some_server \; /bin/sh -i
Run Code Online (Sandbox Code Playgroud)

非常不可取:eval无法使用,因为无法控制输入。

需要的是eval没有执行部分的字符串扩展能力。

sta*_*fry 5

使用read

read -a ssh_args <<< "${SSH_ORIGINAL_COMMAND}"
set -- "${ssh_args[@]}"
Run Code Online (Sandbox Code Playgroud)

这会将单词解析SSH_ORIGINAL_COMMAND到数组中ssh_args,并将反斜杠 ( \) 视为转义字符。然后将数组元素作为参数给出set。它适用ssh于像这样传递的参数列表:

$ ssh some_server foo 'bar\ car' baz
$ ssh some_server foo bar\\ car baz
Run Code Online (Sandbox Code Playgroud)

引用 ssh 包装器的 printf "%q"允许:

$ sshwrap some_server foo bar\ car baz
$ sshwrap some_server foo 'bar car' baz
Run Code Online (Sandbox Code Playgroud)

这是一个这样的包装示例:

#!/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}"
Run Code Online (Sandbox Code Playgroud)