如何在 Bash 参数中保留引号? [复制]

Posted

技术标签:

【中文标题】如何在 Bash 参数中保留引号? [复制]【英文标题】:How to keep quotes in Bash arguments? [duplicate] 【发布时间】:2010-12-12 17:21:58 【问题描述】:

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

例子:

./test.sh this is "some test"

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

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

我该如何做到这一点?

【问题讨论】:

\"$@\" 是错误的——它将 literal 引号添加到第一个和最后一个参数。 "$@",没有反斜杠,是正确的:在其中,引号是纯粹的句法。请参阅BashFAQ #50,了解为什么在 shell 中将参数列表作为字符串传递是天生不正确的。 查尔斯是对的​​。当您想将所有参数传递给另一个脚本或函数时,请使用“$@”(不转义引号)。 看到这个答案:Difference between single and double quotes in Bash. 相关:How do I use a Bash variable (string) containing quotes in a command? unix.stackexchange.com/a/445477/162125 【参考方案1】:

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

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

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

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

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

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

#!/bin/bash

echo $@
echo "$@"

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

【讨论】:

这怎么可能是公认的答案?这需要脚本的调用者知道他使用双引号,尽管这可以在脚本中处理而不改变调用者构建参数列表的方式 如果您无法控制./test.sh,那么这是正确的答案,但如果您正在创作./test.sh,那么the answer from Chris Dodd 是更好的解决方案。 不仅在您无法控制test.sh 的情况下,而且仅在test.sh编写为不安全地使用eval 的情况下才合适。坦率地说,如果某些东西不安全地使用eval,那可能是停止使用该东西的一个很好的理由。 这里的人错了。这是一个公认的答案。它将责任传递给调用者。 虽然这是正确的,但基本上是“你不知道”类型的答案。它并没有真正回答这个问题。它也不遵守 SO 的答案规则。如果有的话,可以在***.com/a/10836225/4687565 找到关于为什么不这样做的更详细的解释【参考方案2】:

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

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

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

【讨论】:

【参考方案3】:

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

#!/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插入文字制表符和换行符/kbd>-V Ctrl-J 在双引号或单引号中,而不是在$'...' 中使用转义符。

关于在 Bash 中插入字符的说明:如果您在 Bash 中使用 Vi 键绑定 (set -o vi)(默认使用 Emacs - set -o emacs),您需要在插入模式为了插入字符。在 Emacs 模式下,您始终处于插入模式。

【讨论】:

但是如何检查用户是否引用了第一个参数? @TonyBarganski:在将参数传递给接收它们的程序之前处理参数时,shell 会去除引号。无法判断参数上是否有 外部引号。【参考方案4】:

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

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

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

【讨论】:

我认为他的意思是引号是参数字符串的一部分,而不是 bash 语法的一部分。 @qwertzguy 不正确。在变量进入参数列表之前,双引号将被去除。 这是评价最高的答案。虽然它本身是正确的,但它并没有真正解决问题。 @TorstenBronger:由于这个问题完全不清楚,所以这是回答我对 OP 实际想要什么的最佳猜测。 @Hofi:这些都是因为您需要正确引用 echo -- xargs"$@" 是无关紧要的。按照我的建议尝试echo -ne "nospace\x0yes space\x0"【参考方案5】:

仅当您是脚本的唯一用户时,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 也解释""-quotes 中的$!!

【讨论】:

使用数组代替?我将在下面发布一个示例。 这几乎是完美的,但我建议做一个小的修改 - 转义所有 ` before appending to $C` - 即添加 i="$i//\\/\\\\" 否则像 "\\\"asdf" 这样的参数将失败。 @unhammer 非常感谢,这太棒了! ℹ️ 和刚才一样:"$@@Q" man bash 说这是一个参数转换。可以在 bash 版本中使用它而不是正则表达式替换,给出C=''; for i in "$@"; do C="$C $i@Q"; done; bash -c "$C"【参考方案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.                               

【讨论】:

是否可以使用"$C[@]" 作为bash -c 的参数?例如。如果您的脚本是使用参数echo foo\"bar"'"fie 调用的 例如foo=( echo bar\"fie"'"fum ); C='';for i in "$foo[@]"; do C="$C \"$i//\"/\\\"\""; done; bash -c "$C" bar"fie'fum 虽然代码看起来更干净,但你还是把引号弄丢了【参考方案7】:

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

