在引号中打印命令行参数

3

当双引号括起来时("$@"),会展开为"$1" "$2" "$3" ...

似乎没有简单的方法打印"$@"内部内容的引用形式。

$ set -- "one two" three  
$ echo "$@"
one two three

What I am looking for is, output of

'one two' three

使用'set -x',会在"$@"内部输出带引号的参数形式。
$ set -x; echo "$@"; set +x
+ set -x
+ echo 'one two' three
one two three
+ set +x

用户自定义变量$DOLLAR_AT包含与"$@"相同的内容

$ DOLLAR_AT="'one two' three"

使用'echo'将$DOLLAR_AT内部的内容倒出,可以按预期工作

$ echo $DOLLAR_AT
'one two' three

由于一些特殊字符的自动引用,导致shell无法正确解析参数。
$ set -x; echo $DOLLAR_AT; set +x
+ echo ''\''one' 'two'\''' three
+ set +x

目前尚不清楚原因

$ echo "$@"

不会产生相同的输出结果

$ echo "$DOLLAR_AT"

在使用从shell脚本中接收到的"$@"启动命令时,需要在调用命令之前以引号形式打印出"$@"中的内容。
在打印从bash数组传递的命令行参数时也有类似的需求。
$ CMDARGS=("one two" "three")

将数组放在双引号内进行评估不会以引用形式打印数组元素。

$ echo "${CMDARGS[@]}"
one two three

我还需要一种简洁的方式来打印$CMDARGS[@]中的内容,以引号形式显示,类似于此迭代输出的结果。
$ for ARG in "${CMDARGS[@]}"; do echo \'$ARG\'; done
'one two'
'three'

1
引号通常由调用脚本的(交互式)Shell 吃掉。引号的效果(分组多词值)在代码运行时已经发挥了作用。值 one two 已经被分配给 $1 - Raxi
1
通常,如果您想以转义形式传递值,例如在传递/调用另一个脚本时,则可以使用 printf %q。它会自动转义值,使它们原样传递到下一个程序中。它不使用引号,而是只反斜杠转义空格,这具有相同的最终结果。 - Raxi
@user3477071 但是为什么呢?考虑使用 printf '%s\n' "$@"。它会在一行上打印 one two,在下一行上打印 three。只是因为缺少引号并不意味着您没有保留两个参数之间的区别。 - chepner
然后尝试 printf "'%s' " "$@"; echo - M. Nejat Aydin
1
你的 for 循环已经逐个显示每个参数;引号不会给你任何额外的信息。 - chepner
显示剩余4条评论
1个回答

3

1. 使用参数扩展:

你可以尝试内联:

$ set -- 'foo bar' baz
$ echo ${@@Q}
'Foo bar' 'baz'

$ echo ${@@A}
set -- 'Foo bar' 'baz'

$ DOLLAR_AT="'one two' three"
$ echo ${DOLLAR_AT@Q}
''\''one two'\'' three'

$ echo ${DOLLAR_AT@A}
DOLLAR_AT=''\''one two'\'' three'

更多信息请参考bash的手册,参数展开子章节。

   ${parameter@operator}
         Parameter transformation.  The expansion is either a transforma‐
         tion  of  the  value of parameter or information about parameter
         itself, depending on the value of operator.  Each operator is  a
         single letter:
 ...
         Q      The  expansion is a string that is the value of parameter
                quoted in a format that can be reused as input.
 ...
         A      The expansion is a string in the form  of  an  assignment
                statement  or  declare  command  that, if evaluated, will
                recreate parameter with its attributes and value.

用于重复使用:

$ myString1=${@@Q}
$ set -- something\ else
$ . <(echo "set -- $myString1")
$ echo ${@@A}
set -- 'Foo bar' 'baz'

1.1. $*$@的区别。

简单来说,$*用于生成字符串,而$@可以根据上下文生成数组字符串S

来自bash的manpage:

   *      Expands to the positional parameters, starting from  one...
          ...   That  is, "$*" is equivalent to "$1c$2c...", where  c is
          the first character of the value of the IFS variable...
   @      Expands to the positional parameters,  starting  from  one...
          ...  That is, "$@" is equivalent to "$1" "$2" ... 

小型演示:

$ paramExpDemo() {
    local IFS=+ _arry
    _arry="${@@Q}"         #  var="..." -> context string
    declare -p _arry

    _arry="${*@Q}"
    declare -p _arry

    _arry=("${@@Q}")       #  var=(...) -> context array
    declare -p _arry

    _arry=("${*@Q}")
    declare -p _arry
}

那么

$ paramExpDemo  'foo bar' baz
declare -- _arry="'foo bar'+'baz'"
declare -- _arry="'foo bar'+'baz'"
declare -a _arry=([0]="'foo bar'" [1]="'baz'")
declare -a _arry=([0]="'foo bar'+'baz'")

在上下文中,string 中的 $@$* 将给出相同的结果,但是在上下文中,array 中,$* 会返回一个 string,而 $@ 返回一个数组。
$ set -- 'foo bar' baz
$ Array=("${@@Q}")
$ declare -p Array
declare -a Array=([0]="'foo bar'" [1]="'baz'")
$ echo "${Array[@]@Q}"
''\''foo bar'\''' ''\''baz'\'''

2. 使用 printf %q

正如Léa Gris所指出的, 你可以使用:

$ printf '%q\n' "$@"
Foo\ bar
baz

$ printf -v myString2 "%q " "$@"
$ echo "${myString2% }"
Foo\ bar baz 

用于重复使用:

$ set -- something\ else
$ . <(echo "set -- $myString2")
$ echo ${@@A}
set -- 'Foo bar' 'baz'

注意:Charles Duffy评论道,这并不完美!事实上,在命令行中无法检索使用了哪种语法。

$ printf "<%q>\n" "$myString1" "${myString2% }"
<\'Foo\ bar\'\ \'baz\'>
<Foo\\\ bar\ baz>

作为“获取”变量的字符串,它们虽然都能工作,但并不相同,因此进一步的字符串操作可能会变得复杂...

另外,printf %q\\n "'one two' three" - Léa Gris
3
@user3477071,你说你正在寻找确切的值输入引用方式。但这并不能给你这个——它会在输出上打印'one two',即使输入是one\ two或者one" "two或者'one 'two或者任何其他写相同值的方式。(也就是说,无法得到确切的值输入引用方式;因为值作为一个C字符串数组传递,而所有解析都已经完成,所以转换成那个C字符串的输入细节已经消失了。) - Charles Duffy
@CharlesDuffy:感谢您对输入的警告,两种方法都会失败。如果我的脚本接收到"$@",则已经是单引号或双引号,如果任何参数具有任何特殊字符。希望找到一种方法来查看当"$@"传递给调用的命令时,位置参数如何被分解。我希望设置CMD="<cmd> ${@@Q}",然后echo "$CMD"和eval "$CMD"就可以完成工作。 - user3477071
@CharlesDuffy:我理解您的意思,不同的 shell 输入可能会产生相同的 "$@"。一旦在脚本或程序内部, 就无法映射回 shell 中呈现命令行参数的确切方式,这导致了 "$@"。 不确定是否存在需要进行此反向工程的情况。对于 $1=one two,我很满意,它来自于 shell 提供的 one\ two、one" "two 或 'one 'two 的值。 我所要做的就是以带引号的形式打印 "$@",以便进行调试,然后调用 'someprog'。 - user3477071
明白了。从你对chepner在问题上的评论的反驳来看,似乎你坚持要获取原始格式,这就是为什么我一直在强调这一点的原因。 - Charles Duffy
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接