107

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

例子:

./test.sh this is "some test"

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

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

我该如何做到这一点?

4

14 回答 14

124

using"$@"会将参数替换为列表,而不会在空格上重新拆分它们(它们在调用 shell 脚本时被拆分一次),如果您只想将参数重新传递给另一个程序,这通常正是您想要的。

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

你想做什么,它以什么方式不起作用?

于 2009-11-03T19:17:43.777 回答
57

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

1 、Shell参数扩展${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
$

请注意,如果将引用的文本显示给人类,则第二种方式会更简洁一些。

相关:对于 bash、POSIX sh 和 zsh:使用单引号而不是反斜杠引用字符串

于 2016-09-13T06:19:24.610 回答
47

只有当您是脚本的唯一用户时,Yuku 的答案才有效,而如果您主要对打印字符串感兴趣,并且希望它们没有引号中的引号,那么丹尼斯威廉姆森的答案就很好。

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

#!/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 也解释了-quotes 之类$的东西。!!""

于 2012-01-04T07:15:18.540 回答
35

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

#!/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"

您还可以在双引号或单引号中使用-和-来插入文字制表符和换行符,而不是在.CtrlV TabCtrlV CtrlJ$'...'

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

于 2009-11-03T19:08:16.697 回答
18

我需要这个来将所有参数转发给另一个解释器。最终适合我的是:

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

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

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

(当然我使用的实际脚本更复杂,在我的例子中涉及 nohup 和重定向等,但这是关键部分。)

于 2018-05-16T09:53:08.503 回答
13

就像 Tom Hale 所说,一种方法是printf使用%qto quote-escape。

例如:

send_all_args.sh

#!/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_fewer_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}"

接收器.sh

#!/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
于 2017-03-03T21:19:10.927 回答
6

将unhammer的示例更改为使用数组。

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.                               
于 2015-04-01T18:46:34.603 回答
6

我的问题很相似,我在这里发布了混合的想法。

我们有一个带有发送电子邮件的 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[@]}"将选项数组传递给远程服务器。

感谢大家在这里发帖!它真的帮助了我!:)

于 2017-01-29T20:59:03.380 回答
6

只需使用:

"${@}"

例如:

# 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
于 2018-11-08T19:24:14.790 回答
4

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

如果要使用带引号的参数,则每次使用时都必须引用它们:

val="$3"
echo "Hello World" > "$val"
于 2009-11-03T17:03:06.987 回答
1

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

例如

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

或者

docker exec -it <my docker> bash -c "$SCRIPT $quoted_args"
于 2019-01-22T12:22:10.867 回答
1

如果您需要将所有参数从另一种编程语言传递给 bash (例如,如果您想执行bash -cor emit_bash_code | bash),请使用以下命令:

  • 使用 .转义所有单引号字符'\''
  • 然后,用单引号将结果括起来

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

于 2019-08-20T16:04:32.053 回答
0

是的,似乎不可能保留引号,但对于我正在处理的问题,它是没有必要的。

我有一个 bash 函数,它将递归搜索文件夹并 grep 查找字符串,问题是传递一个包含空格的字符串,例如“查找此字符串”。然后将其传递给 bash 脚本将获取基本参数 $n 并将其传递给 grep,这使 grep 相信这些是不同的参数。我解决这个问题的方法是,当你引用 bash 来调用函数时,它会将引号中的项目分组到一个参数中。我只需要用引号装饰该参数并将其传递给 grep 命令。

如果你知道你在 bash 中收到的参数需要在下一步中使用引号,你可以用引号来装饰。

于 2018-04-20T16:28:53.147 回答
-18

只需在带有双引号的字符串周围使用单引号:

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

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

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

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

为了了解 shell 正在做什么,或者更确切地说是解释脚本中的参数,您可以编写一个像这样的小脚本:

#!/bin/bash

echo $@
echo "$@"

然后你会看到并测试,用不同的字符串调用脚本时会发生什么

于 2009-11-03T16:57:25.383 回答