bash属性、startup文件与history

bash与shell区别

shell有多个含义:

  • Shell 是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁。Shell 既是一种命令语言,又是一种程序设计语言。
  • Shell 是指一种应用程序,这个应用程序提供了一个界面,用户通过这个界面访问操作系统内核的服务。

bash(GNU Bourne-Again Shell)是最常用的一种shell,是当前大多数Linux发行版的默认Shell。除了bash外,其它的shell还有zsh、sh等。

sh的全名是Bourne Shell。名字中的玻恩就是这个Shell的作者。而bash的全名是Bourne Again Shell。最开始在Unix系统中流行的是sh,而bash作为sh的改进版本,提供了更加丰富的功能。一般来说,都推荐使用bash作为默认的Shell。

如何查看当前系统中默认shell?

1
echo $SHELL

当前正在使用的 Shell 不一定是默认 Shell,一般来说,ps命令结果的倒数第二行是当前 Shell。

1
2
3
4
$ ps
PID TTY TIME CMD
4467 pts/0 00:00:00 bash
5379 pts/0 00:00:00 ps

Shell相当于是一个翻译,把我们在计算机上的操作或我们的命令,翻译为计算机可识别的二进制命令,传递给内核,以便调用计算机硬件执行相关的操作;同时,计算机执行完命令后,再通过Shell翻译成自然语言,呈现在我们面前。

img

bash运行方式

含义

Linux shell是用户与Linux系统进行交互的媒介,而bash作为目前Linux系统中最常用的shell,它在运行时具有两种属性,即“交互”与“登陆”。

  • 交互式,是shell的一种运行模式,交互式shell等待你输入命令,并且立即执行,然后将结果反馈给你。这是每个CLI用户都非常熟悉的流程:登录、执行一些命令、登出。当你登出后,这个shell就终止了。
  • 而非交互式,是shell的另一种运行模式,它专门被用来执行预先设定的命令。在这种模式下,shell不与用户进行交互,而是读取存放在脚本文件中的命令并执行它们。当它读到文件的结尾,这个shell就终止了。

那么我们启动的时候,如何才能进行进行交互或登陆呢?

根据bash手册上的描述:

An interactive shell is one started without non-option arguments and without the -c option whose standard input and error are both connected to terminals (as determined by isatty(3)), or one started with the -i option.

从上面的描述看,只要执行bash命令的时候,不带有“选项以外的参数”或者-c选项,就会启动一个交互式shell。

  • “选项以外的参数”指的就是shell的脚本文件;
  • -c选项将指定字符串作为命令读入bash,也就相当于执行指定的命令,它和前者有些类似,只是不从脚本文件中读取罢了。
1
2
3
4
5
6
7
8
9
[chen@localhost Temp]$ echo "uname -r; date" > script.sh
[chen@localhost Temp]$ bash ./script.sh # 非交互式
3.10.0-514.el7.x86_64
Tue Apr 18 14:43:50 CST 2017
[chen@localhost Temp]$
[chen@localhost Temp]$ bash -c "uname -r; date" # 非交互式
3.10.0-514.el7.x86_64
Tue Apr 18 14:44:49 CST 2017
[chen@localhost Temp]$

另外,从上面描述来看,通常来说,用于执行脚本的shell都是“非交互式”的,但我们也有办法把它启动为“交互式”shell,方法就是在执行bash命令时,添加-i选项:

1
2
3
4
5
[chen@localhost Temp]$ bash -c "echo \$-"
hBc
# 我们看到,添加了-i选项的bash -c命令为我们启动了一个“交互式”shell。
[chen@localhost Temp]$ bash -i -c "echo \$-" # 交互式
himBHc

这里解释一下echo \$-:It shows your Builtin Set Flags. man bash then look for SHELL BUILTIN COMMANDS and then look for the set subsection. You will find the meanings of all those flags:

1
2
3
4
5
6
> h: Remember the location of commands as they are looked up for execution.  This is enabled by default.
> i: interactive
> m: Monitor mode. Job control is enabled
> B: The shell performs brace expansion (see Brace Expansion above). This is on by default
> H: Enable ! style history substitution. This option is on by default when the shell is interactive.
>

判断

那么我们如何在shell脚本或者startup文件中判断当前shell的运行方式呢?

