一个shell脚本如何将所有变量传递给另一个脚本?

261

假设我有一个名为test.sh的shell / bash脚本,其内容为:

#!/bin/bash

TESTVARIABLE=hellohelloheloo
./test2.sh

我的test2.sh长这样:

#!/bin/bash

echo ${TESTVARIABLE}

这种方法行不通。我不想将所有变量作为参数传递,因为在我看来这有些过度了。

还有其他的方式吗?

7个回答

345

你基本上有两个选择:

  1. 在执行第二个脚本之前,将变量设置为环境变量(export TESTVARIABLE)。
  2. 加载第二个脚本,即 . test2.sh,它将在同一个 shell 中运行。这样做可以让你轻松共享更复杂的变量,比如数组,但也意味着其他脚本可以修改源 shell 中的变量。

更新:

要使用 export 设置环境变量,你可以使用现有变量:

A=10
# ...
export A

这应该适用于 bashsh。而 bash 还允许将它们组合起来使用,例如:

export A=10

这个方法也适用于我自己的 sh(实际上是 bash,可以使用 echo $SHELL 命令来检查)。但我不相信这在所有的 sh 中都能保证有效,所以最好还是分开处理。

你以这种方式输出的任何变量都可以在你执行的脚本中被看到,例如:

a.sh:

#!/bin/sh

MESSAGE="hello"
export MESSAGE
./b.sh

b.sh:

#!/bin/sh

echo "The message is: $MESSAGE"

接着:

$ ./a.sh
The message is: hello

这两个脚本都是Shell脚本只是巧合。环境变量可以传递给您执行的任何进程,例如,如果我们使用Python,它可能如下所示:

a.sh:

#!/bin/sh

MESSAGE="hello"
export MESSAGE
./b.py

b.py:

#!/usr/bin/python

import os

print 'The message is:', os.environ['MESSAGE']

采购(Sourcing):

相反,我们可以这样采购:

a.sh:

#!/bin/sh

MESSAGE="hello"

. ./b.sh

b.sh:

#!/bin/sh

echo "The message is: $MESSAGE"

然后:

$ ./a.sh
The message is: hello

这更或多或少地“导入”了b.sh的内容并在同一个 shell中执行它。请注意,我们不必导出变量即可访问它。这隐式共享了您拥有的所有变量,并允许其他脚本在 shell 中添加/删除/修改变量。当然,在此模型中,您的两个脚本应该是相同的语言(shbash)。以下是一个示例,说明我们如何互传消息:

a.sh:

#!/bin/sh

MESSAGE="hello"

. ./b.sh

echo "[A] The message is: $MESSAGE"

b.sh:

#!/bin/sh

echo "[B] The message is: $MESSAGE"

MESSAGE="goodbye"

然后:

$ ./a.sh
[B] The message is: hello
[A] The message is: goodbye

这在bash中同样适用。它还使得共享更复杂的数据变得容易,你可能无法将其表示为环境变量(至少需要你付出一些努力),例如数组或关联数组。


2
如果我需要将$1传递给子shell(因为'sudo sh -c ...'是从脚本中调用的),该怎么办?我必须将$1塞入环境变量中,导出它,并在命令中使用该变量吗? - Urhixidur
1
只是补充一下,如果您需要sudo访问权限,可以使用sudo -E ...来保留环境变量。 - yucer
3
@FatalError,你能否解释一下最后一个a.sh中调用". ./b.sh"的神奇之处吗?第一个点是什么意思?顺便说一下,它工作得很好! - Deian
你也可以将变量和值设置到文件中,以此方式分享它们。 - newshorts
1
@Deian,句点(dot)是bash内置的“source”的简写。 - Kirill Kost

37

致命错误指出一种简单的可能性:引用您的第二个脚本!如果您担心这个第二个脚本可能会更改一些宝贵的变量,您可以始终在子Shell中引用它:

Fatal Error给了一个明确的建议:引用你的第二个脚本!如果你担心这个第二个脚本会修改一些你珍贵的变量,你总是可以在一个子shell中引用它:

( . ./test2.sh )

使用括号可以将源代码放在子 shell 中运行,这样父 shell 将无法看到 test2.sh 所做的修改。


还有另一种可能性应该在此提到:使用 set -a

来自POSIX set 参考文献

-a:当打开此选项时,对每个赋值的变量设置 export 属性;请参见 IEEE Std 1003.1-2001 的基本定义卷,第 4.21 节,变量赋值。如果分配在命令中一个实用程序名称之前,则在实用程序完成后,export 属性不会持续存在于当前执行环境中,但特殊内置实用程序之一的前面会导致内置完成后 export 属性持久存在。如果在命令中赋值不是在实用程序名称之前或者赋值是由 getoptsread 实用程序操作的结果,则导出属性将持续存在,直到取消设置变量为止。

来自Bash 手册

-a:标记要导出到后续命令环境的变量和函数。

因此,在您的情况下:

set -a
TESTVARIABLE=hellohelloheloo
# ...
# Here put all the variables that will be marked for export
# and that will be available from within test2 (and all other commands).
# If test2 modifies the variables, the modifications will never be
# seen in the present script!
set +a

./test2.sh

 # Here, even if test2 modifies TESTVARIABLE, you'll still have
 # TESTVARIABLE=hellohelloheloo

请注意规范仅仅指定当使用set -a时,变量会被标记为导出。也就是说:

set -a
a=b
set +a
a=c
bash -c 'echo "$a"'

