在 shell 脚本中,我的理解是"$@"
扩展到脚本参数,根据需要引用它们。例如,这会将脚本参数转发给 gcc:
gcc -fPIC "$@"
<<<
但是,当使用 bash pass-to-stdin 语法时,"@$"
它并不像我期望的那样工作。
#!/bin/bash
cat <<< "$@"
./test.sh foo "bar baz"
按照给出的方式调用脚本
foo bar baz
我希望
foo "bar baz"
有没有办法编写一个 shell 脚本来打印它的参数,就像在 shell 提示符上编写它们一样?例如:关于接下来使用什么命令的提示,包括提示中的脚本参数。
答案1
好吧,"$@"
扩展到位置参数列表,每个位置参数一个参数。
当你这样做时:
set '' 'foo bar' $'blah\nblah'
cmd "$@"
cmd
正在使用这 3 个参数进行调用:空字符串foo bar
和blah<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'
或者使用zsh
, bash
or ksh93
(此处表示bash
, YMMV 与其他 shell):
$ set '' 'foo bar' $'blah\nblah'
$ printf cmd; printf ' %q' "$@"; printf '\n'
cmd '' foo\ bar $'blah\nblah'
您还可以使用 shell 的 xtrace 选项,使 shell 打印它将要执行的内容:
$ (PS4=; set -x; : cmd "$@")
: cmd '' 'foo bar' 'blah
blah'
上面,我们使用位置参数作为参数运行了:
无操作命令。cmd
我的 shell 以一种漂亮的引用方式打印它们,适合重新输入到 shell 中。并非所有 shell 都这样做。
答案2
`"$@"` expands to the script arguments, quoting them as needed
不,事实并非如此。调用一个程序需要一个列表参数,每个参数都是一个字符串。当您运行 shell 程序时./test.sh foo "bar baz"
,这将构建一个带有三个参数的调用:./test.sh
、foo
和bar baz
。 (第零个参数是程序名称;这允许程序知道它们被调用的名称。)引用是 shell 的一项功能,而不是程序调用的一项功能。 shell 在进行调用时构建此列表。
"$@"
直接将传递给脚本或函数的参数列表复制到使用它的调用中的参数列表。由于没有对这些列表进行 shell 解析,因此不涉及引用。
在 中,您在需要单个字符串的上下文中cat <<< "$@"
使用。"$@"
运算<<<
符需要一个字符串,而不是字符串列表。在这种情况下,bash 获取列表的元素并用空格将它们连接起来。
对于脚本调试,如果您运行set -x
(set +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
答案3
"$@"
扩展为脚本参数,根据需要引用它们
嗯,有点像。出于实际目的,应该足够接近,并且参考手册确实这么说"$@" is equivalent to "$1" "$2" ...
因此,使用两个参数foo
和bar 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 或任何正在运行的命令都不知道命令运行时引用的是什么。或者,即使有任何引用可谈,系统调用也只是接收一个以 null 结尾的字符串列表,而引号只是 shell 语言的一个功能。其他语言可能有其他约定。
答案4
bash 的替代解决方案
q='"'; t=( "${@/#/$q}" ); u=( "${t[@]/%/$q}" ); echo ${u[@]}
Bash 不支持嵌套替换,所以感谢https://stackoverflow.com/questions/12303974/assign-array-to-variable#12304017用于展示如何重新分配数组。看man bash
(https://linux.die.net/man/1/bash)有关数组、扩展和模式替换的详细信息(在参数扩展下)。
分析
Bash 将命令行参数作为数组放入$@
q
保存引用字符。
参数扩展周围的双引号${ ... }
将各个参数保留为不同的元素,并将它们括起来( )
允许您将它们作为数组分配给变量。
/#/$q
^
在参数扩展中,用引用字符替换模式的开头(如 regex )。
/%/$q
$
在参数扩展中,用引用字符替换模式的结尾(如 regex )。
用例:从命令行查询 MySQL 的电子邮件地址列表
上面的语句有一些变化,使用不同的引号字符,在参数之间添加逗号,并删除最后一个逗号。当然,我在 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