1。 Shell parameter expansion: $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 支持内部引用。 manual's entry for 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:Quote string with single quotes rather than backslashes

【讨论】:

这对我有用。在我使用的函数中: local _quoted=$( printf ' "%q"' "$@" ) 这个答案看起来是最可靠的。 第一种方法expand-q有一个错误:$i@Q应该是$i:Q否则它会失败bash: $i@Q: bad substitution @arielf bash 的哪个版本?它的工作原理如$BASH_VERSION 4.4.19(1)-release 所示。你所做的可能是$parameter:offset 的副作用,其中offset0,(可能是因为Qatoi() 给出0)。 这可能是原因。我在 Ubuntu 16.04.4 (LTS) 上。这个系统上最新的 bash 是 4.3.48(1)-release 我发现 $*@Q 对于将所有命令行参数传递给后续的 shell 调用非常有用,在我的例子中是因为 sg 调用更改组。感谢您的回答!【参考方案8】:

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

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

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

【讨论】:

上述略失败:-opt="value" 变为 "-opt=value" 我没有在我的脚本中使用=,但由于\"仅在检测到空格时才添加,-opt="value"实际上会变成-opt=value。像-opt="another value" 这样的东西将作为"-opt=another value" 发送,但由于我的设置将它发送到另一台机器上的另一个脚本,它仍然被解释为预期的单个令牌。远程脚本接收-opt=another value 作为没有引号的单个标记。但是,如果在本地运行,情况就大不相同了。例如,您甚至不需要转义 \"【参考方案9】:

就像 Tom Hale 所说,一种方法是使用 printf 使用 %q 来引用转义。

例如:

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"

receiver.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

【讨论】:

这是一个很好的例子,因为它包含一个可重现的测试,用于通过sh -c 之类的方式转发参数。另外,它有效;-)。并非所有其他人都这样做。【参考方案10】:

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

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

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

【讨论】:

【参考方案11】:

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

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

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

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

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

【讨论】:

【参考方案12】:

只需使用:

"$@"

例如:

# 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

【讨论】:

这不允许将参数转发到 bash -c 调用。为此,您需要***.com/a/42588600/125349【参考方案13】:

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

例如

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

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

【讨论】:

【参考方案14】:

如果您需要将所有参数传递给 bash 其他编程语言(例如,如果您想执行 bash -cemit_bash_code | bash),请使用以下命令:

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

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

【讨论】:

没有。不要将参数转义为嵌入在代码中的文本;相反,请使用其他编程语言的接口,该接口可让您传递 array 参数,并在其自己的数组元素中传递每个项目。这意味着人们永远不应该使用system();幸运的是,它的缺陷已经被认识到了几十年,所以它很少是唯一的选择。 例如,在 Python 中,可以将数组而不是字符串传递给 subprocess-family 调用。在 C 语言中,可以使用execvposix_spawn。在 Java 中,可以使用Runtime.exec() 中的基于数组的接口之一;等等等等。 @CharlesDuffy 对不起,我的回答是从“如果”这个词开始的。如果不够清楚,请参阅“例如”......如果您可以通过使用参数数组来调用 CLI,那当然更好。然而,在我描述的情况下,这是不可能的(bash -canother_program | bash)。 绝对可能。 subprocess.Popen(['bash', '-c', 'code that uses "$1" and "$2"', "_", "value for $1", "value for $2"]),f/e,从 Python 启动 bash,同时传递两个值,使它们远离代码。根本不应该遵循 pipe-to-bash 模式,但是人们可以通过这种方式获取代码,同时类似地使用 another_program | bash -s 'value for $1' 'value for $2' 的任何可用等价物带外传递数据 The pipe-to-bash pattern simply shouldn't be followed at all -- 你可能是对的。这仍然是我的回答。我想在某种意义上可能是错误的,我应该对首选方法发出更多警告。就个人而言,我用它来打印一个 CLI 指令,可以由用户复制粘贴到终端(公平起见,这也是你永远不应该真正鼓励的东西)。

以上是关于如何在 Bash 参数中保留引号? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

如何在bash脚本中传递变量的双引号值作为curl命令的参数?

如何在bash函数参数中引用?

如何删除多余的双引号,而不是使用bash脚本在一行文本中打开和关闭双引号

如何在 shell 脚本中将带引号的参数添加到带引号的命令中? [复制]

bash:以空格作为参数传递路径?

如何在 bash 中结合 getopts 和位置参数? [复制]