MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Bash中的环境变量扩展与操作

2024-11-304.4k 阅读

环境变量概述

在Bash中,环境变量是一种具有特定名称的动态值,这些值可以被操作系统和运行在其上的进程所访问。它们在系统配置、脚本执行以及用户会话管理等方面起着关键作用。从本质上讲,环境变量是一种在进程间传递信息的机制。当一个进程启动时,它会继承其父进程的环境变量。例如,当用户登录到系统并启动一个Bash shell时,这个Bash shell就会继承登录进程(如loginsshd)传递过来的环境变量。

环境变量可以分为系统级环境变量和用户级环境变量。系统级环境变量通常由系统管理员设置,对所有用户都有效,例如PATH变量,它定义了系统在搜索可执行文件时要遍历的目录列表。用户级环境变量则由单个用户设置,仅在该用户的会话或进程中生效。

环境变量的查看与定义

  1. 查看环境变量:在Bash中,可以使用printenv命令来查看当前所有的环境变量及其值。例如,在终端中输入printenv,会输出类似如下的结果:
XDG_SESSION_ID=2
TERM=xterm-256color
SHELL=/bin/bash
SSH_CLIENT=192.168.1.100 51452 22
SSH_TTY=/dev/pts/0
USER=username
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:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:
MAIL=/var/mail/username
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
PWD=/home/username
LANG=en_US.UTF-8
SHLVL=1
HOME=/home/username
LOGNAME=username
XDG_DATA_DIRS=/usr/local/share:/usr/share:/var/lib/snapd/desktop
XDG_CONFIG_DIRS=/etc/xdg/xdg-ubuntu:/etc/xdg
_=/usr/bin/printenv

如果只想查看某个特定的环境变量,可以使用printenv 变量名的形式,例如printenv PATH,会输出PATH变量的值。

另外,也可以使用echo $变量名来查看某个环境变量的值,例如echo $PATH。但这种方式只适用于已经在当前环境中定义的变量,而printenv可以列出所有环境变量,包括那些未在当前shell中显式定义但从父进程继承来的变量。

  1. 定义环境变量:在Bash中,可以使用变量名=值的形式来定义一个普通变量。例如,定义一个名为MY_VARIABLE的变量并赋值为hello world,可以这样写:
MY_VARIABLE="hello world"

注意,在定义变量时,变量名和等号之间不能有空格,否则会被Bash解释为命令。如果要定义一个环境变量(使其能被子进程继承),可以使用export 变量名的形式。例如,将刚才定义的MY_VARIABLE变量导出为环境变量:

export MY_VARIABLE

也可以在定义变量的同时将其导出,例如:

export NEW_VARIABLE="new value"

环境变量扩展

  1. 基本变量扩展:Bash中的变量扩展是指在命令或脚本中使用变量的值。变量扩展使用$变量名${变量名}的形式。例如,定义一个变量NAME并在echo命令中使用它:
NAME="John"
echo "Hello, $NAME"

上述代码会输出Hello, John。使用${变量名}的形式在某些情况下是必要的,比如当变量名与其他字符连在一起时。例如:

NAME="John"
echo "Hello, ${NAME}Doe"

如果写成echo "Hello, $NAMEDoe",Bash会尝试查找名为NAMEDoe的变量,而不是NAME变量,从而导致错误。

  1. 参数替换:Bash提供了多种参数替换的方式,用于在变量扩展时对变量的值进行处理。
    • ${变量名:-默认值}:如果变量变量名存在且非空,则返回变量的值;否则返回默认值。例如:
unset MY_VARIABLE
echo ${MY_VARIABLE:-default value}

上述代码会输出default value,因为MY_VARIABLE未定义。如果先定义MY_VARIABLE="actual value",再执行echo ${MY_VARIABLE:-default value},则会输出actual value。 - ${变量名:=默认值}:如果变量变量名存在且非空,则返回变量的值;否则将变量设置为默认值,并返回默认值。例如:

unset MY_VARIABLE
echo ${MY_VARIABLE:="new default"}
echo $MY_VARIABLE

上述代码中,第一次echo会输出new default,第二次echo也会输出new default,因为MY_VARIABLE在第一次echo时被设置为了new default。 - ${变量名:?错误信息}:如果变量变量名存在且非空,则返回变量的值;否则输出错误信息并终止脚本执行(如果在脚本中)。例如:

unset MY_VARIABLE
echo ${MY_VARIABLE:?"MY_VARIABLE is not set"}

上述代码会输出bash: MY_VARIABLE: MY_VARIABLE is not set,因为MY_VARIABLE未定义。 - ${变量名:+附加值}:如果变量变量名存在且非空,则返回附加值;否则返回空字符串。例如:

MY_VARIABLE="value"
echo ${MY_VARIABLE:+"has value"}
unset MY_VARIABLE
echo ${MY_VARIABLE:+"has value"}

第一次echo会输出has value,第二次echo不会输出任何内容。

  1. 字符串操作:在变量扩展中,还可以对字符串进行一些操作。
    • 删除前缀
      • ${变量名#子字符串}:从变量变量名的值的开头删除最短匹配的子字符串。例如:
URL="https://example.com"
echo ${URL#https://}

上述代码会输出example.com,因为它从URL变量的值开头删除了最短匹配的https://。 - ${变量名##子字符串}:从变量变量名的值的开头删除最长匹配的子字符串。例如:

URL="https://subdomain.example.com"
echo ${URL##https://}

上述代码会输出subdomain.example.com,因为它从URL变量的值开头删除了最长匹配的https://。 - 删除后缀: - ${变量名%子字符串}:从变量变量名的值的结尾删除最短匹配的子字符串。例如:

FILE="document.txt"
echo ${FILE%.txt}

上述代码会输出document,因为它从FILE变量的值结尾删除了最短匹配的.txt。 - ${变量名%%子字符串}:从变量变量名的值的结尾删除最长匹配的子字符串。例如:

FILE="document.tar.gz"
echo ${FILE%%.*}

上述代码会输出document,因为它从FILE变量的值结尾删除了最长匹配的.tar.gz。 - 替换: - ${变量名/旧字符串/新字符串}:将变量变量名的值中第一个匹配的旧字符串替换为新字符串。例如:

TEXT="hello world, hello bash"
echo ${TEXT/hello/goodbye}

上述代码会输出goodbye world, hello bash,因为它只替换了第一个hello。 - ${变量名//旧字符串/新字符串}:将变量变量名的值中所有匹配的旧字符串替换为新字符串。例如:

TEXT="hello world, hello bash"
echo ${TEXT//hello/goodbye}

上述代码会输出goodbye world, goodbye bash,因为它替换了所有的hello

环境变量与脚本

  1. 脚本中使用环境变量:在Bash脚本中,可以使用在脚本外部定义的环境变量。例如,有一个名为test.sh的脚本:
#!/bin/bash
echo "The PATH is: $PATH"

如果在脚本所在目录下有可执行文件,并且PATH变量包含该目录,那么脚本可以正常执行并输出PATH变量的值。在脚本中也可以定义和修改环境变量。例如:

#!/bin/bash
export NEW_ENV_VAR="from script"
echo "New environment variable: $NEW_ENV_VAR"

运行这个脚本后,NEW_ENV_VAR变量就被定义为环境变量,并且在脚本中可以使用它。不过需要注意的是,在脚本中定义的环境变量默认只在脚本的进程及其子进程中有效,脚本执行完毕后,不会影响到脚本外部的环境。

  1. 传递环境变量给子脚本:如果一个脚本需要调用另一个脚本,并将某些环境变量传递给子脚本,可以在调用子脚本时使用export命令。例如,有一个父脚本parent.sh
#!/bin/bash
export SHARED_VARIABLE="shared value"
./child.sh

和一个子脚本child.sh

#!/bin/bash
echo "Shared variable in child: $SHARED_VARIABLE"

运行parent.shchild.sh就可以访问到SHARED_VARIABLE环境变量并输出其值。

环境变量的作用域

  1. 全局环境变量:系统级的环境变量,如PATHLANG等,是全局的,对系统中的所有用户和进程都有效。这些变量通常在系统启动时由初始化脚本(如/etc/profile/etc/bashrc等)设置。全局环境变量在系统配置和进程运行中起着关键作用,例如PATH变量决定了系统在查找可执行文件时的搜索路径。如果没有正确设置PATH变量,一些系统命令可能无法找到并执行。

  2. 局部环境变量:在Bash shell中,通过export命令定义的环境变量默认是局部的,即只在当前shell进程及其子进程中有效。例如,在一个终端会话中定义了一个环境变量MY_LOCAL_VAR

export MY_LOCAL_VAR="local value"

这个变量在当前终端会话中以及由该终端会话启动的所有子进程(如运行的脚本)中都可以访问。但如果打开另一个新的终端会话,新会话中是不会有MY_LOCAL_VAR这个变量的。

  1. 函数内的环境变量:在Bash函数内部定义的环境变量默认也是局部的,只在函数内部及其子进程中有效。例如:
my_function() {
    export FUNCTION_VAR="function value"
    echo "Inside function: $FUNCTION_VAR"
}
my_function
echo "Outside function: $FUNCTION_VAR"

上述代码中,在函数内部可以输出FUNCTION_VAR的值,但在函数外部输出时,FUNCTION_VAR未定义,不会输出任何内容。如果要在函数外部也能访问函数内定义的环境变量,可以在函数定义前使用export -f命令将函数导出,这样函数内定义的环境变量就会被传播到函数调用的环境中。例如:

export -f my_function
my_function() {
    export FUNCTION_VAR="function value"
    echo "Inside function: $FUNCTION_VAR"
}
my_function
echo "Outside function: $FUNCTION_VAR"

此时,在函数外部也能输出FUNCTION_VAR的值。

环境变量的持久化

  1. 用户级环境变量的持久化:对于用户级环境变量,要使其在每次登录时都生效,可以将变量定义添加到用户的配置文件中。在Bash中,常用的用户配置文件有~/.bashrc~/.bash_profile
    • ~/.bashrc:这个文件在每次启动交互式非登录shell时都会被执行。例如,当用户打开一个新的终端会话时,就会执行~/.bashrc。如果要定义一个持久化的用户级环境变量,比如定义一个MY_USER_VAR变量,可以在~/.bashrc文件中添加如下内容:
export MY_USER_VAR="user value"

保存文件后,在当前终端会话中可以通过source ~/.bashrc命令使配置立即生效,之后每次打开新的终端会话,MY_USER_VAR变量都会被定义。 - ~/.bash_profile:这个文件在每次用户登录时(即启动交互式登录shell时)会被执行。它主要用于设置用户特定的环境变量和启动程序。如果在~/.bash_profile中定义环境变量,同样可以实现持久化。例如:

export ANOTHER_USER_VAR="another user value"

在用户下次登录时,ANOTHER_USER_VAR变量就会被定义。需要注意的是,在大多数系统中,~/.bash_profile会在执行时调用~/.bashrc,所以一些通用的环境变量设置可以放在~/.bashrc中,而特定于登录时的设置可以放在~/.bash_profile中。

  1. 系统级环境变量的持久化:系统级环境变量的持久化通常需要修改系统的配置文件。在基于Linux的系统中,常用的系统配置文件有/etc/profile/etc/bashrc
    • /etc/profile:这个文件在系统启动时以及用户登录时会被执行,它用于设置系统范围内的环境变量和启动程序。如果要添加一个系统级环境变量,比如SYSTEM_VAR,可以以管理员身份编辑/etc/profile文件,添加如下内容:
export SYSTEM_VAR="system value"

保存文件后,需要重新启动系统或让用户重新登录,新的环境变量才会生效。 - /etc/bashrc:这个文件在每次启动交互式非登录shell时会被执行,对所有用户都有效。如果在/etc/bashrc中定义环境变量,也可以实现系统级的持久化。例如:

export COMMON_VAR="common value for all users"

这样,每次用户打开新的终端会话(非登录shell)时,COMMON_VAR变量都会被定义。但需要谨慎修改/etc/bashrc,因为它会影响到所有用户的非登录shell环境。

环境变量与安全

  1. 环境变量的潜在风险:环境变量在使用不当的情况下可能会带来安全风险。例如,恶意用户可能会修改PATH变量,将恶意程序所在目录置于系统命令目录之前,从而在用户执行系统命令时执行恶意程序。假设系统中有一个名为ls的命令,正常情况下ls命令位于/bin/ls。如果恶意用户将PATH变量修改为/malicious/directory:/bin:/usr/bin,并且在/malicious/directory目录下有一个伪装成ls的恶意程序,当用户执行ls命令时,就会执行恶意程序而不是真正的ls命令。

另外,一些脚本可能依赖特定的环境变量来执行操作。如果环境变量被恶意篡改,脚本可能会执行错误的操作,例如泄露敏感信息或执行未授权的命令。例如,一个备份脚本可能依赖BACKUP_DIR环境变量来指定备份目录,如果这个变量被修改为一个临时目录或其他非预期的目录,备份数据可能会被错误放置,甚至导致数据丢失或泄露。

  1. 安全措施:为了避免环境变量带来的安全风险,可以采取以下措施:
    • 谨慎设置环境变量:在设置环境变量时,尤其是在脚本中,要确保变量的值是可信的。避免使用用户输入直接作为环境变量的值,除非对输入进行了严格的验证和过滤。例如,如果脚本接受用户输入来设置一个环境变量,应该检查输入是否符合预期的格式和范围。
    • 保护系统配置文件:系统级和用户级的环境变量配置文件(如/etc/profile~/.bashrc等)应该设置合适的权限,防止未经授权的修改。通常,系统配置文件应该只有管理员(root用户)有写权限,用户配置文件应该只有用户自己有写权限。例如,/etc/profile的权限应该设置为-rw-r--r--~/.bashrc的权限应该设置为-rw-------
    • 使用固定路径:在脚本中执行命令时,尽量使用命令的完整路径,而不是依赖PATH变量。例如,使用/bin/ls而不是直接使用ls,这样可以避免恶意用户通过修改PATH变量来执行恶意程序。
    • 清除不必要的环境变量:在脚本执行完毕或用户会话结束时,清除不必要的环境变量,尤其是那些可能包含敏感信息的变量。例如,如果在脚本中设置了一个用于连接数据库的环境变量DB_PASSWORD,在脚本执行完毕后,可以使用unset DB_PASSWORD命令将其删除,防止其他进程获取到该敏感信息。

高级环境变量操作

  1. 动态环境变量生成:在一些复杂的场景中,可能需要根据运行时的条件动态生成环境变量。例如,根据系统的不同架构生成不同的编译相关环境变量。可以使用条件判断语句结合变量定义来实现。以下是一个简单的示例:
if [ "$(uname -m)" = "x86_64" ]; then
    export ARCH_FLAGS="-m64"
else
    export ARCH_FLAGS="-m32"
fi
echo "Set ARCH_FLAGS: $ARCH_FLAGS"

上述代码通过uname -m命令获取系统架构信息,如果是64位架构(x86_64),则设置ARCH_FLAGS-m64,否则设置为-m32

  1. 环境变量数组:Bash支持数组变量,同样也可以创建环境变量数组。例如:
export MY_ARRAY=(value1 value2 value3)

要访问数组中的元素,可以使用${MY_ARRAY[索引]}的形式,索引从0开始。例如:

echo ${MY_ARRAY[0]}

会输出value1。在脚本中,可以通过循环遍历数组环境变量。例如:

for element in ${MY_ARRAY[@]}; do
    echo $element
done

上述代码会依次输出数组MY_ARRAY中的每个元素。

  1. 环境变量的继承与传递控制:虽然子进程默认会继承父进程的环境变量,但有时可能需要更精细地控制环境变量的继承与传递。例如,在启动一个新的进程时,可以使用env命令来设置特定的环境变量并启动进程,而不继承当前环境的所有变量。例如:
env NEW_VAR="new value" /path/to/executable

上述命令会以NEW_VAR环境变量设置为new value的环境启动/path/to/executable程序,该程序不会继承当前环境中的其他不必要的环境变量。另外,在脚本中,可以通过set -a命令来自动导出所有新定义的变量,通过set +a命令停止自动导出。例如:

set -a
NEW_VARIABLE="auto exported"
set +a

set -aset +a之间定义的NEW_VARIABLE变量会自动被导出为环境变量。

与其他编程语言的交互中的环境变量

  1. 从Bash传递环境变量到其他语言:在Bash脚本中启动其他编程语言的脚本或程序时,可以将Bash中的环境变量传递给它们。例如,在Python中可以通过os.environ来获取环境变量。假设有一个Bash脚本launch_python.sh
export PYTHON_VARIABLE="from bash"
python3 my_python_script.py

my_python_script.py中可以这样获取环境变量:

import os
print(os.environ.get('PYTHON_VARIABLE'))

运行launch_python.sh脚本,Python脚本就能获取到从Bash传递过来的PYTHON_VARIABLE环境变量并输出其值。同样,在其他编程语言如Ruby、Perl等中也有类似的方式来获取环境变量。在Ruby中可以使用ENV['变量名']来获取环境变量,在Perl中可以使用$ENV{变量名}来获取。

  1. 从其他语言设置环境变量供Bash使用:从其他编程语言设置环境变量供Bash使用相对复杂一些,因为不同语言和操作系统的实现方式有所不同。在一些情况下,可以通过将环境变量设置写入文件,然后在Bash中读取文件来设置环境变量。例如,在Python中可以这样写:
with open('env_vars.sh', 'w') as f:
    f.write('export OTHER_VARIABLE="from python"\n')

然后在Bash脚本中可以通过source命令来读取这个文件并设置环境变量:

python3 set_env.py
source env_vars.sh
echo $OTHER_VARIABLE

这样就能在Bash中获取到从Python设置的OTHER_VARIABLE环境变量的值。不过这种方法相对繁琐,并且在不同操作系统上可能需要调整文件路径和权限等设置。另外,一些编程语言提供了与操作系统交互的库,可以更直接地设置环境变量,但这些方法通常是操作系统特定的。例如,在Python中,在Windows系统上可以使用os.environ['变量名'] = '值'来设置环境变量,但这种设置只在当前Python进程及其子进程中有效,不会影响到外部的Bash环境。在Linux系统上,可以通过subprocess模块调用export命令来设置环境变量,但同样存在只在子进程有效等问题。

通过深入理解和掌握Bash中的环境变量扩展与操作,可以更好地编写高效、灵活且安全的脚本和程序,同时在不同编程语言和系统组件之间实现更有效的数据传递和交互。在实际应用中,要根据具体的需求和场景,合理运用环境变量的各种特性,确保系统的稳定性和安全性。