如何在 Bash 参数中保留引号?

我有一个 Bash 脚本,希望在传递的参数中保留引号。

例如:

./test.sh this is "some test"

然后我想使用这些参数,并重复使用它们,包括整个参数列表中的引号和引号。

我尝试使用 \"$@\",但它删除了列表中的引号。

我该怎么做?

135815 次浏览

只要在字符串周围使用单引号和双引号即可:

./test.sh this is '"some test"'

所以单引号内的双引号也被解释为字符串。

但我建议将整个字符串放在单引号之间:

 ./test.sh 'this is "some test" '

为了理解 shell 在做什么,或者更确切地说,为了解释脚本中的参数,您可以编写如下小脚本:

#!/bin/bash


echo $@
echo "$@"

然后您将看到并测试使用不同字符串调用脚本时发生了什么

引号由 bash 解释,不存储在命令行参数或变量值中。

如果你想使用引用的参数,你必须在每次使用它们的时候引用它们:

val="$3"
echo "Hello World" > "$val"

如果假设一个包含空格的参数必须被(并且应该)引用是安全的,那么你可以这样添加它们:

#!/bin/bash
whitespace="[[:space:]]"
for i in "$@"
do
if [[ $i =~ $whitespace ]]
then
i=\"$i\"
fi
echo "$i"
done

下面是一个例子:

$ ./argtest abc def "ghi jkl" $'mno\tpqr' $'stu\nvwx'
abc
def
"ghi jkl"
"mno    pqr"
"stu
vwx"

您还可以在双引号或单引号内使用 Ctrl-V TabCtrl-V Ctrl-J,而不是在 $'...'中使用转义符,从而使用 插入文字制表符和换行符。

关于 Bash 中 插入字符的注意事项: 如果在 Bash 中使用 Vi 键绑定(set -o vi)(默认为 Emacs-set -o emacs) ,则需要处于 插入模式 以便插入字符。在 Emacs 模式下,始终处于插入模式。

使用 "$@"可以将参数替换为一个列表,而不需要在空格中重新分割它们(当调用 shell 脚本时,它们被分割了一次) ,如果您只是想将参数重新传递给另一个程序,那么这通常正是您所需要的。

请注意,这是一个特殊的形式,只有当它以这种方式出现时才能被识别。如果在引号中添加其他内容,结果将合并为一个参数。

你到底想做什么,为什么不起作用?

Yuku 的回答只有在你是脚本的唯一用户的情况下才有效,而 Dennis Williamson 的回答很棒,如果你主要对打印字符串感兴趣,并且希望它们没有引号的话。

如果您希望将所有参数作为一个大的引号字符串参数传递给 bashsu-c参数,可以使用下面的版本:

#!/bin/bash
C=''
for i in "$@"; do
i="${i//\\/\\\\}"
C="$C \"${i//\"/\\\"}\""
done
bash -c "$C"

