在Bash shell脚本中传递所有参数

1223

我正在编写一个非常简单的脚本,调用另一个脚本,需要将当前脚本的参数传递给我要执行的脚本。

例如,我的脚本名称是foo.sh,并调用了bar.sh

foo.sh:

bar $1 $2 $3 $4

如何在不显式指定每个参数的情况下完成这个操作?


请参见https://dev59.com/x2ct5IYBdhLWcg3wI6J6。 - Vadzim
我不得不使用这个解决方案来解决我的用例。 - forresthopkinsa
12个回答

1926

如果您希望参数被传递相同,请使用"$@"而不是普通的$@

观察:

$ cat no_quotes.sh
#!/bin/bash
echo_args.sh $@

$ cat quotes.sh
#!/bin/bash
echo_args.sh "$@"

$ cat echo_args.sh
#!/bin/bash
echo Received: $1
echo Received: $2
echo Received: $3
echo Received: $4

$ ./no_quotes.sh first second
Received: first
Received: second
Received:
Received:

$ ./no_quotes.sh "one quoted arg"
Received: one
Received: quoted
Received: arg
Received:

$ ./quotes.sh first second
Received: first
Received: second
Received:
Received:

$ ./quotes.sh "one quoted arg"
Received: one quoted arg
Received:
Received:
Received:

13
这段代码用于纯引用/转义字符串的传递:观察: cat rsync_foo.sh #!/bin/bash echo "$@" rsync "$@" ./rsync_foo.sh -n "bar me" bar2 bar me bar2skipping directory bar me 是否可能有一个Shell脚本可以查看真正的原始命令行并包括引号或转义字符串? - Mark Edington
3
传递标志怎么样?例如,'./bar.sh --with-stuff'。 - Nathan Long
9
我建议任何想更好地了解单词拆分主题的人,可以在这里阅读更多相关内容:http://mywiki.wooledge.org/WordSplitting - Rany Albeg Wein
5
我建议您在这三个例子中,也展示一下包含带空格的 'arg with spaces' 的情况。我对结果感到惊讶,希望您能解释一下。 - Michael Scheper
3
@MichaelScheper,无论如何,“./foo.sh“arg with spaces“”和“./foo.sh 'arg with spaces'”是完全相同的,因此我不明白为什么建议将其添加到给出的示例中会有任何帮助。 - Charles Duffy
显示剩余5条评论

515

对于bash和其他 Bourne-like shells:

bar "$@"