我们首先来看,bash手册的描述:

PS1 is set and $- includes i if bash is interactive, allowing a shell script or a startup file to test this state.

也就是说,可以判断变量PS1是否有值,或者判断变量$-是否包含i,实现在shell脚本或者startup文件中判断当前shell的运行方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
# 在shell脚本中写入如下语句,通过输出判断当前shell运行方式
[chen@localhost Temp]$ cat ./test1.sh
echo "\$0 : $0"
echo "\$- : $-"
echo "\$PS1 : $PS1"
[chen@localhost Temp]$ bash ./test1.sh # 非交互式shell
$0 : ./test1.sh
$- : hB
$PS1 :
[chen@localhost Temp]$ bash -i ./test1.sh # 交互式shell
$0 : ./test1.sh
$- : himB
$PS1 : [\u@\h \W]\$

登陆shell与非登陆shell

含义

“登陆shell”通常指的是:

  1. 用户通过输入用户名/密码(或证书认证)后启动的shell;
  2. 通过带有-l|--login参数的bash命令启动的shell。例如,系统启动、远程登录、使用su -切换用户、通过bash --login命令启动bash等。

而其他情况启动的shell基本上就都是“非登陆shell”了。例如,从图形界面启动终端、使用su切换用户、通过bash命令启动bash等。

判断

根据bash手册上的描述:

A login shell is one whose first character of argument zero is a -, or one started with the --login option.

我们可以通过在shell中echo $0查看,显示-bash的一定是“登陆shell”,反之显示bash的则不好说。

1
2
3
[chen@localhost ~]$ bash --login
[chen@localhost ~]$ echo $0
bash

可以看出,使用bash --login启动的“登陆shell”,其$0也并非以-开头,这也就是为什么手册上的描述里使用“or”的原因。

另外,当我们执行exit命令退出shell时,也可以观察到它们的不同之处:

1
2
3
4
5
6
[chen@localhost ~]$ bash --login
[chen@localhost ~]$ exit # 退出登陆shell
logout
[chen@localhost ~]$ bash
[chen@localhost ~]$ exit # 退出非登陆shell
exit

原则上讲,我们使用logout退出“登陆shell”,使用exit退出“非登录shell”。但其实exit命令会判断当前shell的“登陆”属性,并分别调用logoutexit指令,因此使用起来相对方便。

主要区别

对于用户而言,“登录shell”和“非登陆shell”的主要区别在于启动shell时所执行的startup文件不同

简单来说,“登录shell”执行的startup文件为~/.bash_profile,而“非登陆shell”执行的startup文件为~/.bashrc

下面我们进行详细说明。

bash的startup文件

它支持的startup文件也并不单一,甚至容易让人感到费解。接下来以CentOS7系统为例,对bash的startup文件进行一些必要的梳理和总结。

根据bash手册的描述:

  • /etc/profile
    The systemwide initialization file, executed for login shells

  • /etc/bash.bash_logout
    The systemwide login shell cleanup file, executed when a login shell exits

  • ~/.bash_profile
    The personal initialization file, executed for login shells

  • ~/.bashrc
    The individual per-interactive-shell startup file

  • ~/.bash_logout
    The individual login shell cleanup file, executed when a login shell exits

此外,bash还支持~/.bash_login~/.profile文件,作为对其他shell的兼容,它们与~/.bash_profile文件的作用是相同的。

备注:Debian系统会使用~/.profile文件取代~/.bash_profile文件,因此在相关细节上,会与CentOS略有不同。

profile与rc系列

通过名字的不同,我们可以直观地将startup文件分为“profile”与“rc”两个系列,其实他们的功能都很类似,但是使用的场景不同,这也是大家最容易忽略的地方。

所谓的不同场景,其实就是shell的运行模式。我们知道运行中的bash有“交互”和“登陆”两种属性,而执行“profile”系列还是“rc”系列,就与shell的这两个属性有关。

原理上讲,“登陆shell”启动时会加载“profile”系列的startup文件,而“交互式非登陆shell”启动时会加载“rc”系列的startup文件。

总结

