ChatGPT解决这个技术问题 Extra ChatGPT

在 bash shell 脚本中传播所有参数

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

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

foo.sh:

bar $1 $2 $3 $4

在不明确指定每个参数的情况下如何做到这一点?


I
Ilan Schemoul

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

观察:

$ 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:

这对引用/转义字符串的纯传递起作用:观察: cat rsync_foo.sh #!/bin/bash echo "$@" rsync "$@" ./rsync_foo.sh -n "bar me" bar2 bar me bar2skipping directory bar me 是否有可能拥有可以看到带有引号或转义字符串的真正原始命令行的 shell 脚本?
传旗怎么办?例如,'./bar.sh --with-stuff'
我建议任何想要更好地理解分词主题的人,read more about it here.
我建议您也展示在三个示例中包含 'arg with spaces' 时会发生什么。我对结果感到惊讶;希望你能解释一下。
@MichaelScheper,无论如何,./foo.sh "arg with spaces"./foo.sh 'arg with spaces' 是 100% 相同的,所以我看不出将它添加到给出的示例中的建议会有什么帮助。
C
Chris Johnsen

对于 bash 和其他类似 Bourne 的 shell:

java com.myserver.Program "$@"

@Amir:不,不适合 csh。为了每个人的理智:do not script csh。但我认为 $argv:q 可能适用于某些 csh 变体。
谢谢!根据需要,这会传递脚本接收到的同一组参数——而不是一个大参数。双引号是必须的。即使使用包含空格的引用参数也有效。
相关:如果您的 shell 脚本只是充当运行 java 的包装器,请考虑将最后一行 exec java com.myserver.Program "$@" 这导致 bash 执行到 java,而不是等待它完成。因此,您使用的进程槽少了一个。此外,如果父进程(运行您的脚本)正在通过 pid 监视它,并期望它是“java”进程,那么如果您不执行 exec,一些不寻常的事情可能会中断; exec 导致 java 继承相同的 pid。
@dragonxlwang:如果您的 shell 支持它们(例如 bashzsh 等,但不是普通的 Bourne-或 POSIX-shell),您可以使用数组变量:保存到argsargs=("$@") 并用 "${args[@]}" 将每个元素扩展为一个单独的外壳“单词”(类似于 "$@")。
使用 "$@" 时是否有任何“陷阱”需要牢记,例如,如果您在参数中转义了空格、空字符或其他特殊字符,它会失败吗?
S
Sk8erPeter

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

[...] ,bash 具有“$@”变量,它扩展到所有由空格分隔的命令行参数。

Bash by example


那么“$@”不仅是在 $@ 周围的引号,而且实际上是一个不同的内置变量吗?
@ben 它是一个单一变量,但它需要双引号来具有与(损坏的)$* 不同的有用值。我相信这里有历史的进步; $* 没有按设计工作,因此发明了 $@ 来代替它;但是引用规则就是它们,它周围的双引号仍然是必需的(否则它将恢复为损坏的 $* 语义)。
“那么“$@”不仅是在 $@ 周围的引号,而且实际上是一个不同的内置变量?” -- 出于所有意图和目的,是的:stackoverflow.com/a/28099707/162094
如果您将包含 echo "$@" 的脚本称为 ./script.sh a "b c" d,那么您只会得到 a b c d 而不是 a "b c" d,这是非常不同的。
@isarandi虽然输出确实不再包含引号,但这就是您所期望的......但请放心,在脚本中, echo 接收三个参数: "a" "b c" "d" (然后shell将它们连接在一起作为它的字符串扩展)。但是,如果您使用 for i in "$@"; do echo $i; done,您将获得 a⏎b c⏎d
J
JDS

我意识到这已经得到了很好的回答,但这里是 "$@" $@ "$*" 和 $* 之间的比较

测试脚本内容:

# 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
=================================

K
KARASZI István

这里的很多答案都推荐使用带引号和不带引号的 $@$*,但似乎没有一个答案能解释它们的真正作用以及为什么要这样做。因此,让我从 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 "$@"
G
Gregory Patmore
#!/usr/bin/env bash
while [ "$1" != "" ]; do
  echo "Received: ${1}" && shift;
done;

只是认为在尝试测试 args 如何进入脚本时这可能会更有用


这绝对无助于回答他的问题,但这确实很有用。赞成!
当传递一个空参数时它会中断。您应该检查 $#
好的 args 测试器,同意 ""'' 作为参数,如果没有 args,它也是无声的。我试图解决这个问题,但需要一个 for 循环和带有 $# 的计数器。我刚刚在最后添加了这个:echo "End of args or received quoted null"
C
ColinM

如果将 $@ 包含在带有其他字符的带引号的字符串中,则当有多个参数时行为非常奇怪,只有第一个参数包含在引号内。

例子:

#!/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 中 "$@" 的语义中是有意义的。它还有助于说明 $@$* 之间的主要区别,以及它们为何有用。来自 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'
感谢您提供有关 $* 的文档引用和信息,我似乎忘记了它的存在。
呵呵。我恰恰相反,当我第一次开始编写 shell 脚本时,$@ 刚刚获得关注,我仍然需要提醒自己它就在那里。在脚本中使用 "$*" 是很常见的……然后作者会意识到它正在将他们所有的论点粉碎在一起,所以他们会尝试各种复杂的废话与分词 "$*",或者 [re]通过循环 shift 来组装一个 arg 列表以将它们一个一个拉下来......只需使用 $@ 就可以解决它。 (也有助于 bash 使用相同的助记符来访问数组成员:${var[*]} 表示它们都作为一个单词,${var[@]} 表示单词列表。)
这里真正的问题是您使用 bash -c 的方式完全没有意义。
É
Étienne

我的 SUN Unix 有很多限制,甚至 "$@" 都没有被解释为期望的。我的解决方法是 ${@}。例如,

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

顺便说一句,我必须有这个特定的脚本,因为我的 Unix 也不支持 grep -r


这是一个 Bash 问题;您正在使用 ksh
k
kvantour

有时您想传递所有参数,但前面有一个标志(例如 --flag

$ bar --flag "$1" --flag "$2" --flag "$3"

您可以通过以下方式执行此操作:

$ bar $(printf -- ' --flag "%s"' "$@")

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


D
Davidiusdadi

bar "$@" 将等同于 bar "$1" "$2" "$3" "$4"

注意引号很重要!

"$@"$@"$*"$* 在本 stackoverflow answer 中描述的转义和串联方面的行为会略有不同。

一个密切相关的用例是在这样的参数中传递所有给定的参数:

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

我使用@kvantour 答案的变体来实现这一点:

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


u
user6650302

工作正常,除非你有空格或转义字符。在这种情况下,我找不到捕获参数并发送到脚本内的 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 '@' \' )

p
puchu

"${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'"