Contents

使用source命令将文件包含到 bash 脚本中

1. 概述

在本教程中,我们将学习如何在 bash 脚本中包含文件。这是一种导入环境变量、重用现有代码或从另一个脚本中执行一个脚本的方法。

两个说明性示例涉及导入环境变量和构建函数库。

2. source命令

内置的 bash  source命令读取并执行文件的内容。如果源文件是 bash 脚本,则整体效果归结为运行它。我们可以在终端或 bash 脚本中使用此命令。

要获取有关此命令的文档,我们应该在终端中键入help source我们假设 Bash(Bourne again shell)不在 POSIX 模式下,贯穿整个教程。

2.1. Bash 变量和函数定义

当我们使用source命令执行脚本时,它在我们获取它的同一个 shell 中运行。因此,脚本会从发出source命令的 shell 访问所有变量。这种通信以相反的方向工作——源文件中的所有定义都在父 shell 中可用

另一方面,当我们使用bash 命令或仅通过键入其名称来运行脚本时,就会创建一个新的 shell 实例。*因此,脚本只能访问在父 shell 中使用关键字*export定义的变量和函数。此外,后代 shell 的所有定义在退出时都会消失。

作为一个简单的例子,让我们编写脚本test_var

#!/bin/bash
echo "expVar = $expVar"
echo "myVar = $myVar"

让我们在终端中设置变量,然后以两种方式运行脚本来比较结果:

export expVar = "Exported variable"
myVar="My variable"
./test_var
expVar = Exported variable
myVar = 
source test_var
expVar = Exported
variable myVar = My variable

此特性使source命令对于在不同文件之间共享内容和功能非常有用。

2.2. 其他重要财产

此外,source命令的功能还包括:

  • 它在当前 shell的PATH变量中列出的文件夹中搜索脚本
  • 它识别给脚本的绝对或相对路径
  • 该脚本不需要是可执行的
  • 它返回脚本中最后执行的命令的状态
  • 如果源脚本发出exit 命令,源脚本也退出

3. 从文件传递环境变量

在此示例中,我们将一组环境变量导入到脚本中。众所周知的案例是从用户的*.bash_profile*(或*.profile*)读取和设置变量。

这在cron 作业的情况下特别有用,因为cron的shell通常有一组非常稀疏的环境变量。

假设我们要将用户交互式 shell 的所有环境变量传递给 cron 作业脚本。因此,让我们通过采购配置文件开始脚本:

#!/bin/bash
source /etc/profile
source ~/.bash_profile
source ~/.bashrc
# do something useful

当然,我们不需要限制自己重复使用用户的配置文件。我们可以在文件中收集任何有用且有意义的变量并将它们导入到脚本中。

4. 构建函数库

让我们创建一个可重用函数库,在一个单独的文件中定义它们,然后在另一个脚本中使用它们。

例如,假设我们的lib_example库包含两个函数:

  1. my_name查找当前用户的名称(使用whoami 命令)
  2. my_used_disk_space计算用户主目录占用的磁盘空间(使用du 命令)。此外,cutdu的输出中删除文件夹名称。

让我们定义函数:

#!/bin/bash
 function my_name()
 {
 	whoami
 }
 
 function my_used_disk_space()
 {
 	du -sh ~ | cut -f1
 }

source命令的帮助下,我们可以从另一个脚本lib_test访问这些函数:

#!/bin/bash
source lib_example
echo "My name is $(my_name), I am using $(my_used_disk_space)"

让我们启动脚本并检查结果:

./lib_test
My name is joe, I am using 46G

5. 设置正确的路径

让我们仔细看看上一节中示例库的结构。 源代码lib_example 和源代码lib_test两个脚本都位于同一目录中。但是,命令source lib_example 会尝试在调用*lib_test的目录中查找脚本

因此,该解决方案非常容易出错。事实上,只有当我们从它所在的文件夹中启动lib_test时,它才能正常工作。从另一个目录调用脚本后,我们得到一堆错误。

让我们研究一下改进库结构的方法。

5.1. 提供绝对路径

我们可以在源脚本的名称前加上其完整路径:

source /home/joe/example/lib_example

这是一个坚如磐石的解决方案,尽管当我们改变它的位置时它需要重写脚本。

5.2. 使用dirname命令

让我们尽量避免路径的硬编码。我们知道这两个脚本都在同一个文件夹中。此外,我们使用dirname 从包装脚本的名称中提取路径

我们应该将lib_test脚本中source命令的参数更改为:

source $(dirname "$0")/lib_example

dirname参数*$0*只是采购脚本的全名。

5.3. 使用PATH环境变量

此外,由于sourcePATH中列出的文件夹中搜索文件,我们应该重新组织我们的库。

让我们检查一下示例用户joePATH

/home/joe/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/home/joe/.local/bin:/home/joe/.local/bin

现在,我们要将lib_example复制到*/home/joe/.local/bin*文件夹。

因此,源代码现在与一开始的形式相同——source lib_example——但源脚本可以在PATH 上找到,并且脚本lib_test可以正常工作

6. 使用source命令传递参数

Bash 脚本接受参数。因此,source命令允许将参数传递给脚本。但是,如果从另一个脚本中获取,其背后的逻辑比在终端中使用要复杂一些。

让我们从最简单的例子开始,一个输出所有 bash 参数的脚本arg_list

#!/bin/bash
echo "My argument(s): $@"

让我们检查一下它在另一个脚本arg_test中是如何工作的:

#!/bin/bash
source arg_list foo_bar

之后,我们使用两个参数启动新脚本:

./arg_test foo bar
My argument(s): foo_bar

我们应该注意到source将参数foo_bar传递给包含的脚本,独立于包装脚本的参数foobar

让我们稍微改变一下 arg_test以显示source命令的一个基本特性。我们将跳过arg_list脚本的参数:

#!/bin/bash
source arg_list

再来一次,让我们检查一下结果:

./arg_test foo bar
My argument(s): foo bar

因此,它与预期的空字符串不同。

事实上,在另一个脚本中,将包装脚本的参数传递给源脚本,除非后者的参数明确给出。

这是我们应该注意的重要一点——主要是如果包含的脚本的操作依赖于其参数的数量。

7. 可用性和兼容性

source命令的实用性因它的流行而有所降低。因此,我们应该了解 bash shell 语言的不同实现

事实上,只有 Bourne again shell (Bash) 以这种形式提供了这个命令。Debian Almquist Shell (Dash) 或 Bourne Shell (sh),仅举几例,提供 . 命令(“点”)代替。当然,‘dot’ 也存在于 Bash 中。

‘dot’ 命令的用途与source相同,但其功能略有不同。

另一方面,它的优势是POSIX 合规性。因此**,使用 ‘dot’ 的脚本是可移植的,并且可以与任何 POSIX 兼容的 bash 语言实现一起运行**。

此外,Bash 还可以在 POSIX 模式下工作,从而改变source的行为。在不详细说明的情况下,所提供的命令描述涉及非 POSIX 模式下的 Bash。