如何在Linux上读取和设置环境变量和Shell变量
介绍
通过 shell 会话与服务器交互时,shell 会编译许多信息以确定其行为和对资源的访问。 其中一些设置包含在配置设置中,其他设置由用户输入确定。
Shell 跟踪所有这些设置和详细信息的一种方法是通过它维护的称为 环境 的区域。 环境是 shell 每次启动包含定义系统属性的变量的会话时构建的区域。
在本指南中,我们将讨论如何与环境交互以及如何通过配置文件交互地读取或设置环境和 shell 变量。
要使用浏览器中的终端继续本教程,请单击下面的 Launch an Interactive Terminal!
按钮:
启动交互式终端!
否则,如果您想继续使用本地系统或远程服务器,请打开终端并在此处运行本教程中的命令。
环境和环境变量如何工作
每次产生 shell 会话时,都会发生一个进程来收集和编译应该对 shell 进程及其子进程可用的信息。 它从系统上的各种不同文件和设置中获取这些设置的数据。
环境提供了一个媒介,shell 进程可以通过它获取或设置设置,然后将这些设置传递给它的子进程。
环境被实现为表示键值对的字符串。 如果传递了多个值,它们通常用冒号 (:
) 字符分隔。 每对通常看起来像这样:
KEY=value1:value2:...
如果该值包含重要的空格,则使用引号:
KEY="value with spaces"
这些场景中的关键是变量。 它们可以是两种类型之一,环境变量或 shell 变量。
环境变量是为当前shell定义的变量,由任何子shell或进程继承。 环境变量用于将信息传递到从 shell 生成的进程中。
Shell 变量 是专门包含在设置或定义它们的 shell 中的变量。 它们通常用于跟踪临时数据,例如当前工作目录。
按照惯例,这些类型的变量通常使用全部大写字母来定义。 这有助于用户区分其他上下文中的环境变量。
打印外壳和环境变量
每个 shell 会话都跟踪自己的 shell 和环境变量。 我们可以通过几种不同的方式访问这些。
我们可以使用 env
或 printenv
命令查看所有环境变量的列表。 在默认状态下,它们的功能应该完全相同:
printenv
您的 shell 环境可能设置了更多或更少的变量,其值与以下输出不同:
OutputSHELL=/bin/bash TERM=xterm USER=demouser LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:su=37;41:sg=30;43:ca:... MAIL=/var/mail/demouser PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games PWD=/home/demouser LANG=en_US.UTF-8 SHLVL=1 HOME=/home/demouser LOGNAME=demouser LESSOPEN=| /usr/bin/lesspipe %s LESSCLOSE=/usr/bin/lesspipe %s %s _=/usr/bin/printenv
这对于 printenv
和 env
的输出来说都是相当典型的。 这两个命令之间的区别仅在它们更具体的功能上很明显。 例如,使用 printenv
,您可以请求单个变量的值:
printenv PATH
Output/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games
另一方面,env
允许您通过将一组变量定义传递给如下命令来修改程序运行的环境:
env VAR1="value" command_to_run command_options
正如我们在上面了解到的,子进程通常继承父进程的环境变量,这使您有机会覆盖值或为子进程添加其他变量。
正如您从我们的 printenv
命令的输出中看到的,在我们的系统文件和进程中设置了相当多的环境变量,而无需我们的输入。
这些显示了环境变量,但是我们如何看到 shell 变量呢?
为此可以使用 set
命令。 如果我们在没有任何附加参数的情况下键入 set
,我们将获得所有 shell 变量、环境变量、局部变量和 shell 函数的列表:
set
OutputBASH=/bin/bash BASHOPTS=checkwinsize:cmdhist:expand_aliases:extglob:extquote:force_fignore:histappend:interactive_comments:login_shell:progcomp:promptvars:sourcepath BASH_ALIASES=() BASH_ARGC=() BASH_ARGV=() BASH_CMDS=() . . .
这通常是一个巨大的列表。 您可能希望将其通过管道传输到寻呼机程序中,以便更轻松地处理输出量:
set | less
我们收到的额外信息量有点庞大。 例如,我们可能不需要知道所有已定义的 bash 函数。
我们可以通过指定 set
应该在 POSIX 模式下运行来清理输出,这不会打印 shell 函数。 我们可以在子 shell 中执行它,这样它就不会改变我们当前的环境:
(set -o posix; set)
这将列出所有已定义的环境和 shell 变量。
我们可以尝试将此输出与 env
或 printenv
命令的输出进行比较,以尝试仅获取 shell 变量的列表,但这将是不完善的,因为这些命令的方式不同输出信息:
comm -23 <(set -o posix; set | sort) <(env | sort)
这可能仍会包含一些环境变量,因为 set
命令输出引用的值,而 printenv
和 env
命令不引用字符串的值.
这仍然可以让您对会话中设置的环境和 shell 变量有一个很好的了解。
这些变量用于各种事情。 它们提供了一种为进程之间的会话设置持久值的替代方法,而无需将更改写入文件。
常见的环境变量和 Shell 变量
一些环境变量和 shell 变量非常有用,并且经常被引用。 以下是您会遇到的一些常见环境变量:
SHELL
:这描述了将解释您键入的任何命令的 shell。 在大多数情况下,默认情况下这将是 bash,但如果您更喜欢其他选项,可以设置其他值。TERM
:指定运行 shell 时要模拟的终端类型。 针对不同的操作需求,可以模拟不同的硬件终端。 不过,您通常不需要担心这一点。USER
:当前登录用户。PWD
:当前工作目录。OLDPWD
:上一个工作目录。 这是由 shell 保存的,以便通过运行cd -
切换回之前的目录。LS_COLORS
:这定义了用于选择性地将彩色输出添加到ls
命令的颜色代码。 这用于区分不同的文件类型并一目了然地向用户提供更多信息。MAIL
:当前用户邮箱的路径。PATH
:系统在查找命令时将检查的目录列表。 当用户键入命令时,系统将按此顺序检查目录中的可执行文件。LANG
:当前语言和本地化设置,包括字符编码。HOME
:当前用户的主目录。_
:最近执行的命令。
除了这些环境变量之外,您会经常看到的一些 shell 变量是:
BASHOPTS
:执行 bash 时使用的选项列表。 这对于确定 shell 环境是否会按照您希望的方式运行很有用。BASH_VERSION
:正在执行的 bash 版本,以人类可读的形式。BASH_VERSINFO
:bash 的版本,在机器可读的输出中。COLUMNS
:用于在屏幕上绘制输出的列宽数。DIRSTACK
:pushd
和popd
命令可用的目录堆栈。HISTFILESIZE
:存储到文件的命令历史行数。HISTSIZE
:内存中允许的命令历史行数。HOSTNAME
:此时计算机的主机名。IFS
:用于分隔命令行输入的内部字段分隔符。 默认情况下,这是一个空格。PS1
:主要命令提示符定义。 这用于定义启动 shell 会话时提示的外观。PS2
用于声明命令跨多行时的辅助提示。SHELLOPTS
:可以使用set
选项设置的 Shell 选项。UID
:当前用户的UID。
设置 Shell 和环境变量
为了更好地理解 shell 和环境变量的区别,并介绍设置这些变量的语法,我们将做一个小演示。
创建外壳变量
我们将从在当前会话中定义一个 shell 变量开始。 这很容易实现; 我们只需要指定一个名称和一个值。 我们将遵守保持变量名全部大写的约定,并将其设置为一个简单的字符串。
TEST_VAR='Hello World!'
在这里,我们使用了引号,因为我们的变量的值包含一个空格。 此外,我们使用了单引号,因为感叹号是 bash shell 中的一个特殊字符,如果它没有被转义或放入单引号中,它通常会扩展为 bash 历史记录。
我们现在有一个 shell 变量。 此变量在我们当前的会话中可用,但不会传递给子进程。
我们可以通过在 set
输出中查找我们的新变量来看到这一点:
set | grep TEST_VAR
OutputTEST_VAR='Hello World!'
我们可以通过使用 printenv
来验证这不是环境变量:
printenv | grep TEST_VAR
不应返回任何输出。
让我们借此机会演示一种访问任何 shell 或环境变量的值的方法。
echo $TEST_VAR
OutputHello World!
如您所见,通过在变量前面加上 $
符号来引用变量的值。 shell 认为这意味着它应该在遇到这个时替换变量的值。
所以现在我们有了一个shell变量。 它不应该传递给任何子进程。 我们可以从当前的 shell 中生成一个 new bash shell 来演示:
bash echo $TEST_VAR
如果我们键入 bash
来生成一个子 shell,然后尝试访问变量的内容,则不会返回任何内容。 这是我们所期望的。
通过输入 exit
回到我们原来的 shell:
exit
创建环境变量
现在,让我们将 shell 变量转换为环境变量。 我们可以通过 导出 变量来做到这一点。 这样做的命令被恰当地命名为:
export TEST_VAR
这会将我们的变量更改为环境变量。 我们可以通过再次检查我们的环境清单来检查这一点:
printenv | grep TEST_VAR
OutputTEST_VAR=Hello World!
这一次,我们的变量出现了。 让我们再次尝试使用我们的子 shell 进行实验:
bash echo $TEST_VAR
OutputHello World!
伟大的! 我们的子 shell 已收到其父 shell 设置的变量。 在我们退出这个子 shell 之前,让我们尝试导出另一个变量。 我们可以像这样一步设置环境变量:
export NEW_VAR="Testing export"
测试它是否作为环境变量导出:
printenv | grep NEW_VAR
OutputNEW_VAR=Testing export
现在,让我们回到原来的 shell:
exit
让我们看看我们的新变量是否可用:
echo $NEW_VAR
什么都没有返回。
这是因为环境变量只传递给子进程。 没有设置父 shell 的环境变量的内置方法。 这在大多数情况下都很好,并且可以防止程序影响调用它们的操作环境。
NEW_VAR
变量在我们的子 shell 中被设置为环境变量。 该变量对其自身和任何 its 子 shell 和进程都可用。 当我们退出回到我们的主 shell 时,那个环境被破坏了。
降级和取消设置变量
我们仍然将 TEST_VAR
变量定义为环境变量。 我们可以通过键入以下命令将其更改回 shell 变量:
export -n TEST_VAR
它不再是环境变量:
printenv | grep TEST_VAR
但是,它仍然是一个 shell 变量:
set | grep TEST_VAR
OutputTEST_VAR='Hello World!'
如果我们想完全取消设置一个变量,无论是 shell 还是环境变量,我们可以使用 unset
命令:
unset TEST_VAR
我们可以验证它不再设置:
echo $TEST_VAR
没有返回任何内容,因为变量未设置。
登录时设置环境变量
我们已经提到许多程序使用环境变量来决定如何操作的细节。 我们不希望每次启动新的 shell 会话时都必须设置重要变量,并且我们已经看到登录时已经设置了多少变量,那么我们如何自动创建和定义变量呢?
这实际上是一个比最初看起来更复杂的问题,因为 bash shell 读取的配置文件取决于它的启动方式。
登录、非登录、交互式和非交互式 Shell 会话之间的区别
bash shell 根据会话的启动方式读取不同的配置文件。
不同会话之间的一个区别是外壳是作为 login 还是 non-login 会话生成的。
login shell 是一个以验证用户身份开始的 shell 会话。 如果您正在登录终端会话或通过 SSH 并进行身份验证,您的 shell 会话将被设置为登录 shell。
如果您从经过身份验证的会话中启动新的 shell 会话,就像我们通过从终端调用 bash
命令所做的那样,则会启动 non-login shell 会话。 当您启动子 shell 时,系统不会要求您提供身份验证详细信息。
可以做出的另一个区别是外壳会话是交互式的还是非交互式的。
interactive shell 会话是附加到终端的 shell 会话。 non-interactive shell 会话是一个未附加到终端会话的会话。
因此,每个 shell 会话都被分类为登录或非登录以及交互或非交互。
以 SSH 开头的正常会话通常是交互式登录 shell。 从命令行运行的脚本通常在非交互式、非登录 shell 中运行。 终端会话可以是这两个属性的任意组合。
将 shell 会话分类为登录 shell 还是非登录 shell 会影响读取哪些文件来初始化 shell 会话。
作为登录会话启动的会话将首先从 /etc/profile
文件中读取配置详细信息。 然后它将在用户的主目录中查找第一个登录 shell 配置文件,以获取特定于用户的配置详细信息。
它从 ~/.bash_profile
、~/.bash_login
和 ~/.profile
中读取它可以找到的第一个文件,并且不再读取任何其他文件。
相反,定义为非登录 shell 的会话将读取 /etc/bash.bashrc
,然后读取用户特定的 ~/.bashrc
文件来构建其环境。
非交互式 shell 读取名为 BASH_ENV
的环境变量并读取指定的文件以定义新环境。
实现环境变量
如您所见,我们通常需要查看各种不同的文件来放置我们的设置。
这提供了很大的灵活性,可以在我们希望登录 shell 中的某些设置以及非登录 shell 中的其他设置的特定情况下提供帮助。 但是,大多数情况下,我们希望在两种情况下都使用相同的设置。
幸运的是,大多数 Linux 发行版都将登录配置文件配置为获取非登录配置文件。 这意味着您可以在非登录配置文件中定义所需的环境变量。 然后将在两种情况下读取它们。
我们通常会设置用户特定的环境变量,并且我们通常希望我们的设置在登录和非登录 shell 中都可用。 这意味着定义这些变量的地方在 ~/.bashrc
文件中。
现在打开这个文件:
nano ~/.bashrc
这很可能已经包含相当多的数据。 这里的大部分定义都是用于设置 bash 选项,与环境变量无关。 您可以像在命令行中一样设置环境变量:
export VARNAME=value
任何新的环境变量都可以添加到 ~/.bashrc
文件的任何位置,只要它们没有放在另一个命令或 for 循环的中间。 然后我们可以保存并关闭文件。 下次您启动 shell 会话时,您的环境变量声明将被读取并传递给 shell 环境。 您可以通过键入以下内容强制当前会话立即读取文件:
source ~/.bashrc
如果您需要设置系统范围的变量,您可能需要考虑将它们添加到 /etc/profile
、/etc/bash.bashrc
或 /etc/environment
。
结论
环境变量和 shell 变量始终存在于您的 shell 会话中,并且非常有用。 它们是父进程为其子进程设置配置详细信息的一种有趣方式,并且是一种在文件之外设置选项的方式。
这在特定情况下有很多优点。 例如,一些部署机制依赖环境变量来配置身份验证信息。 这很有用,因为它不需要将这些保存在外部各方可能看到的文件中。
还有许多其他更普通但更常见的场景,您需要读取或更改系统环境。 这些工具和技术应该为您进行这些更改和正确使用它们提供了良好的基础。