「这是我参与11月更文挑战的第6天,活动详情查看:2021最后一次更文挑战」。
我们经常会发现输入过的shell指令从history里面找不到,你知道为什么吗?
我们也经常觉得设置的环境变量没有生效,一顿操作后又好了,你仔细想过其中的原理吗?
本文将深入分析其中的原理。
这是讲shell系列的第二篇,第一篇为SSH、SHELL和终端(一),有兴趣可以看看。
bash程序参数和返回值
程序就是一个黑盒,有输入输出,shell程序有些自定义变量
$$
当前进程ID$0
当前进程启动命令$1
当前输入参数
# ps $$
PID TTY STAT TIME COMMAND
8898 pts/1 Ss 0:00 -bash
# echo $$
8898
复制代码
- exit命令: status 返回0表示成功, 1表示失败
- 进程有配置文件,一般初始化的时候都会先读取配置文件
- bash有配置文件
/root/.bash_profile
等 - history也有配置文件
- 其他各种程序都有配置文件
环境变量和SHELL
系统和程序都用
环境变量是给操作系统或者应用程序使用的变量信息。
# echo $HISTFILE
/root/.bash_history
复制代码
- 可以设置整体的环境变量
- 进程也可以单独设置自己独有的环境变量
环境变量的隔离
- 使用shell内置的export设置的环境变量,当前bash可生效;export命令可新增,修改或删除环境变量
- export设置的环境变量在bash具有隔离性,A 只在bash1生效,B只在bash2生效。
这就是为什么我们经常觉得设置的环境变量没有生效的原因。
环境变量的共享
需要把export写到bash配置文件才能全局生效:

