shell脚本是否可以打印其引号,就像您在shell提示符下编写引号一样?


9

在shell脚本中,我的理解是"$@"扩展到脚本参数,并根据需要引用它们。例如,这会将脚本参数转发给gcc:

gcc -fPIC "$@"

但是,当使用bash传递到stdin的语法时<<<"@$"却无法正常运行。

#!/bin/bash
cat <<< "$@"

调用脚本./test.sh foo "bar baz"

foo bar baz

我希望

foo "bar baz"

有没有一种方法可以像在shell提示符下那样编写可打印其参数的shell脚本?例如:关于下一步要使用的命令的提示,包括提示中的脚本参数。

Answers:


5

好,"$@"扩展到位置参数列表,每个位置参数一个参数。

当您这样做时:

set '' 'foo bar' $'blah\nblah'
cmd "$@"

cmd正在使用以下3个参数进行调用:空字符串foo barblah<newline>blah。Shell将execve()使用类似以下内容的方式调用系统调用:

execve("/path/to/cmd", ["cmd", "", "foo bar", "blah\nblah"], [envvars...]);

如果要重建将重现相同调用的shell命令行(即shell语言中的代码),则可以执行以下操作:

awk -v q="'" '
  function shellquote(s) {
    gsub(q, q "\\" q q, s)
    return q s q
  }
  BEGIN {
    for (i = 1; i < ARGC; i++) {
      printf "%s", sep shellquote(ARGV[i])
      sep = " "
    }
    printf "\n"
  }' cmd "$@"

或使用zsh,询问不同类型的引号:

set '' 'foo bar' $'blah\nblah'
$ print -r -- cmd "${(q)@}"
cmd '' foo\ bar blah$'\n'blah
$ print -r -- cmd "${(qq)@}"
cmd '' 'foo bar' 'blah
blah'
$ print -r -- cmd "${(qqq)@}"
cmd "" "foo bar" "blah
blah"
$ print -r -- cmd "${(qqqq)@}"
cmd $'' $'foo bar' $'blah\nblah'

或使用zshbashksh93(在此处bash,YMMV和其他外壳一起使用):

$ set '' 'foo bar' $'blah\nblah'
$ printf cmd; printf ' %q' "$@"; printf '\n'
cmd '' foo\ bar $'blah\nblah'

您还可以使用外壳程序的xtrace选项,该选项使外壳程序打印将要执行的内容:

(PS4=; set -x; : cmd "$@")
: cmd '' 'foo bar' 'blah
blah'

上面,我们运行了:带有cmd位置参数作为参数的no-op命令。我的外壳以引号不错的方式将它们打印出来,适合重新输入到外壳中。并非所有的壳都这样做。


5
`"$@"` expands to the script arguments, quoting them as needed

不,这不会发生。调用程序需要一个参数列表,每个参数都是一个字符串。当您运行shell程序./test.sh foo "bar baz",这将构建三个参数调用:./test.shfoo,和bar baz。(第零个参数是程序名称;这使程序可以知道它们使用的名称。)报价是Shell的功能,而不是程序调用的功能。Shell进行调用时将构建此列表。

"$@"直接将传递给脚本或函数的参数列表复制到使用它的调用中的参数列表。由于这些列表上没有外壳解析,因此不涉及任何引用。

在中cat <<< "$@",您在"$@"需要单个字符串的上下文中使用。该<<<operator`需要一个字符串,而不是字符串列表。在这种情况下,bash获取列表中的元素,并将它们之间以空格隔开。

对于脚本调试,如果运行set -xset +x关闭),则会激活跟踪模式,在该跟踪模式下将在执行每个命令之前打印每个命令。在bash中,该跟踪使用引号引起来,从而可以将命令粘贴回shell中(并非每个sh实现都如此)。

如果您有一个字符串,并且想要将其转换为可解析回原始字符串的shell源语法,则可以用单引号将其括起来,并用替换字符串中的每个单引号'\''

for x do
  printf %s "'${x//\'/\'\\\'\'}' "
done
echo

字符串替换语法是ksh93 / bash / zsh / mksh特定的。在普通sh中,您需要遍历字符串。

for raw do
  quoted=
  while case "$raw" in *\'*) true;; *) false;; esac; do
    quoted="$quoted'\\''${raw%%\'*}"
    raw="${raw#*\'}"
  done
  printf %s "'$quoted$raw' "
done
echo

2

"$@" 扩展到脚本参数,并根据需要引用它们

好吧,有点。出于实际目的,应该足够接近,参考手册确实指出"$@" is equivalent to "$1" "$2" ...

所以,用两个参数foobar baz,这些将是一样:

echo "$@"
echo "$1" "$2"
echo "foo" "bar baz"

(除非参数包含特殊字符而不是纯字符串,否则它们在扩展$@$1... 之后将不会再次扩展。)

但是,即使我们考虑将其$@替换为引号中的参数,也不会echo看到引号,与此类似,gcc也不会得到引号。

<<<是有点例外的"$@"== "$1" "$2" ...规则,它明确地提到的是The result is supplied as a single string to the command on its standard input通过参数和变量扩展和他人之间的报价去除去了。因此,像往常一样,<<< "foo"只给foo定输入,就像somecmd "foo"只给foo定参数一样。

./test.sh foo "bar baz"我期望的方式 调用脚本foo "bar baz"

如果引号仍然存在,它仍然必须是"foo" "bar baz"。shell或任何正在运行的命令都不知道命令运行时的引用是什么。或什至没有任何引用可言,系统调用仅接收以空值终止的字符串列表,而引号只是shell语言的功能。其他语言可能有其他约定。


0

bash的替代解决方案

q='"'; t=( "${@/#/$q}" ); u=( "${t[@]/%/$q}" ); echo ${u[@]}

Bash不支持嵌套替换,因此感谢/programming/12303974/assign-array-to-variable#12304017显示了如何重新分配数组。有关数组,扩展和模式替换(在参数扩展下)的详细信息,请参见man bashhttps://linux.die.net/man/1/bash)。

分析

Bash将命令行参数作为数组放入 $@

q 保留引号字符。

参数扩展周围的双引号${ ... }将各个参数保留为不同的元素,并将其包装起来( )即可将其作为数组分配给变量。

/#/$q在参数扩展中,^用引号char 替换模式的开头(如regex )。

/%/$q在参数扩展中,$用引号char 替换模式的结尾(如regex )。

用例:从命令行查询MySQL以获取电子邮件地址列表

上面的语句有一些更改,以使用不同的双引号char,在参数之间添加逗号,并去除最后的逗号。当然,通过将密码放入mysql调用中,我感到很糟糕。告我

q="'"; t=( "${@/#/$q}" ); u="${t[@]/%/$q,}"; v="u.email in( ${u%,} )"
mysql -uprod_program -h10.90.2.11 -pxxxxxxxxxxxx my_database <<END
select ...
from users u
join ....
where $v # <<<<<<<<<<<<<<<<<< here is where all the hard work pays off :-)
group by user_id, prog_id
;
END

请注意,用双引号引起来对保护反斜杠,- $扩展和其他双引号不是很有帮助。这就是为什么其他的答案用单引号,而将一些方法来处理单引号的字符串,或者使用shell的自己的特点用于生产线的报价副本。
ilkkachu

@ilkkachu适当指出!这就是(除其他原因外)我赞成所有先前答案的原因。也是为什么我添加了这个用例。希望完美不是善良的敌人。
杰夫
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.