Linux环境变量详解

Linux环境变量详解

1. 生效时机

与环境变量有关的四个文件

  • /etc/profile

  • /etc/bashrc

  • ~/.bash_profile

  • ~/.bashrc

本篇文章实验比较多, 步骤有点复杂, 这里先说结论

执行方式profilebashrc示例
非交互式且非登录shell不加载不加载crontab中执行的bash脚本
非交互式登录shell加载加载crontab中通过su -l执行的脚本
交互式非登录shell不加载加载如命令行中通过su 用户名执行的命令
交互式登录shell加载加载普通的终端登录, ssh远程登录, 以及使用su - l 用户名切换身份等

1.1 理论基础

首先认识一下登录shell与非登录shell, 交互式shell与非交互式shell.

1.1.1 登录(login)与非登录(nologin)

普通的终端登录, ssh远程登录, 或者是su -l 用户名切换用户等, 包含的登录行为, 称打开的终端类型为登录式shell, 除了读取profile, 还会读取bashrc;

与之相对的, 在打开的终端中执行bash命令得到新的shell会话, 还有使用su 用户名切换用户的操作创建的终端, 以及执行脚本时创建的bash进程, 都是非登录式shell.

需要注意的是, 从桌面发行版图形界面登录, 或是通过VNC登录行为, 都是非登录形式. 经过实验, 觉得这句话并正确, 点击登录按钮时, 桌面程序会加载/etc/profile文件, 见下面的实验.

1.1.2 交互式与非交互式

交互式模式: 就是shell等待用户输入,并且执行用户键入的命令. 此时shell与用户进行交互, 输入输出与终端相连. 这种模式也是大多数用户非常熟悉的: 终端登录、执行一些命令、退出. 退出后, shell也随之终止.

非交互式模式: shell不与用户进行交互,而是读取存放在文件中的命令, 并且执行. 当它读到文件的结尾,shell也就终止了. 典型的例子就是crontab定时执行脚本, 它们的输入输出不会与任何bash进程相连接.

它们之间可以相互组合, 所以一共有4种情况.

1.2 实验验证

1.2.1 验证登录式与非登录式shell

实验如下操作

[root@localhost ~]# echo 'usual_var=123456' >> /etc/profile
[root@localhost ~]# echo $usual_var

## 切换为普通用户, 非登录shell
[root@localhost ~]# su general
[general@localhost root]$ echo $usual_var

[general@localhost root]$ exit
exit
[root@localhost ~]# echo $usual_var

[root@localhost ~]# su -l general
Last login: Sun Dec 11 20:01:46 PST 2016 on pts/1
[general@localhost ~]$ echo $usual_var
123456
[general@localhost ~]$ exit
logout
[root@localhost ~]# echo $usual_var

可以看出ssh命令的-l选项的作用, 就是开启一个正常的登录shell会话. 同时也验证了, profile文件需要是登录shell才能加载的.

继续进行, 在当前终端执行bash命令, 会打开一个新的shell, 这也是一个非登录式shell.

[root@localhost ~]# echo 'usual_var=123456' >> /etc/profile
[root@localhost ~]# echo $usual_var

[root@localhost ~]# bash
[root@localhost ~]# echo $usual_var

可见, bashsu都没有重新加载profile文件.

usual_var变量写入到bashrc文件, 再执行上述类似操作, 你会发现bash, susu -l三种行为都会加载bashrc, 这里不再列出.

注意bash命令加载的是当前用户的~/.bashrc, 而su操作加载的是目标用户的~/.bashrc.

1.2.2 crontab的非交互式属性

我们都能理解, 通过crontab执行的脚本, 输入输出不与特定终端相连, 所以被称为非交互式shell. 下面我们验证一下, 使用crontab执行定时任务时, 任务脚本对两种文件的加载情况.

/etc/profile中添加环境变量env_var.

$ echo 'export env_var=abcdef' >> /etc/profile

建立实验用的任务脚本nointeract.sh

$ cat /tmp/nointeract.sh
#!/bin/bash
echo $env_var >> /tmp/var_result

建立crontab任务, 每10s执行1次.

$ crontab -e
* * * * * sleep 10; /bin/bash /tmp/nointeract.sh
* * * * * sleep 20; /bin/bash /tmp/nointeract.sh
* * * * * sleep 30; /bin/bash /tmp/nointeract.sh
* * * * * sleep 40; /bin/bash /tmp/nointeract.sh
* * * * * sleep 50; /bin/bash /tmp/nointeract.sh