- 其他同时运行的bash执行source才能看到其他bash设置的环境变量。
- 新开的bash启动的时候会先将配置文件里面的命令执行一遍。
如下图:
- alias设置别名也是这样的生效原理,这是因为也是bash的默认配置文件。
#echo "alias ll=\"ls -l\"" >>.bash_profile
#. .bash_profile
复制代码
- source和.是一样的,是重新从配置文件读取并执行配置文件中的命令。
- 这里提到的命令都是shell的内置命令,他们都是在shell进程里面运行的命令(不会启动一个新的进程)。
环境变量可继承
-
因为子进程继承父进程的数据空间,所以子进程在启动时可以继承父进程的环境变量
-
在bash的终端里,设置的环境变量终端执行的程序都可以看到,因为都是bash的子进程。
如下启动一个新的bash,还是可以读到$AAA。
# export AAA=a
# echo $AAA
a
# bash
# echo $AAA
a
复制代码
- 在守护进程里,同理。
SHELL历史HISTORY
我们经常会发现输入过的shell指令从history里面找不到,你知道为什么吗?
这里讲先述history(history [n])命令。
基本用法和相关环境变量
- n 的参数仅列出最后 n 行。
- 如果设置了 shell 变量 HISTTIMEFORMAT可以显示时间
如下演示:
# echo $HISTTIMEFORMAT
%F %T
# history 2
2989 2021-11-14 08:45:35 whereis java
2990 2021-11-14 08:45:48 ll -a /usr/bin/java
#echo $HISTFILE
/root/.bash_history
# echo $HISTSIZE
3000
# history |wc -l
3000
复制代码
- HISTFILE是history历史文件的记录文件。
- HISTSIZE是HISTFILE最大行数
- 还有一些不常用的变量
选项和功能
推拉原理
history有几个重要的选项,提供了以下功能:
- -a 追加“新”历史行,从当前 bash开始输入到历史文件。
- -n 将尚未从历史文件中读取的历史行读入当前历史列表。这些是自当前 bash 会话开始以来附加到历史文件的行。
- -a 执行逻辑,交互式输入的,不重复录入history命令,也可以指定要忽略的一些常见命令
- -n 将尚未从历史文件中读取的历史行读入当前历史列表
我们经常会发现输入过的shell指令从history里面找不到,就是因为我们不清楚shell的历史记录是保存在内存里的,需要执行命令或正常退出的时候才保存到文件;而有太多的异常退出了。
异常退出
bash进程exit退出的时候会保留曾经执行过的history,很多异常都会导致shell被中断,比如:
- 断电
- 断网
- 终端强行被关闭
也有可能bash存在定期保持历史的行为,但是如果有重要的命令要及时保存,不能心存侥幸。
所以这就能解释我们为什么history总找不到曾经执行过的命令了。
其他选项
还有一些其他选项的功能,但基本不重要了,了解或不了解都可。
- history -c 删除所有条目来清除历史列表,一切归零。
- -d offset: 删除位置偏移处的那条历史记录。
- -r 读取历史文件的内容并将其用作当前历史
- -w 将当前历史写入历史文件,覆盖历史文件的内容
快捷键
- CTRL+R: 搜索最新的历史命令
#(reverse-i-search)\`do\`:
- 上下箭头:查找刚执行的上面一个或几个命令。
shell脚本有没有history记录?
shell脚本
上面提到,shell脚本是通过bash来运行的一个脚本文件。 就像前面提到的那样,bash程序就是一个黑盒,有输入输出: $$
当前bash进程ID, $0
当前shell指令, $1
当前输入参数...
但是shell脚本有没有history记录呢?
做了一个小实验, 开启的终端运行的shell是/bin/bash(PID=9923),
小实验1
- hello脚本文件里面不指定shell,以下是全部内容:
echo "Hello World !"
sleep 15
echo "done"
复制代码
运行chmod a+x hello && ./hello
则会启动一个新的bash,在新bash里面 会依次执行这些命令 如下效果:
# ps -ef |grep 9923
root 9923 9920 0 16:59 pts/1 00:00:00 -bash
root 11686 9923 0 17:04 pts/1 00:00:00 -bash
# ps -ef |grep 11686
root 11686 9923 0 17:04 pts/1 00:00:00 -bash
root 11687 11686 0 17:04 pts/1 00:00:00 sleep 15
复制代码
可以看出,
- 11686是新启动的bash进程,正在执行
sleep 15
这个指令。 - 11686读取hello文件
lr-x------ 1 root root 64 11月 15 21:43 254 -> /root/hello
- 等bash2的指令执行完毕,则11686退出
- 退出后,执行记录没有保存到history文件
小实验2
- 以
#!/bin/bash
开头
#!/bin/bash
echo "Hello World !"
...
复制代码
效果:
# ps -ef |grep 9923
root 9923 9920 0 16:59 pts/1 00:00:00 -bash
root 14967 9923 0 17:15 pts/1 00:00:00 /bin/bash ./hello
# ps -ef |grep 14967
root 14967 9923 0 17:15 pts/1 00:00:00 /bin/bash ./hello
root 14968 14967 0 17:15 pts/1 00:00:00 sleep 15
复制代码
可以看出
- 使用
/bin/bash ./hello
的方式运行脚本,这个进程是14967,里面运行的指令是它的子进程。 - 以
#!/bin/sh
开头的脚本效果和上面一样,只不过运行的进程换成sh-/bin/sh ./hello
- 还可以看出sleep进程的pid只比运行它的shell大1,因为我们进程ID是单调递增的,说明echo命令没有创建新的进程。
- Shell在执行外部命令或子脚本,会向系统申请创建新进程
- Shell的内部命令不会创建新进程
- Built-in命令很多,包括
alias, bg,cd, echo, exit, export, fg, help, history, jobs, kill, pwd, read, set, source,type,ulimit
等,echo也是内置命令
# type echo echo 是 shell 内嵌 复制代码
小实验3
在bash里面输入bash
# bash
# whoami
root
# exit
exit
# history -n
复制代码
- 子进程bash执行whoami
- 子进程bash exit
- 父进程执行history -n
- 父进程bash上下能翻到
whoami
这个历史命令
但是之前的shell脚本却没有history记录,这是为什么呢?
可以猜想是因为那个shell不是交互式的,而是读取的文件,所以不记录到历史文件。
- shell bash通过-i 选项,或者不读文件,或存在 -c 选项(从 string 中读取命令),则 shell 是交互式的,否则不是。
- history功能是为交互式设计的,非交互式的命令不记录history文件的。
所以回到问题,shell脚本有没有history记录?
答案就是没有。
Next
Shell系统的下一篇,会讲讲exec,以及Docker中的exec和shell两种命令启动模式。
如果这篇文章对您有所帮助,或有所启发的话,可以点赞收藏或关注:),您的支持是我坚持写作最大的动力。