13
@Amir:不,不是为了 csh。为了大家的健康,请不要编写 csh 脚本(http://www.faqs.org/faqs/unix-faq/shell/csh-whynot/)。但我认为 $argv:q 或许在某些 csh 变体中可以工作。 - Chris Johnsen
3
谢谢!如要求的那样,这将传递与脚本接收到的相同一组参数,而不是一个大参数。双引号是必需的。即使是包含空格的带引号参数也可以正常工作。 - Andy Thomas
29
相关内容:如果您的shell脚本只是作为运行java的包装器,请考虑将最后一行改为exec java com.myserver.Program "$@"。这会导致bash进入java,而不是等待它完成。因此,您将使用少一个进程插槽。此外,如果父进程(运行您的脚本)通过pid监视它,并期望它是“java”进程,如果您不执行exec,则可能会出现一些异常情况;exec导致java继承相同的pid。 - greggo
7
如果你使用的shell(例如bash、zsh等)支持数组变量,你可以使用数组变量:用args=("$@")将参数存储到args中,然后将每个元素作为单独的shell“word”扩展(类似于"$@")使用 "${args[@]}" - Chris Johnsen
1
在使用"$@"时,有没有需要注意的“陷阱”,例如如果参数中有转义空格、空字符或其他特殊字符,它是否会失败? - IQAndreas
显示剩余5条评论

121

使用 "$@" (适用于所有 POSIX 兼容性)。

[...] ,bash 特性 "$@" 变量会展开为由空格分隔的所有命令行参数。

来自Bash 实例


9
“$@”不仅仅是将$@用引号括起来的意思,它实际上是一个不同的内置变量吗? - ben
8
它是一个单一变量,但需要用双引号将其括起来才能具有与(损坏的)“$*”不同的有用值。我认为这里存在历史进展; “$*”不能像设计的那样工作,因此发明了“$@”来替换它;但由于引用规则的限制,仍然需要双引号将其括起来(否则它将恢复到破碎的“$*”语义)。 - tripleee
9
“$@” 不仅仅是引号包裹的 $@,实际上它是一个不同的内置变量吗?-- 就所有意图和目的而言,确实如此:https://dev59.com/x2ct5IYBdhLWcg3wI6J6#28099707 - Stuart Berg
2
如果你调用一个包含 echo "$@" 的脚本,例如 ./script.sh a "b c" d,那么你得到的结果是 a b c d 而不是 a "b c" d,这两者之间有很大的区别。 - isarandi
8
虽然输出结果不再包含引号,但这是预期的结果。请放心,在脚本内部,echo 命令接收到三个参数: "a" "b c" "d" (然后 shell 将它们连接起来作为字符串扩展的一部分)。但如果你使用了 for i in "$@"; do echo $i; done,你会得到 a⏎b c⏎d 的输出。 - FeRD
显示剩余2条评论

117

我知道这个问题早已得到了很好的回答,但这里是"$@"、$@、"$*"和$*之间的比较。

测试脚本的内容:

# cat ./test.sh
#!/usr/bin/env bash
echo "================================="

echo "Quoted DOLLAR-AT"
for ARG in "$@"; do
    echo $ARG
done

echo "================================="

echo "NOT Quoted DOLLAR-AT"
for ARG in $@; do
    echo $ARG
done

echo "================================="

echo "Quoted DOLLAR-STAR"
for ARG in "$*"; do
    echo $ARG
done

echo "================================="

echo "NOT Quoted DOLLAR-STAR"
for ARG in $*; do
    echo $ARG
done

echo "================================="

现在,使用不同的参数运行测试脚本:

# ./test.sh  "arg with space one" "arg2" arg3
=================================
Quoted DOLLAR-AT
arg with space one
arg2
arg3
=================================
NOT Quoted DOLLAR-AT
arg
with
space
one
arg2
arg3
=================================
Quoted DOLLAR-STAR
arg with space one arg2 arg3
=================================
NOT Quoted DOLLAR-STAR
arg
with
space
one
arg2
arg3
=================================

101

这里有很多答案推荐使用带引号或不带引号的$@$*,但是似乎没有人解释这些命令的真正作用和为什么应该这样使用。所以让我从这个答案中窃取这个精彩总结:

+--------+---------------------------+
| Syntax |      Effective result     |
+--------+---------------------------+
|   $*   |     $1 $2 $3 ... ${N}     |
+--------+---------------------------+
|   $@   |     $1 $2 $3 ... ${N}     |
+--------+---------------------------+
|  "$*"  |    "$1c$2c$3c...c${N}"    |
+--------+---------------------------+
|  "$@"  | "$1" "$2" "$3" ... "${N}" |
+--------+---------------------------+

注意引号会使两者的行为有所不同,如果没有引号,则两者的行为相同。

根据我的需求,我需要将参数原封不动地从一个脚本传到另一个脚本,而最好的选择是:

# file: parent.sh
# we have some params passed to parent.sh 
# which we will like to pass on to child.sh as-is

./child.sh $*

请注意上述情况中没有引号,$@ 也应该能够正常工作。


1
您可以使用 env --debug 命令自行查看。例如,将 env --debug echo "$*" 放入一个函数中,并尝试使用不同的参数执行它。 - user2846495
3
你的例子是错误的。$* 不应该按照原样传递。也许这取决于你如何定义“按原样传递”。例如:如果你调用 ./parent.sh 'a b' c,那么在父脚本中 $1 将会被解析为 a b,但在子脚本中 $1 只会被解析为 a。所以这不是我期望的情况。我期望两个脚本都“看到”相同的参数,而这只能通过使用 ./child.sh "$@" 来实现。 - bibermann

40
#!/usr/bin/env bash
while [ "$1" != "" ]; do
  echo "Received: ${1}" && shift;
done;

我觉得当你尝试测试参数如何进入你的脚本时,这可能会更有用。


7
这绝对没有回答他的问题,但确实很有用。点赞! - Dario Russo
2
当传递一个空参数时,它会中断。你应该检查 $# - Sebi
好的参数测试器,同意将 ""'' 作为参数,如果没有参数,则保持沉默。我试图修复这个问题,但需要使用 $# 的 for 循环和计数器。我只是在最后添加了这个:echo "End of args or received quoted null" - Merlin
1
您也可以简单地执行 env --debug <command> <args> 以获得类似的输出。例如:env --debug echo "$@" - user2846495

7
如果您将$@与其他字符一起放在引号字符串中,则在有多个参数时,行为非常奇怪,只有第一个参数包含在引号内。
例如:
#!/bin/bash
set -x
bash -c "true foo $@"

产生:

$ bash test.sh bar baz
+ bash -c 'true foo bar' baz

但是先将其赋值给另一个变量:

#!/bin/bash
set -x
args="$@"
bash -c "true foo $args"

产生:

$ bash test.sh bar baz
+ args='bar baz'
+ bash -c 'true foo bar baz'

4
我不否认这很让人不安,但在Bash中的"$@"语义上,这实际上是有道理的。它还有助于说明 $@$* 之间的关键区别以及它们各自的用处。从 bash(1) 手册的“特殊参数”部分可以得知:* —— 当扩展出现在双引号内时,它将扩展为单个单词,其值为每个参数。也就是说,"$*" 等同于 "$1c$2c...",其中 c 是 [$IFS]。事实上,在第一个例子中使用 $* 而不是 $@ 会产生与第二个版本相同的输出。 - FeRD
2
现在,将其与"$@"进行比较。再次引用手册页面:“@” - 当扩展出现在双引号内时,每个参数都会扩展为单独的单词。也就是说,“$@”等同于“$1”“$ 2”… 如果双引号扩展出现在单词内,则第一个参数的扩展将与原始单词的开头部分连接,最后一个参数的扩展将与原始单词的最后一部分连接。”…确实,如果您的代码是“bash -c“ true foo $ @ bar baz”,那么以“test.sh one two”运行它将得到“bash -c'true foo one' 'two bar baz'”。 - FeRD
1
感谢提供文档引用和$*的信息,我好像忘记它的存在了。 - ColinM
呵呵,我正好相反,当我开始编写shell脚本时,$@刚刚开始流行,我仍然需要提醒自己它的存在。在脚本中常见的是使用"$*"...然后作者会意识到它将所有参数都粘在一起,于是他们会尝试各种复杂的无用操作,如单词拆分"$*"或通过循环shift逐个拉下来重新组装参数列表...只需使用$@即可解决问题。(bash使用相同的助记符${var[*]}访问数组成员,${var[@]}表示单词列表。) - FeRD
1
这里真正的问题是你正在以一种毫无意义的方式使用 bash -c - tripleee

6

我的SUN Unix有很多限制,甚至"$@"不能按预期解释。我的解决方法是${@}。例如:

#!/bin/ksh
find ./ -type f | xargs grep "${@}"

顺便说一下,我必须使用这个特定的脚本,因为我的Unix系统也不支持grep -r命令。

4
这是一个Bash问题;你正在使用ksh - tripleee

6
有时候你想传递所有的参数,但在前面加上一个标记(比如--flag)。
$ bar --flag "$1" --flag "$2" --flag "$3"

您可以按照以下方式进行操作:
$ bar $(printf -- ' --flag "%s"' "$@")

注意:为避免额外的字段分割,您必须引用%s$@, 为避免成为单个字符串, 您不能引用printf的子shell。


1
当参数可能包含 " 时,这种方法不起作用 - 您真的需要使用支持 %qprintf - Toby Speight

5

bar "$@" 相当于 bar "$1" "$2" "$3" "$4"

请注意引号的重要性!

"$@"$@"$*"$* 在转义和拼接方面的行为略有不同,详情请参见这个stackoverflow答案

一个相关的使用案例是将所有给定的参数传递到一个参数中,像这样:

bash -c "bar \"$1\" \"$2\" \"$3\" \"$4\""

我使用了 @kvantour 答案的一种变体方法来实现这个目的:

bash -c "bar $(printf -- '"%s" ' "$@")"


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