源和出口之间有什么区别?

47

我正在编写一个Shell脚本,用于读取一个包含键值对的文件,并将这些变量设置为环境变量。但是我有一个疑问,如果我执行source file.txt,是否会将该文件中定义的变量设置为环境变量,还是应该逐行读取文件并使用export命令进行设置?

在这种情况下,source命令和export命令是否不同?


你使用的是哪个Shell? - shx2
4
为什么不尝试一下,然后自己找出答案呢? - Mat
我会非常警惕在生产环境中在shell脚本中引用用户定义的文件。想象一下,一个不满意的员工添加了rm -rf ${HOME}(或更糟的)这行命令... - johnsyweb
相关的,非常有帮助和更详细的内容请参考:https://askubuntu.com/questions/862236/source-vs-export-vs-export-ld-library-path/862256#862256 - Gabriel Staples
@johnsyweb这就是为什么我们需要代码审查。除此之外,所有它做的就是删除prod,只需要部署一个新的即可。不使用某物仅因存在潜在危险并不是一个很好的方法。"不满意的雇员"可能会做更糟糕的事情,比如捣乱数据库数据或向注册客户发送糟糕的消息或安全信息。 - James
2个回答

48

当你source这个文件时,变量将被设置但是除非设置了allexport选项,否则变量不会被导出。如果你想要导出所有的变量,使用allexport并且source这个文件比读取文件并显式地使用export更加简单。换句话说,你应该这样做:

set -a
. file.txt

我更喜欢使用.,因为它比source更易移植,但在bashsource同样有效。

请注意,导出变量并不会使其成为环境变量,它只会在任何子shell中成为环境变量。


2
请注意,导出变量并不会使其成为环境变量。它只是在任何子shell中将其作为环境变量。那么,1)什么是环境变量?2)如何将变量设置为环境变量? - Gabriel Staples
1
在大多数情况下,变量是否在环境中并不重要。环境变量将被所有子进程继承,已导出的非环境变量也是如此。至于“什么是环境变量?”……它是在环境中的变量!在shell中,实际上这只意味着它是从其父进程继承而来的变量。一些shell家族提供了一个显式的setenv函数来将变量放入环境中,但并非所有shell都提供该功能。 - William Pursell
感谢您的回复。请注意,我已经添加了一个答案,并提供了有关exportsource(.)的额外解释和示例,我认为这非常有用,链接在这里:https://dev59.com/2mUp5IYBdhLWcg3wJlAH#62626515。我自己很长时间没有编写bash脚本后,总是忘记这些东西的含义,因此我将我的答案称为“规范化”的答案,这意味着每次我忘记细节或需要刷新时,我都计划再次回到这个答案。 - Gabriel Staples
这对于需要传递大量环境变量到make文件的任何人来说都是一个好主意。 - Evan Hu

29

source.)与export(以及最后还有一些文件锁[flock]的内容)的区别

简而言之

  1. source some_script.sh,或者POSIX兼容的等效方式. some_script.sh从其他脚本中引入变量,而
  2. export my_var="something" 将变量传递给从当前脚本/进程调用/启动的其他脚本/进程

在Linux shell脚本中使用source some_script.sh. some_script.sh有点像在Python中使用import some_module,或者在C或C++中使用#include <some_header_file.h>它从被引用的脚本中引入变量。

使用export some_var="something"有点像在本地设置该变量,以便在当前脚本或进程的其余部分中可用,并且同时将其传递给从此处开始调用的所有子脚本或进程。

更多细节

所以,这个:

# export `some_var` so that it is set and available in the current
# script/process, as well as in all sub-scripts or processes which are called
# from the current script/process
export some_var="something"
# call other scripts/processes, passing in `some_var` to them automatically
# since it was just exported above! 
script1.sh  # this script now gets direct access to `some_var`
script2.sh  # as does this one
script3.sh  # and this one

就好像你已经做过这件事一样。
# set this variable for the current script/process only
some_var="something" 
# call other scripts/processes, passing in `some_var` to them **manually**
# so they can use it too 
some_var="something" script1.sh  # manually pass in `some_var` to this script
some_var="something" script2.sh  # manually pass in `some_var` to this script
some_var="something" script3.sh  # manually pass in `some_var` to this script

除了上面的第一个版本,我们在其中调用了export some_var="something",实际上是将变量递归传递或导出给子进程,因此如果我们从当前脚本/进程内部调用script1.sh,那么script1.sh将会从当前脚本中获取导出的变量,如果script1.sh调用script5.sh,而script5.sh又调用script10.sh,那么这两个脚本也会自动获取导出的变量。这与上面手动设置变量的情况形成对比,在手动设置变量的情况下,只有显式调用并设置变量的脚本才会获取它们,因此子脚本不会自动从调用脚本中获取任何变量!
如何“取消导出”一个变量
请注意,一旦您导出了一个变量,调用unset将“取消导出”,如下所示:
# set and export `some_var` so that sub-processes will receive it
export some_var="something"
script1.sh  # this script automatically receives `some_var`

# unset and un-export `some_var` so that sub-processes will no longer receive it
unset some_var
script1.sh  # this script does NOT automatically receive `some_var`

总结

  1. 导入
  2. 导出 导出
  3. 取消设置 取消导出

示例

创建此脚本:

source_and_export.sh:

#!/bin/bash

echo "var1 = $var1"
var2="world"