…等待10s后, 查看/tmp/var_result文件, 是空的, 说明任务脚本没有读取到/etc/profile. 然后把env_var变量写到bashrc文件中, 10s后var_result依然文件的输出依然为空.

说明非交互shell且非登录形式的bash进程不会加载profile也不会加载bashrc.


先将上面写入到bashrcenv_var变量删除, 只保留profile, 然后修改crontab任务列表, 内容如下

* * * * * sleep 10; su -l root -c '/bin/bash /tmp/nointeract.sh'
* * * * * sleep 20; su -l root -c '/bin/bash /tmp/nointeract.sh'
* * * * * sleep 30; su -l root -c '/bin/bash /tmp/nointeract.sh'
* * * * * sleep 40; su -l root -c '/bin/bash /tmp/nointeract.sh'
* * * * * sleep 50; su -l root -c '/bin/bash /tmp/nointeract.sh'

然后, /tmp/var_result就有输出了, 的确是profile文件中定义的env_var的值.

保留crontab, 将env_varprofile文件中移除, 添加到bashrc中, 观察/tmp/var_result的输出, 也能得到env_var的值.

说明非交互式登录shell可以同时加载profilebashrc文件.

1.2.3 图形界面的非登录属性

为了验证这一点, 需要有两台主机, 分别以A和B指代, A是拥有图形界面的桌面发行版.

我们在主机A的/etc/profile文件写入以下内容. (主机A是Fedora 24的桌面发行版)

for i in {0..100};
do
    ping -c 1 主机B的IP地址
    sleep 1
done

重启主机A, 不做任何登录操作, 我们在主机抓取来自主机A的ICMP包tcpdump -i eth0 src 主机A的IP and dst 主机B的IP. 没有任何反应.

然后, 在主机A的图形界面登录, 将会得到100秒的黑屏, 在这期间, 主机B上可以抓取到来自A的ping包. 说明在这段时间主机A在加载/etc/profile的内容, 并且需要等到其执行完成才进行下一步操作.

还没完, 在主机A的图形界面黑屏期间, 我们尝试使用ssh连接到主机A, 刚完成登录, 终端界面就打印出ping操作的结果, 说明这个新创建的bash进程也尝试加载并执行了/etc/profile文件( 不同于图形界面完全阻塞的黑屏状态, 在ssh界面我们可以使用Ctrl+C取消此ping操作的执行), 这很正常.

在ssh登录的shell中执行查询

$ ps aux | grep bash
general    7056  0.0  0.0 119904     8 tty2     S+   22:37   0:00 -/bin/bash -c gnome-session
root      23738  0.0  0.1 120708  4376 pts/0    Ss   22:42   0:00 -bash
root      32895  0.0  0.0 117140  1032 pts/0    R+   22:51   0:00 grep --color=auto bash

也就是说, 图形界面其实也是由bash进程启动的. 但在未登录阶段, /etc/profile并未起作用.

等待图形界面黑屏结束, 在ssh终端中, 再次查找bash进程, 就只剩下自己了. 停留在图形界面时, 没有开启bash进程. 但是当打开"终端"程序时, 就会自动以当前用户建立交互式登录shell.

/etc/profile与"开机启动"这种操作没有任何关系, 不要把开机启动项写在这里, 没用的.

2. 加载顺序

非交互式非登录shell比较好说, 哪一个都不加载…

交互式非登录shell也比较好说, 也就是/etc/bashrc -> ~/.bashrc, 后者覆盖前者.

两种登录式shell两种文件都会加载, 一般是

/etc/bashrc -> ~/.bashrc,

/etc/profile -> ~/.bash_profile.

不过/etc/bashrc -> ~/.bash_profile, 总是用户级配置优先级更高. 所以最终加载顺序可以看成是

/etc/profile -> /etc/bashrc -> ~/.bash_profile -> ~/.bashrc.

Linux环境变量解析2-变量作用域及生命周期

  1. abc=123

  2. abc=123 && set -a abc

  3. export abc=123

  4. declare abc=123

  5. declare -x abc=123

  6. env abc=123

其中, 1和4的效果相同, 2,3和5的效果相同.

1,4只是在当前bash进程中设置变量, 无法影响到其他bash进程, 无论是子bash进程, 父bash进程还是同级的新创建的bash进程. 这里称这种变量为普通变量, 也被称为bash变量.

