环境变量、HISTORY和SHELL(二)

「这是我参与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表示失败

history1.png

  • 进程有配置文件,一般初始化的时候都会先读取配置文件
  • bash有配置文件/root/.bash_profile
  • history也有配置文件
  • 其他各种程序都有配置文件

环境变量和SHELL

系统和程序都用

环境变量是给操作系统或者应用程序使用的变量信息。

# echo $HISTFILE
/root/.bash_history
复制代码
  • 可以设置整体的环境变量
  • 进程也可以单独设置自己独有的环境变量

环境变量的隔离

  • 使用shell内置的export设置的环境变量,当前bash可生效;export命令可新增,修改或删除环境变量

history3.png

  • export设置的环境变量在bash具有隔离性,A 只在bash1生效,B只在bash2生效。

这就是为什么我们经常觉得设置的环境变量没有生效的原因。

环境变量的共享

需要把export写到bash配置文件才能全局生效:

  • 其他同时运行的bash执行source才能看到其他bash设置的环境变量。
  • 新开的bash启动的时候会先将配置文件里面的命令执行一遍。

如下图:

history4.png

  • 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 会话开始以来附加到历史文件的行。

history6.png

  • -a 执行逻辑,交互式输入的,不重复录入history命令,也可以指定要忽略的一些常见命令
  • -n 将尚未从历史文件中读取的历史行读入当前历史列表

我们经常会发现输入过的shell指令从history里面找不到,就是因为我们不清楚shell的历史记录是保存在内存里的,需要执行命令或正常退出的时候才保存到文件;而有太多的异常退出了。

异常退出

bash进程exit退出的时候会保留曾经执行过的history,很多异常都会导致shell被中断,比如:

  • 断电
  • 断网
  • 终端强行被关闭

也有可能bash存在定期保持历史的行为,但是如果有重要的命令要及时保存,不能心存侥幸。

history2.png

所以这就能解释我们为什么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
复制代码

history9.png

可以看出,

  • 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
复制代码

history8.png

可以看出

  • 使用/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两种命令启动模式。


如果这篇文章对您有所帮助,或有所启发的话,可以点赞收藏或关注:),您的支持是我坚持写作最大的动力。

猜你喜欢

转载自juejin.im/post/7030801782834118693