将会回显c而不是空行或b(也就是说,set +a不会取消导出标记,也不仅仅为导出环境“保存”赋值的值)。这当然是最自然的行为。

结论:使用set -a/set +a可能比手动导出所有变量更少繁琐。它优于源化第二个脚本,因为它适用于任何命令,而不仅仅是在相同的Shell语言中编写的命令。


2
“set -a”和“set +a”非常有用!我将其与被接受的答案一起使用,完美地解决了问题。谢谢! - amurrell
1
source 是帮助我的关键词。执行 "source some_script_with_exports.sh" 而不是 "bash some_script ..." 可以正常工作。 - mr.tarsa

19

实际上,有一种比导出、取消或重新源化更容易的方法(至少在bash中,只要您愿意手动传递环境变量):

假设a.sh:

#!/bin/bash
secret="winkle my tinkle"
echo Yo, lemme tell you \"$secret\", b.sh!
Message=$secret ./b.sh

以及 b.sh 是

#!/bin/bash
echo I heard \"$Message\", yo

观察到的输出为

[rob@Archie test]$ ./a.sh
Yo,让我告诉你“winkle my tinkle”,b.sh!
我听到了“winkle my tinkle”,yo

魔力在于a.sh中最后一行,只有在调用./b.sh期间,Message才被设置为来自a.shsecret的值。 基本上,这有点像命名参数/参数。不仅如此,它甚至适用于像$DISPLAY这样的变量,它控制应用程序在哪个X服务器上启动。

请记住,环境变量列表的长度不是无限的。在我的系统上,拥有相对简单内核的情况下,xargs --show-limits告诉我参数缓冲区的最大大小为2094486字节。从理论上讲,如果您的数据超过这个大小(管道呢?),那么您使用的shell脚本方式就是错误的。


13

在Bash中,如果您在子shell内导出变量并使用括号(如所示),则可以避免泄漏导出的变量:

#!/bin/bash

TESTVARIABLE=hellohelloheloo
(
export TESTVARIABLE    
source ./test2.sh
)
这里的优点是,在你从命令行运行脚本后,你不会看到$TESTVARIABLE泄漏到你的环境中:

这里的好处在于,在你从命令行运行脚本之后,你不会在环境变量中看到$TESTVARIABLE的泄漏:

$ ./test.sh
hellohelloheloo
$ echo $TESTVARIABLE
                            #empty! no leak
$

我使用了它,但似乎不起作用 - 我写了: #!/bin/bash for (( i=1;i<=3;i++)) do (export i source ./script2.sh) done 错误提示:./script2.sh: 没有那个文件或目录。 - Pranjal Gupta
1
子shell实际上并不需要,因为顶层环境(命令行$提示符)永远不会看到导出的$TESTVARIABLE。导出只是将变量的副本传递给任何后续子进程。除了将值保存到存储中并在父进程脚本中稍后读取该存储之外,无法将变量传递回父进程链。将管道传输到父脚本的第二个副本是可能的,但那不是同一个进程这里有一个很好的解释 - DocSalvager

8

除了Fatal Error的回答外,还有一种方法可以将变量传递给另一个shell脚本。

上述建议的解决方案存在一些缺点:

  1. 使用Export:它会导致变量存在于其作用域之外,这不是一个好的设计实践。
  2. 使用Source:它可能会导致名称冲突或意外覆盖已预定义的另一个shell脚本文件中的变量。

我们还有另一种简单的解决方案可供使用。 考虑您发布的示例,

test.sh

#!/bin/bash

TESTVARIABLE=hellohelloheloo
./test2.sh "$TESTVARIABLE"

test2.sh

#!/bin/bash

echo $1

输出

hellohelloheloo

同时需要注意,如果我们传递的是含有多个单词的字符串,则""是必要的。再来看一个例子。

master.sh

#!/bin/bash
echo in master.sh
var1="hello world"
sh slave1.sh $var1
sh slave2.sh "$var1"
echo back to master

slave1.sh

#!/bin/bash
echo in slave1.sh
echo value :$1

slave2.sh

#!/bin/bash
echo in slave2.sh
echo value : $1

输出

in master.sh
in slave1.sh
value :"hello
in slave2.sh
value :"hello world"

由于 这个链接 中描述的原因,这种情况就会发生。

6
使用source实际上是一件好事。关于你担心的“意外覆盖预定义变量”的问题,你可以在一个子shell 中使用source。这样完全解决了这个问题。 - gniourf_gniourf
@gniourf_gniourf 怎样将内容源入到子shell中? - Toskan
1
@Toskan,我的回答中提到了这个:( . ./test2.sh )。括号会使 Bash 在子 shell 中运行其内容。 - gniourf_gniourf

3
另一个选项是使用eval。仅当字符串受信任时才适用。第一个脚本可以回显变量赋值: echo "VAR=myvalue" 然后: eval $(./first.sh) ./second.sh 当您想要为的第二个脚本设置环境变量不是在bash中并且您也不想export变量时,这种方法特别有趣,也许因为它们很敏感,您不希望它们持久存在。

2

另一种方法,对我来说稍微简单些的是使用命名管道。命名管道提供了一种在不同进程之间同步和发送消息的方式。

A.bash:

#!/bin/bash
msg="The Message"
echo $msg > A.pipe

B.bash:

#!/bin/bash
msg=`cat ./A.pipe`
echo "message from A : $msg"

使用方法:

$ mkfifo A.pipe #You have to create it once
$ ./A.bash & ./B.bash # you have to run your scripts at the same time

B.bash会等待消息,一旦A.bash发送了消息,B.bash就会继续它的工作。


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