通过2,3,5方式创建的变量, 在同一用户, 并且属于当前bash进程的子级进程中有效. 即通过当前bash shell以这几种方法创建的变量, 可以传递给所有在当前bash, 以同一用户身份启动的进程中. 但对于父级bash进程和同级bash进程无效. 这种变量称为用户变量.

变量作用域分析

bash变量

示例1

当前bash中定义的普通变量, 无法传递到子bash进程.

$ cat test.sh
#!/bin/bash
echo 'begin'
echo $abc
echo 'end'
$ abc=123
$ echo $abc
123
$ ./test.sh
begin

end
示例2

当前bash中定义的普通变量, 也无法影响到父级bash进程.

$ cat test.sh 
#!/bin/bash
def=456
echo $def
$ ./test.sh 
456
$ echo $def

示例3

但是这种普通变量却可以传递到subshell进程中, 不过subshell中定义的普通变量影响到父shell.

$ abc=123
$ (echo $abc)
123
$ (abc=789; echo $abc)
789
$ echo $abc
123

这里需要注意的是, bash进程与shell进程不是一回事. 两者的区别可以通过$$特殊变量查看, 这表示当前bash进程的进程号, 或者也可以通过$BASH_SUBSHELL变量查看, 它表示子shell的层级数.

$ echo $$; echo $BASH_SUBSHELL
3299
0
$ (echo $$; echo $BASH_SUBSHELL)
3299
1

可以看到, 这种通过小括号创建的subshell, 与原shell同用同一个bash进程, 只是shell层级会增加. subshell是允许嵌套调用的,可以在函数或圆括号结构内再次调用圆括号结构创建subshell.

而执行脚本时, 由于第一行#!/bin/bash这一句的存在, shell脚本都是新建bash进程然后再执行其中的命令.

$ cat test.sh
#!/bin/bash
echo $$; echo $BASH_SUBSHELL
$ ./test.sh
5990
0

这两者的区别应该用更深层的涵义, 目前先保留这个问题<???>.

用户变量分析

用户级变量与bash级变量最大的不同, 应该就是前者可以延父子关系上下传递而后者不可以.

通过2,3,5中任意一种方式可以创建用户级变量.

$ cat test.sh 
#!/bin/bash
echo 'begin'
echo $abc
echo 'end'
$ export abc=789
$ ./test.sh 
begin
789
end

不同级别变量操作

1. set命令

set命令作用主要是显示系统中已经存在的shell变量, 或者用于更改shell特性,符号"+“和”-"的作用分别是打开和关闭指定的模式. set命令本身不能够定义新的shell变量.

在不添加任何选项的时候, 显示当前会话中所有合法的变量,包括用户级变量.

2. env命令

不接任何参数的情况下, 显示当前的用户级别变量.

env abc=123可以将abc设置为用户级别变量, 当前bash进程及其子进程中有效.

env -u abc可以从当前用户级环境变量中删除变量abc.

3. export命令

在不接任何参数时, 显示当前导出成用户变量的bash变量, 这些变量原本是bash级别变量, 之后被提升至用户级别.

它可以接一个参数, 类似于export abc=123或是abc=123 && export abc, 将普通变量abc提升到用户变量级别, 之后通过此shell启动的命令, 都可以读取到这个变量, 它的生命周期仅限于当前shell结束之前.

不过, export的输出都很相似, 如下

$ export
declare -x CLASSPATH=".:/usr/local/jdk1.8.0_101/lib/dt.jar:/usr/local/jdk1.8.0_101/lib/tools.jar"
declare -x HISTCONTROL="ignoredups"
declare -x HISTSIZE="1000"
...

所有被提升至用户级别的变量, 实际上都是通过declare命令为bash级变量赋予了-x属性, 暂时不知道这两个命令之间有何内在的关联<???>.

4. declare命令

在不接任何参数时, 可以显示当前会话中所有合法变量…貌似与set的输出相同, 不仅包括普通变量, 还有用户级别变量.

declare abc=123可以将变量abc声明为一个普通变量. 这句话被注释掉了, 但也不能完全说是错误的, 在CentOS7系统中declare abc=123默认为附带-x属性, 即将其声明为用户级别变量, 所以如果不带任何属性, abc也本应该只是一个普通变量而已.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

墨烦信息

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值