因此,所有参数在它们周围得到了一个引号(如果之前没有这个引号,那么它是无害的) ,但是我们也转义任何转义,然后转义已经在参数中的任何引号(语法 ${var//from/to}进行全局子字符串替换)。

你当然可以只引用已经有空格的东西,但这里不重要。这样的脚本的一个实用工具是能够拥有一组特定的预定义环境变量(或者,使用 su,以特定用户的身份运行某些东西,而不需要双引号)。


更新: 我最近有理由使用 POSIX 方式,使用最小的分叉,这导致了这个脚本(最后一个 printf 输出用于调用该脚本的命令行,您应该能够复制粘贴,以便使用等效的参数调用该脚本) :

#!/bin/sh
C=''
for i in "$@"; do
case "$i" in
*\'*)
i=`printf "%s" "$i" | sed "s/'/'\"'\"'/g"`
;;
*) : ;;
esac
C="$C '$i'"
done
printf "$0%s\n" "$C"

我切换到 '',因为 shell 也用 ""引号来解释像 $!!这样的东西。

取下锤子的示例更改为使用数组。

printargs() { printf "'%s' " "$@"; echo; };  # http://superuser.com/a/361133/126847


C=()
for i in "$@"; do
C+=("$i")  # Need quotes here to append as a single array element.
done


printargs "${C[@]}"  # Pass array to a program as a list of arguments.

有两种安全的方法可以做到这一点:

1. 壳参数展开: ${variable@Q}:

当通过 ${variable@Q}展开一个变量时:

展开是一个字符串,该字符串是引用的参数值,其格式可以重用为输入。

例如:

$ expand-q() { for i; do echo ${i@Q}; done; }  # Same as for `i in "$@"`...
$ expand-q word "two words" 'new
> line' "single'quote" 'double"quote'
word
'two words'
$'new\nline'
'single'\''quote'
'double"quote'

2. printf %q "$quote-me"

printf支持内部报价。 printf的手册入口表示:

%q使 printf 以可重用为 shell 输入的格式输出相应的参数。

例如:

$ cat test.sh
#!/bin/bash
printf "%q\n" "$@"
$
$ ./test.sh this is "some test" 'new
>line' "single'quote" 'double"quote'
this
is
some\ test
$'new\nline'
single\'quote
double\"quote
$

注意,如果将引用的文本显示给人看,第二种方法会更干净一些。

Related: For bash, POSIX sh and zsh: 使用单引号而不是反斜杠的引号字符串

My problem was similar and I used mixed ideas posted here.

我们有一个带有 PHP 脚本的服务器来发送电子邮件。然后我们有第二个服务器,它通过 SSH 连接到第一个服务器并执行它。

两个服务器上的脚本名称相同,实际上都是通过 bash 脚本执行的。

在服务器1(本地) bash 脚本上,我们只有:

/usr/bin/php /usr/local/myscript/myscript.php "$@"

它驻留在 /usr/local/bin/myscript上,由远程服务器调用。即使对于带有空格的参数,它也能正常工作。

但是在远程服务器上,我们不能使用相同的逻辑,因为第一个服务器将不会接收来自 "$@"的引号。我使用 JohnMudd 和 Dennis Williamson 的想法来重新创建带引号的选项和参数数组。我喜欢只在项中有空格时才添加转义引号的想法。

所以远程脚本的运行方式是:

CSMOPTS=()
whitespace="[[:space:]]"
for i in "$@"
do
if [[ $i =~ $whitespace ]]
then
CSMOPTS+=(\"$i\")
else
CSMOPTS+=($i)
fi
done


/usr/bin/ssh "$USER@$SERVER" "/usr/local/bin/myscript ${CSMOPTS[@]}"

注意,我使用 "${CSMOPTS[@]}"将选项数组传递给远程服务器。

感谢每一个在这里发帖的人! 它真的帮助了我! :)

正如 Tom Hale 所说,一种方法是使用 printf使用 %q引用-转义。

例如:

发送

#!/bin/bash
if [ "$#" -lt 1 ]; then
quoted_args=""
else
quoted_args="$(printf " %q" "${@}")"
fi


bash -c "$( dirname "${BASH_SOURCE[0]}" )/receiver.sh${quoted_args}"

Send _ less _ args. sh

#!/bin/bash
if [ "$#" -lt 2 ]; then
quoted_last_args=""
else
quoted_last_args="$(printf " %q" "${@:2}")"
fi


bash -c "$( dirname "${BASH_SOURCE[0]}" )/receiver.sh${quoted_last_args}"

接收器

#!/bin/bash
for arg in "$@"; do
echo "$arg"
done

示例用法:

$ ./send_all_args.sh
$ ./send_all_args.sh a b
a
b
$ ./send_all_args.sh "a' b" 'c "e '
a' b
c "e
$ ./send_fewer_args.sh
$ ./send_fewer_args.sh a
$ ./send_fewer_args.sh a b
b
$ ./send_fewer_args.sh "a' b" 'c "e '
c "e
$ ./send_fewer_args.sh "a' b" 'c "e ' 'f " g'
c "e
f " g

是的,似乎是不可能永远保存的报价,但对于我正在处理的问题,这是没有必要的。

我有一个 bash 函数,它会递归地搜索下文件夹并搜索一个字符串 grep,问题是传递一个有空格的字符串,比如“ find this string”。将其传递给 bash 脚本将获取基参数 $n 并将其传递给 grep,这使 grep 相信这些参数是不同的。我解决这个问题的方法是,当您引用 bash 调用函数时,它将引号中的项分组为一个参数。我只需要用引号装饰参数并将其传递给 grep 命令。

如果您知道您在 bash 中接收到的下一步需要引号的参数是什么,那么您可以使用引号来装饰它。

我需要它来将所有参数转发给另一个解释器。 最后对我有利的是:

bash -c "$(printf ' %q' "$@")"

示例(当命名为 forward.sh 时) :

$ ./forward.sh echo "3 4"
3 4
$ ./forward.sh bash -c "bash -c 'echo 3'"
3

(当然,我使用的实际脚本更复杂,包括 nohup 和重定向等,但这是关键部分。)

Just use:

"${@}"

例如:

# cat t2.sh
for I in "${@}"
do
echo "Param: $I"
done
# cat t1.sh
./t2.sh "${@}"


# ./t1.sh "This is a test" "This is another line" a b "and also c"
Param: This is a test
Param: This is another line
Param: a
Param: b
Param: and also c

正如 Gary S. Weaver 在他的源代码提示中所示,诀窍是使用参数‘-c’调用 bash,然后引用下一个。

例如:。

bash -c "<your program> <parameters>"

或者

docker exec -it <my docker> bash -c "$SCRIPT $quoted_args"

如果需要将所有参数传递给 bash 从另一种编程语言(例如,如果希望执行 bash -cemit_bash_code | bash) ,请使用以下方法:

  • '\''转义所有单引号字符。
  • 然后,用单引号包围结果

因此,abc'def的参数将转换为 'abc'\''def'。字符 '\''的解释如下: 已经存在的引号以第一个引号结束,然后转义单引号 \'出现,然后开始新的引号。