Linux 中的环境变量

ShellBeginner
立即练习

简介

程序通常需要一些小型配置,例如主目录、语言、搜索路径或应用设置。Linux 进程通过环境接收其中许多值。环境是一组带名称的字符串。

本实验将分层构建这些概念。你将先创建一个只属于当前 Shell 的变量,然后将变量导出到子进程,查看标准变量,扩展 PATH,最后让设置出现在未来的 Zsh 会话中。每个步骤都会留下可观察的文件或命令结果,帮助你验证 Shell 实际执行了什么。

创建并展开 Shell 变量

在此步骤中,你将创建一个 Shell 变量,并观察引号如何影响变量展开。

Shell 变量会在当前 Shell 中将名称与值关联起来。赋值语法中的 = 两侧不能有空格。为值加引号可以保留其中的空格:

cd /home/labex/project/environment-lab
course_name="Linux for Noobs"

在名称前加上 $,Shell 就会将其展开为对应的值:

echo "$course_name"

双引号允许变量展开,并将展开结果保留为一个参数。单引号会按字面保留美元符号。对比下面两条命令:

printf 'double: %s\n' "$course_name"
printf 'single: %s\n' '$course_name'

将展开后的值保存为可观察的结果:

printf 'course=%s\n' "$course_name" > shell-variable.txt
cat shell-variable.txt

除非导出,否则这个变量只存在于当前 Shell 中。

观察子进程继承

在此步骤中,你将比较普通 Shell 变量和导出变量在子进程中的表现。

每条命令都在某个进程中运行。子进程会从父进程接收已导出的环境变量,但不会接收普通 Shell 变量。

返回工作区,并分别创建这两类变量:

cd /home/labex/project/environment-lab
local_message="visible only in this shell"
export SHARED_MESSAGE="visible in child processes"

创建一个小型 Bash 子进程,同时打印这两个名称。带引号的 heredoc 标记会阻止当前 Shell 在写入脚本时展开变量:

cat > inspect-child.sh <<'EOF'
#!/bin/bash
printf 'local=%s\n' "${local_message:-<missing>}"
printf 'shared=%s\n' "${SHARED_MESSAGE:-<missing>}"
EOF
chmod +x inspect-child.sh

运行该脚本,将输出重定向到文件,然后显示文件内容。将这两个动作分成单独的命令,可以清楚地区分哪个动作运行子进程,哪个动作查看保存的结果:

./inspect-child.sh > child-environment.txt
cat child-environment.txt

普通变量应显示为 <missing>,而 SHARED_MESSAGE 会被继承。确认导出的名称存在于环境中:

env | grep '^SHARED_MESSAGE='

查看标准环境变量

在此步骤中,你将查看用于描述账户、工作目录、终端和命令搜索路径的标准变量。

Linux Shell 通常会提供以下有用的标准变量:

  • HOME 表示你的主目录。
  • USER 表示你的账户名称。
  • SHELL 表示已配置的登录 Shell。
  • PWD 跟踪当前工作目录。
  • PATH 列出命令搜索目录,各目录之间用冒号分隔。
  • TERM 向交互式程序描述终端功能。

使用 printenv 按名称显示选定的值。该命令读取已导出的变量:

printenv HOME USER SHELL PWD TERM

PATH 按每行一个条目显示更容易阅读。通过 tr 传递该值,将每个冒号替换为换行符:

printf '%s\n' "$PATH" | tr ':' '\n'

根据本实验中预期值已知的变量创建一份稳定的报告:

cd /home/labex/project/environment-lab
printf 'HOME=%s\nUSER=%s\n' "$HOME" "$USER" > standard-environment.txt
cat standard-environment.txt

使用个人命令扩展 PATH

在此步骤中,你将把个人可执行文件目录添加到 PATH,然后从另一个目录按名称运行命令。

当你输入的命令不包含斜杠时,Shell 会按照从左到右的顺序搜索 PATH 中的目录。你可以添加个人可执行文件目录,而不替换现有的搜索路径。

创建名为 course-status 的命令:

mkdir -p "$HOME/bin"
cat > "$HOME/bin/course-status" <<'EOF'
#!/bin/bash
echo "Linux learning environment is ready"
EOF
chmod +x "$HOME/bin/course-status"

将该目录追加到当前 PATH。保留 $PATH 可以继续访问标准命令:

export PATH="$PATH:$HOME/bin"

让 Shell 告诉你将执行哪个文件,然后从另一个目录运行该命令:

command -v course-status
cd /tmp
course-status

将命令输出保存到实验工作区:

course-status > /home/labex/project/environment-lab/path-command.txt

为未来的 Zsh 会话持久化设置

在此步骤中,你将配置未来的 Zsh 会话,并区分持久化配置与当前 Shell 状态。

导出变量会修改当前进程及其未来的子进程,但不会编辑配置文件。只有当启动文件重新创建某个设置时,该设置才会出现在后续终端中。LabEx Terminal 使用 Zsh,其交互式启动文件是 ~/.zshrc

追加一个带有清晰标记的代码块。每条 export 命令都会在新的交互式 Zsh 启动时重新创建一个环境设置。这个面向初学者的版本特意保持启动文件直接明了:保留现有的 PATH,然后在末尾添加个人目录。

cat >> "$HOME/.zshrc" <<'EOF'
## LABEX_ENV_LAB_START
export PROJECT_DIR="$HOME/project"
export PATH="$PATH:$HOME/bin"
## LABEX_ENV_LAB_END
EOF

source 命令会在当前 Shell 中读取文件。现在重新加载 .zshrc,这样无需关闭 Terminal 就能使用其中的设置:

source "$HOME/.zshrc"

确认重新加载的变量可用:

printf 'PROJECT_DIR=%s\n' "$PROJECT_DIR"

你应该看到 PROJECT_DIR=/home/labex/project。你还可以启动一个新的 Zsh 进程,证明未来的会话会加载相同的设置。在 zsh -ic 中,-i 请求启动交互式 Shell,以便读取 .zshrc-c 提供该 Shell 要执行的命令。将它输出的两行重定向到 future-shell.txt,然后查看文件:

zsh -ic 'printf "PROJECT_DIR=%s\n" "$PROJECT_DIR"; command -v course-status' > /home/labex/project/environment-lab/future-shell.txt
cat /home/labex/project/environment-lab/future-shell.txt

第一行应显示 /home/labex/project;第二行应显示 /home/labex/bin/course-status

最后,了解当前状态与持久化配置之间的区别。创建并删除一个临时环境变量:

export TEMP_NOTE="remove me"
printenv TEMP_NOTE
unset TEMP_NOTE
printenv TEMP_NOTE

最后一条 printenv 不会产生输出,并会返回非零状态,因为该名称已经不存在。unset 会从当前 Shell 中删除该名称。如果某个名称仍在启动文件中定义,未来的 Shell 仍会重新创建它,直到删除对应的配置行。

总结

你观察了三个经常被混淆的机制:Shell 变量属于单个 Shell,export 会将值传递给子进程,而启动文件会在未来的 Shell 中重新创建设置。你还查看了标准环境变量,在不丢失现有条目的情况下扩展了 PATH,验证了命令查找,并使用 unset 删除了临时状态。