我正在编写一个调用另一个脚本的非常简单的脚本,我需要将参数从我当前的脚本传播到我正在执行的脚本。
例如,我的脚本名称是 foo.sh
并调用 bar.sh
foo.sh:
bar $1 $2 $3 $4
在不明确指定每个参数的情况下如何做到这一点?
如果您确实希望传递相同的参数,请使用 "$@"
而不是普通的 $@
。
观察:
$ 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:
对于 bash 和其他类似 Bourne 的 shell:
java com.myserver.Program "$@"
$argv:q
可能适用于某些 csh 变体。
exec java com.myserver.Program "$@"
这导致 bash 执行到 java,而不是等待它完成。因此,您使用的进程槽少了一个。此外,如果父进程(运行您的脚本)正在通过 pid 监视它,并期望它是“java”进程,那么如果您不执行 exec,一些不寻常的事情可能会中断; exec 导致 java 继承相同的 pid。
args=("$@")
并用 "${args[@]}"
将每个元素扩展为一个单独的外壳“单词”(类似于 "$@"
)。
"$@"
时是否有任何“陷阱”需要牢记,例如,如果您在参数中转义了空格、空字符或其他特殊字符,它会失败吗?
使用 "$@"
(适用于所有 POSIX 兼容)。
[...] ,bash 具有“$@”变量,它扩展到所有由空格分隔的命令行参数。
$*
不同的有用值。我相信这里有历史的进步; $*
没有按设计工作,因此发明了 $@
来代替它;但是引用规则就是它们,它周围的双引号仍然是必需的(否则它将恢复为损坏的 $*
语义)。
echo "$@"
的脚本称为 ./script.sh a "b c" d
,那么您只会得到 a b c d
而不是 a "b c" d
,这是非常不同的。
echo
接收三个参数: "a" "b c" "d"
(然后shell将它们连接在一起作为它的字符串扩展)。但是,如果您使用 for i in "$@"; do echo $i; done
,您将获得 a⏎b c⏎d
。
我意识到这已经得到了很好的回答,但这里是 "$@" $@ "$*" 和 $* 之间的比较
测试脚本内容:
# 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
=================================
这里的很多答案都推荐使用带引号和不带引号的 $@
或 $*
,但似乎没有一个答案能解释它们的真正作用以及为什么要这样做。因此,让我从 this answer 中窃取这个出色的摘要:
+--------+---------------------------+
| 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 $*
请注意,没有引号,$@
在上述情况下也可以正常工作。
env --debug
亲自查看。例如,将 env --debug echo "$*"
放入函数中并尝试使用不同的参数执行它。
$*
按原样不传递它们。也许这取决于您如何定义“原样”。示例:如果您调用 ./parent.sh 'a b' c
,则在父脚本中 $1
将评估为 a b
,但在子脚本中 $1
将评估为仅 a
。所以这不是我所期望的。我希望两个脚本都“看到”相同的参数,并且这只适用于 ./child.sh "$@"
。
#!/usr/bin/env bash
while [ "$1" != "" ]; do
echo "Received: ${1}" && shift;
done;
只是认为在尝试测试 args 如何进入脚本时这可能会更有用
$#
""
、''
作为参数,如果没有 args,它也是无声的。我试图解决这个问题,但需要一个 for 循环和带有 $#
的计数器。我刚刚在最后添加了这个:echo "End of args or received quoted null"
如果将 $@
包含在带有其他字符的带引号的字符串中,则当有多个参数时行为非常奇怪,只有第一个参数包含在引号内。
例子:
#!/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'
"$@"
的语义中是有意义的。它还有助于说明 $@
和 $*
之间的主要区别,以及它们为何有用。来自 bash(1)
手册页的特殊参数部分:“*
— 当扩展发生在双引号内时,它会扩展为具有每个参数值的单个单词 […] 也就是说,"$*"
相当于 {7 },其中 c
是 [$IFS
]。"实际上,在您的第一个示例中使用 $*
而不是 $@
会得到与第二个版本相同的输出。
"$@"
进行比较。再次来自手册页:“@
- 当扩展发生在双引号内时,每个参数扩展为一个单独的单词。也就是说,"$@"
等效于 "$1"
"$2"
...如果发生双引号扩展在一个词中,第一个参数的扩展与原词的开头部分连接,最后一个参数的扩展与原词的最后部分连接。” ...确实,如果您的代码是 bash -c "true foo $@ bar baz"
,那么将其作为 test.sh one two
运行将获得 bash -c 'true foo one' 'two bar baz'
。
$*
的文档引用和信息,我似乎忘记了它的存在。
$@
刚刚获得关注,我仍然需要提醒自己它就在那里。在脚本中使用 "$*"
是很常见的……然后作者会意识到它正在将他们所有的论点粉碎在一起,所以他们会尝试各种复杂的废话与分词 "$*"
,或者 [re]通过循环 shift
来组装一个 arg 列表以将它们一个一个拉下来......只需使用 $@
就可以解决它。 (也有助于 bash 使用相同的助记符来访问数组成员:${var[*]}
表示它们都作为一个单词,${var[@]}
表示单词列表。)
bash -c
的方式完全没有意义。
我的 SUN Unix 有很多限制,甚至 "$@" 都没有被解释为期望的。我的解决方法是 ${@}。例如,
#!/bin/ksh
find ./ -type f | xargs grep "${@}"
顺便说一句,我必须有这个特定的脚本,因为我的 Unix 也不支持 grep -r
ksh
有时您想传递所有参数,但前面有一个标志(例如 --flag
)
$ bar --flag "$1" --flag "$2" --flag "$3"
您可以通过以下方式执行此操作:
$ bar $(printf -- ' --flag "%s"' "$@")
注意: 为避免额外的字段拆分,您必须引用 %s
和 $@
,并且为避免出现单个字符串,您不能引用 printf
的子shell。
bar "$@"
将等同于 bar "$1" "$2" "$3" "$4"
注意引号很重要!
"$@"
、$@
、"$*"
或 $*
在本 stackoverflow answer 中描述的转义和串联方面的行为会略有不同。
一个密切相关的用例是在这样的参数中传递所有给定的参数:
bash -c "bar \"$1\" \"$2\" \"$3\" \"$4\""
。
我使用@kvantour 答案的变体来实现这一点:
bash -c "bar $(printf -- '"%s" ' "$@")"
工作正常,除非你有空格或转义字符。在这种情况下,我找不到捕获参数并发送到脚本内的 ssh 的方法。
这可能有用但太丑了
_command_opts=$( echo "$@" | awk -F\- 'BEGIN { OFS=" -" } { for (i=2;i<=NF;i++) { gsub(/^[a-z] /,"&@",$i) ; gsub(/ $/,"",$i );gsub (/$/,"@",$i) }; print $0 }' | tr '@' \' )
"${array[@]}"
是在 bash 中传递任何数组的正确方法。我想提供一份完整的备忘单:如何准备参数、绕过和处理它们。
pre.sh
-> foo.sh
-> bar.sh
。
#!/bin/bash
args=("--a=b c" "--e=f g")
args+=("--q=w e" "--a=s \"'d'\"")
./foo.sh "${args[@]}"
#!/bin/bash
./bar.sh "$@"
#!/bin/bash
echo $1
echo $2
echo $3
echo $4
结果:
--a=b c
--e=f g
--q=w e
--a=s "'d'"
'arg with spaces'
时会发生什么。我对结果感到惊讶;希望你能解释一下。./foo.sh "arg with spaces"
和./foo.sh 'arg with spaces'
是 100% 相同的,所以我看不出将它添加到给出的示例中的建议会有什么帮助。