Bash 中的 _ 是不是环境变量
首先,我们想到的会是 export(等价于 declare -x)命令:
|
$ export | grep 'declare -x _=' |
没有找到,那么结论就是 _ 不是环境变量?当然没那么简单,否则本篇文章就该结束了。别忘了还有 env(或者 printenv)命令:
|
$ env | grep '_=' _=/usr/bin/env |
这下怎么办,_ 到底是不是环境变量?谁说的对?然而下面还有更诡异的:
|
$ bash -c "export | grep 'declare -x _='" declare -x _="/bin/bash" $ bash -c ":;export | grep 'declare -x _='" |
当用 bash -c 的方式执行且 export 是第一条命令的时候,_ 被认为是环境变量,一旦执行过别的命令,_ 就变成了非环境变量。
为了找到答案,我只好去翻阅 Bash 源码,下面直接说出我从源码中得出的三点结论:
1. Bash 启动的时候
当 Bash 启动时,如果 Bash 的父进程给 Bash 传入的环境变量数组里有 _,那么 Bash 一定会继承这个环境变量。如果父进程没有传入 _ 环境变量,那么 Bash 会自己创建 _ 这个变量,并把它的初始值赋值为父进程传入的第 0 个参数(argv[0],通常是Bash 的文件名或完整路径),但不会把 _ 设置为环境变量,仅仅是一个普通变量,相关代码在 variables.c 里的 initialize_shell_variables 函数里:
/* Set up initial value of $_ */
temp_var = set_if_not ("_", dollar_vars[0]) # 如果用 Bash 代码翻译这句 c 代码就是:[[ -z $_ ]] && _=$0
2. 在执行任意简单命令之后
我们常见的 $_ 的用法就是用它来获取上一条执行过的命令的最后一个参数,所以显然在执行完任意一条命令之后,Bash 都得为这个变量重新赋值,不过除了赋值之外,Bash 还做了一件事,就是把 _ 变量标记为非环境变量。在 execute_cmd.c 里有个 bind_lastarg 函数就是来干这两件事的:
static void
bind_lastarg (arg)
char *arg;
{
SHELL_VAR *var;if (arg == 0)
arg = "";
var = bind_variable ("_", arg, 0); # 这句代码是把 _ 的值设置成上次命令的最后一个参数
VUNSETATTR (var, att_exported); # 这句是把 _ 标记为非环境变量
}
3. 执行外部命令之前
在执行外部命令之前,Bash 会专门把 _ 的值设置成这个外部命令的路径,同时把 _ 标记为环境变量。相关代码在 execute_cmd.c 里的 execute_disk_command 函数中:
put_command_name_into_env (command);
这个 put_command_name_into_env 函数的实现在 variables.c 里:
void
put_command_name_into_env (command_name)
char *command_name;
{
update_export_env_inplace ("_=", 2, command_name); # 这句代码翻译成 Bash 代码就是:export _=$command,command 就是外部命令的路径
}
三点结论说完了,然后再回头看看刚才那些看似诡异的代码示例。
为什么 export 看不到 _ 而 env 能看到?
因为我们执行测试代码通常是在交互式的 Shell 下进行的,交互 Shell 会执行 .bashrc 启动脚本,所以当我们执行 export 命令的时候,一定已经执行过别的命令了,在执行任意命令之后,Bash 都会把 _ 标记为非环境变量,所以 export 看不到 _,我们可以开启一个不执行 rc 脚本的交互 Shell 再看看:
|
$ bash --norc $ export | grep 'declare -x _=' # bash 也是个外部命令,所以按照上面第 3 点结论里说的,现在这个 Shell 的父 Shell 会把 _ 标记为环境变量, # 同时把它的值设置为 bash 的路径,现在这个 Shell 继承了 _ 这个环境变量,所以这里能看到 _ declare -x _="/bin/bash" $ export | grep 'declare -x _=' # 当执行完前一条命令后,按照上面第 2 点结论里说的,当前 Shell 会把 _ 标记为非环境变量,所以这里 _ 已经不是环境变量了 |
env 一直能看到,是因为 env 是外部命令,在执行它之前 Bash 总会把 _ 标记为环境变量,同时把 _ 的值设置为 /usr/bin/env。
为什么连 export _; export 也看不到 _
虽然 export _ 会把 _ 标记为环境变量,但因为 export _ 也是一条命令,按照上面第二点结论说的,当 export _ 执行后,Bash 又把 _ 标记成了非环境变量。
在执行 _=foo env 这条命令时,env 命令里看到的 _ 环境变量的值是什么
是 /usr/bin/env,在执行这条命令时,_ 的值先被赋值成了 foo,然后又被改写成了 /usr/bin/env。
再通过代码演示证明一下第 1 点结论
证明一下 Bash 会继承父进程的 _ 环境变量,以及在父进程没有 _ 环境变量的时候 Bash 会在启动时建立这个 _ 变量。
|
$ env bash -c 'echo $_' # env 继承了当前 Shell 专门为它设置的 _ 环境变量,然后又传给了它的子进程 Shell /usr/bin/env $ env _=1 bash -c 'echo $_' # env 重新赋值了 _,然后又传给了 bash 1 $ env -i bash -c 'echo $_' # env 在调用 Bash 时没有传入任何环境变量,但 Bash 自己初始化了 _ 变量 bash $ env -i bash -c 'export' # 但 Bash 初始化的 _ 变量不是环境变量,Bash 在启动时只强制创建三个环境变量 declare -x OLDPWD |
manual 里少说了什么
看一下 manual 里讲 $_ 的段落省略了哪些信息:
At shell startup, set to the absolute pathname used to invoke the shell or shell script being executed as passed in the environment or argument list
这里说的只是如果父进程没有传给 Bash _ 环境变量时的表现,如果传了 _,Bash 不会做这件事。
Subsequently, expands to the last argument to the previous command, after expansion.
没有说同时会把 _ 标记为非环境变量。
Also set to the full pathname used to invoke each command executed and placed in the environment exported to that command.
没明确指出这里的 command 只指外部命令,虽然 pathname 这个单词已经隐含了它是个外部命令了。
总结
总结一下,这个问题的答案就是:_ 在 Bash 刚刚启动的时候(执行第一条命令之前)可能是环境变量(来自父进程)或者在执行外部命令之前的那一刻是环境变量,在其他时候都是非环境变量。
为什么
难道你没有疑问吗,为什么要特意的在执行完一条简单命令后,就把 _ 变成非环境变量,从 Bash 启动时就让它一直保持环境变量不就得了,干嘛切来切去。为此我特地问了 Bash 现任作者,他的猜测(当时不是他维护 Bash)和我预想的一样:那就是因为没什么用,你想想看,如果你执行的是个内部命令,内部命令本来就运行在当前 Shell 里,即便不是环境变量,它也能访问到,如果你执行的是个外部命令,_ 本来就会被改写成环境变量(同时改值),所以当时写 Bash 的人就写了那么一句,没有什么特别的考虑。
Bash 中的 _ 是不是环境变量的更多相关文章
- 【转】Mac OS X 中 Zsh 下 PATH 环境变量的正确设置
在 Mac OS X 中使用 zsh,环境变量 PATH 一不小心就会变得很紊乱,表现为自己设置的路径总是被放到系统路径之后,部分路径还会有重复.这是我们不太了解 zsh 启动时加载文件的顺序和 Ma ...
- awk中使用shell的环境变量
awk中使用shell的环境变量一:"'$var'"这种写法大家无需改变用'括起awk程序的习惯,是老外常用的写法.如:var="test"awk 'BEGIN ...
- Linux中配置JDK的环境变量
一. 解压安装jdk 在shell终端下进入jdk-6u14-linux-i586.bin文件所在目录, 执行命令 ./jdk-6u14-linux-i586.bin 这时会出现一段协议,连继敲回车, ...
- Java中jdk安装与环境变量配置
Java中jdk安装与环境变量配置 提示:下面是jdk1.7和jdk1.8的百度网盘链接 链接:https://pan.baidu.com/s/1SuHf4KlwpiG1zrf1LLAERQ 提取码: ...
- Bash 中为 _ 变量赋空值的三个场景
$_ 有好几个功能,我们最常用的是用它来获取“刚刚执行过的命令的最后一个参数”这个功能,比如下面这样: $ ls ~/Downloads/very/long/dir/ # ls 到某个目录看看有没有 ...
- linux centos中添加删除修改环境变量,设置java环境变量
前言 安装完软件必要添加环境变量.指令很少,然而长时间不写就会不自信:我写的对吗?于是百度开始,于是发现又是各有千秋.好吧,好记星不如烂笔头.当然,最重要的是,百度出来的都他妈的是如何添加环境变量,只 ...
- linux系统中给mysql配置环境变量
安装过程就不写了,记得安装的路径就行,接下来要用到. 修改配置文件 vim /etc/profile 设置环境变量 写一个MYSQL_HOME,值为“mysql的安装路径” 在PATH后面加上$MYS ...
- VS中使用系统的环境变量作为INCLUDE和LIBPATH的值
所谓INCLUDE的值实际上就是头文件的搜索路径,而LIBPATH就是.lib的搜索路径,对应着命令行中的/I和/LIBPATH选项 假设你有一个 D:/demo/abc/include/abc.h, ...
- Linux中crontab的坑爹环境变量问题
手动在CentOS中执行sh脚本,调用java程序,一切正常: 将该sh加入crontab中定时调度之后,挂了,完全没有执行到的感觉啊!!! 查看crontab执行日志: cat /var/log/c ...
随机推荐
- CST时间转换成 yyyy-MM-dd格式
将 "Tue Oct 28 12:12:10 CST 2010" 时间格式转成 "2010-10-28 12:12:10" 格式: + (NSString *) ...
- ELF Format 笔记(十五)—— 符号哈希表
ilocker:关注 Android 安全(新手) QQ: 2597294287 符号哈希表用于支援符号表的访问,能够提高符号搜索速度. 下表用于解释该哈希表的组织,但该格式并不属于 ELF 规范. ...
- 【来至百度百科】linux文件结构
文件结构 /:根目录,所有的目录.文件.设备都在/之下,/就是Linux文件系统的组织者,也是最上级的领导者. /bin:bin 就是二进制(binary)英文缩写.在一般的系统当中,都可以在这个目录 ...
- 控制反转IoC简介
控制反转IoC简介 在实际的应用开发中,我们需要尽量避免和降低对象间的依赖关系,即降低耦合度.通常的业务对象之间都是互相依赖的,业务对象与业务对象.业务对象与持久层.业务对象与各种资源之间都存在这样或 ...
- 【Python数据分析】Python模拟登录(一) requests.Session应用
最近由于某些原因,需要用到Python模拟登录网站,但是以前对这块并不了解,而且目标网站的登录方法较为复杂, 所以一下卡在这里了,于是我决定从简单的模拟开始,逐渐深入地研究下这块. 注:本文仅为交流学 ...
- 小讲堂:Mobox文档管理软件中的文件外链是什么?
今天我们来讨论Mobox文档管理软件中的文件外链是什么?熟悉MOBOX的朋友们应该知道,如果有文件需要分享给其他同事,直接可以进行文件共享.对方会在AM的即时通讯客户端有消息提醒,点击消息提醒可以看到 ...
- NOIP2012国王游戏
题目描述 恰逢 H 国国庆,国王邀请 n 位大臣来玩一个有奖游戏.首先,他让每个大臣在左.右 手上面分别写下一个整数,国王自己也在左.右手上各写一个整数.然后,让这 n 位大臣排 成一排,国王站在 ...
- jdbc java数据库连接 5)CallableStatement 接口
CallableStatement执行存储过程(也是预编译语言) 首先在sql中执行以下带有输入参数的代码: DELIMITER $ CREATE PROCEDURE pro_findById(IN ...
- 你一无所知的CSS
也许标题有点夸大了.虽然不能完全保证大家都不知道这些,但是这也算是一个好机会检测下你是否知道或使用过下面的内容. Selectors Root :root { } 使用root可以让你在DOM中选择 ...
- c3p0数据源的使用初步及Mysql8小时问题解决
原文:http://blog.csdn.net/xby1993/article/details/23707775 c3p0号称是java界最好的数据池. c3p0的配置方式分为三种,分别是 1.set ...