然后将其标记为可执行文件:
chmod +x source_and_export.sh

现在我在终端上运行一些命令来测试这个脚本中的source (.) 和 export 命令。输入你在以$开头的行后面看到的命令(不包括注释)。其他行是输出结果。按顺序逐个运行这些命令:
$ echo "$var1"              # var1 contains nothing locally.

$ var1="hello"              # Set var1 to something in the current process 
                            # only.
$ ./source_and_export.sh    # Call a sub-process.
var1 =                      # The sub-process can't see what I just set var1 
                            # to.
$ export var1               # **Export** var1 so sub-processes will receive it.
$ ./source_and_export.sh    # Call a sub-process.
var1 = hello                # Now the sub-process sees what I previously set 
                            # var1 to.
$ echo "$var1 $var2"        # But, I (my terminal) can't see var2 from the 
                            # subprocess/subscript.
hello 
$ . ./source_and_export.sh  # **Source** the sub-script to _import_ its var2 
                            # into the current process.
var1 = hello
$ echo "$var1 $var2"        # Now I CAN see what the subprocess set var2 to 
                            # because I **sourced it!**
hello world                 # BOTH var1 from the current process and var2 from 
                            # the sub-process print in the current process!
$ unset var1                # Unexport (`unset`) var1.
$ echo "$var1"              # var1 is now NOT set in the current process.
$ ./source_and_export.sh    # And the sub-process doesn't receive it either.
var1 = 
$ var1="hey"                # Set var1 again in the current process.
$ . ./source_and_export.sh  # If I **source** the script, it runs in the 
                            # current process, so it CAN see var1 from the 
                            # current process!
var1 = hey                  # Notice it prints.
$ ./source_and_export.sh    # But if I run the script as a sub-process, it can 
                            # NOT see var1 now because it was `unset` 
                            # (unexported) above and has NOT been `export`ed 
                            # again since then!
var1 =                      # So, var1 is not exported to the subprocess.
$

在进程之间使用文件作为全局变量

有时候,在编写脚本来启动程序等操作时,我会遇到 export 似乎无法正常工作的情况。在这种情况下,有时候必须将文件本身用作全局变量,以传递信息从一个程序到另一个程序。以下是如何完成这一操作的示例。在本示例中,文件 ~/temp/.do_something 的存在会被用作进程间的布尔变量:

# ------------------------------------------------------------------------------
# In program A, if the file "~/temp/.do_something" does NOT exist, 
# then create it
# ------------------------------------------------------------------------------
mkdir -p ~/temp
if [ ! -f ~/temp/.do_something ]; then
    touch ~/temp/.do_something  # create the file
fi


# ------------------------------------------------------------------------------
# In program B, check to see if the file exists, and act accordingly
# ------------------------------------------------------------------------------

mkdir -p ~/temp
DO_SOMETHING="false"
if [ -f ~/temp/.do_something ]; then
    DO_SOMETHING="true"
fi

if [ "$DO_SOMETHING" == "true" ] && [ "$SOME_OTHER_VAR" == "whatever" ]; then 
    # remove this global file "variable" so we don't act on it again
    # until "program A" is called again and re-creates the file
    rm ~/temp/.do_something 
    do_something
else
    do_something_else
fi

简单地检查文件的存在,如上所示,对于在程序和进程之间全局传递布尔条件非常有效。然而,如果您需要传递更复杂的变量,比如字符串或数字,您可能需要通过将这些值写入文件来实现。在这种情况下,您应该使用文件锁功能flock来确保进程间同步。它是一种进程安全(即“进程间”)的互斥原语。您可以在这里阅读相关信息:
  1. Shell脚本中的flock命令:https://man7.org/linux/man-pages/man1/flock.1.html。还可以参考man flockman 1 flock
  2. Linux库C命令:https://man7.org/linux/man-pages/man2/flock.2.html。还可以参考man 2 flock。在您的C文件中,您必须#include <sys/file.h>以使用此函数。

参考资料

  1. Ask Ubuntu: source vs export vs export LD_LIBRARY_PATH
  2. 我的个人实验和测试。
  3. 我将把上述示例添加到我的GitHub项目中,位于bash文件夹下:https://github.com/ElectricRCAircraftGuy/eRCaGuy_hello_world

另请参阅

  1. 关于“如何在Bash中编写、导入、使用和测试库”的个人网站文章

    这篇文章更多地讨论了使用“.”操作符引用文件以及如何通过使用类似Python的特殊魔法使文件不会在引用时运行:

    if [ "$__name__" = "__main__" ]; then
        main "$@"
    fi
    
  2. 我的回答在从shell脚本导入函数

  3. 我的回答在Bash中与Python的if __name__ == '__main__'等效的语句是什么?


2
很棒的解释!import#include 以及“将其传递给从此处调用的任何和所有子脚本或进程”确实有助于澄清这个概念! - slow-but-steady
2
谢谢留言!很高兴你觉得它有用。我也经常忘记这些东西,所以会经常回来参考自己的答案。我会把事情写下来,以便以后可以回来查看。 - Gabriel Staples
2
又回到了这里...阅读我的自己的回答。我再一次对export的作用,以及什么时候应该使用它,以及如何将变量传递给正在调用的脚本感到困惑--而我在自己的回答中找到了答案!例如:some_var="something" script1.sh #手动将 'some_var' 传递给此脚本 - Gabriel Staples

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