对于“登录shell”而言,“交互式”执行“登陆”和“登出”相关的“profile”系列startup文件,“非交互式”只执行“登陆”相关的“profile”系列startup文件;对于“非登陆shell”而言,“交互式”执行“rc”系列的startup文件,而“非交互式”执行的配置文件由环境变量BASH_ENV指定。

Linux中startup文件区分全局和个人:全局startup文件放在/etc目录下,用于设置所有用户共同的配置,除非你清楚地知道你在做的事情,否则不要轻易改动它们;个人startup文件放在~目录下,用于设置某个用户的个性化配置。

~/.bash_profile会显式调用~/.bashrc文件,而~/.bashrc又会显式调用/etc/bashrc文件,这是为了让所有交互式界面看起来一样。无论你是从远程登录(登陆shell),还是从图形界面打开终端(非登陆shell),你都拥有相同的提示符,因为环境变量PS1/etc/bashrc文件中被统一设置过。

下面我来对startup文件进行一个完整的总结:

startup文件交互登陆非交互登陆交互非登陆非交互非登陆
/etc/profile直接执行1直接执行1--
~/.bash_profile直接执行2直接执行2--
~/.bash_login条件执行2条件执行2--
~/.profile条件执行2条件执行2--
~/.bash_logout直接执行3不执行--
/etc/bash.bash_logout直接执行4不执行--
~/.bashrc引用执行2.1引用执行2.1直接执行1-
/etc/bashrc引用执行2.2引用执行2.2引用执行1.1-

备注:

  1. “直接执行”表示此文件被系统直接调用,它的执行是无条件的;
  2. “条件执行”表示此文件被系统调用是有先决条件的(没有优先级更高的文件可用);
  3. “引用执行”表示此文件不是被系统直接调用的,而是被其他文件显式调用的;
  4. 后面的数字表示文件被调用的顺序,数字越大调用越靠后;
  5. “非交互非登陆”shell的配置文件可以由BASH_ENV环境变量指定;

如果你想对bash的功能进行设置或者是定义一些别名,推荐你修改~/.bashrc文件,这样无论你以何种方式打开shell,你的配置都会生效。而如果你要更改一些环境变量,推荐你修改~/.bash_profile文件,因为考虑到shell的继承特性,这些更改确实只应该被执行一次(而不是多次)。针对所有用户进行全局设置,推荐你在/etc/profile.d目录下添加以.sh结尾的文件,而不是去修改全局startup文件。

具体更加详细的解释可以参考文章关于“.bash_profile”和“.bashrc”区别的总结

bash history

默认情况下, bash 只在退出的时候更新命令历史, 而且这个”更新”是用新版直接覆盖旧版。这会使你无法保持一份完整的命令历史记录, 原因有两个:

  • 如果一个用户登录多次,这种覆盖的机制会使得只有最后一个退出的 bash 能保存它的历史记录。(一个登录的用户打开多个终端模拟器, 或者使用 screen/tmux 等工具启动多个 bash 等也在此列 )
  • 如果你的 bash 异常退出了 – 比如网络故障,防火墙更改,或者它的进程被杀掉了 – 会话中所有的历史记录都会丢失。

你设置在 .bashrc 文件中添加下面这句就够了

1
shopt -s histappend

它让 shell 退出时是添加新记录,而不是覆盖原来的文件。这样你关闭多个终端时就不会挨个覆盖了。

顺便,你也许想把历史记录保存条数设置大一点

1
2
3
4
# 设置历史记录条数
export HISTFILESIZE=100000
# 设置显示历史记录条数
export HISTSIZE=10000

另外下面非常推荐设置,可以让你能够用方向键翻阅历史

1
2
bind '"\e[A": history-search-backward'
bind '"\e[B": history-search-forward'

参考

Shell 教程
Bash 简介
Bash编程入门-1:Shell与Bash
关于“交互式-非交互式”与“登录-非登陆”shell的总结
Why does running “echo $-“ output “himBH” on the bash shell?
关于“.bash_profile”和“.bashrc”区别的总结
bash 下 history 会因多个终端而覆盖丢失,有好的解决方案吗? - Zhou Zhao的回答 - 知乎
[译] 如何防止丢失任何 bash 历史命令?

------ 本文结束------
坚持原创技术分享,您的支持将鼓励我继续创作!

欢迎关注我的其它发布渠道