今天在看~/.bashrc文件的时候看到一行,对它的意思不是非常了解:

[[ $- != *i* ]] && return

就特意上Google搜索了一下,对于搜索关键字是符号(这里是$-)的,我是不怎么抱希望的。所以干脆将$-的结果(himBh)放到Google一起搜索。果然找到两篇不错的帖子:

这两个帖子都是在ChinaUnix论坛上的。这些老论坛虽然人气不行,但是一些资源还是很赞的,这些总结帖都非常好。尤其是第一篇非常值得一读,我只是看了其中一部分。对sh和bash的区别并没有仔细看,觉得这些没必要去死记,到时候碰到问题再去理解就好,要不然就太枯燥了。

Linux用了这么多年,Shell一直在用,执行命令,安装软件,配置文件,以及编写脚本,但是说真地,我对Shell一直不是非常了解,几乎没看过和它相关的任务手册和文档。一般系统中最常见的Shell还是Bash。Bash的手册非常长,我是坚决不看的,头痛。后果就是对Bash的一些现象一知半解,其实这些基本上在手册上都是有写的。

回到正文,在~/.bashrc中开头的话意思是,如果$-的内容不包含i,则直接退出脚本。加一句题外话,return语句只能用于函数或者被source的脚本中返回,如果直接执行则会报错。事实上,$-中包含i意思是指当前的Shell是一个交互式(interactive)的Shell。什么是交互式的呢?就像我们平时通过命令行做事一样,你敲一个命令,终端解析执行完之后给你一个结果,这样一种交互式的形式。那么,平时我们接触的Shell基本上都是交互式的,如gnome-terminal打开一个Shell以及通过Ctrl+alt+1等切换过去的文本终端。交互式Shell下, "echo $-"返回的字符串中包含i,否则不包含。也可以通过在bash后面加-i参数打开一个交互式的Shell,具体可以看man bash。bash后面加-c参数执行命令打开的是非交互式Shell,可以用以下命令验证:

$ bash -c 'echo $-'  # 返回hBc

解释完交互式之后,继续解析文章题目后半部分中的登录二字。登录Shell其实很好理解,就是我们平时通过用户名/密码才能登录的Shell,最典型的就是用Ctrl+alt+1切换过去的文本终端。如何区分登录Shell和非登录Shell呢,可以通过查看$0的值,登录Shell返回-bash,而非登录Shell返回的是bash。平时gnome-terminal打开的Shell就是非登录Shell。也可以通过在bash后面加--login参数打开一个登录Shell。虽然是这么说,但我还是不知道如何在gnome-terminal下打开一个登录式的Shell。

接下来看看这些不类的调用Shell有什么区别,可以查看man bash中的Invocation一节。一段一段来看:

A login shell is one whose first character of argument zero is a -, or one started with the --login option.
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. PS1 is set and $- includes i if bash is interactive, allowing a shell script or a startup file to test this state.

上面一段话的内容在前面基本上已经介绍过了。加粗的这段话可以用来判断一个Shell是否为交互式的。

When bash is invoked as an interactive login shell, or as a non-inter‐active shell with the --login option, it first reads and executes commands from the file /etc/profile, if that file exists. After reading that file, it looks for ~/.bash_profile, ~/.bash_login, and ~/.profile, in that order, and reads and executes commands from the first one that exists and is readable. The --noprofile option may be used when the shell is started to inhibit this behavior. When a login shell exits, bash reads and executes commands from the file ~/.bash_logout, if it exists.

如果一个Shell是交互式登录Shell或者使用--login参数的非交互式登录Shell。这首先会读取/etc/profile文件并执行,如果该文件存在。然后按顺序查找~/.bash_profile, ~/.bash_login,或者~/.profile,读入第一个存在的文件并执行。可能通过指定--noprofile参数来禁止这种默认行为。当登录Shell退出之后,会读取~/.bash_logout文件并执行。

When an interactive shell that is not a login shell is started, bash reads and executes commands from ~/.bashrc, if that file exists. This may be inhibited by using the --norc option. The --rcfile file option will force bash to read and execute commands from file instead of ~/.bashrc.

如果是一个交互式非登录Shell,bash会读取~/.bashrc文件中定义的命令。同时,可以指定--norc参数来禁止该行为,或者通过--rcfile指定其它文件。

When bash is started non-interactively, to run a shell script, for example, it looks for the variable BASH_ENV in the environment, expands its value if it appears there, and uses the expanded value as the name of a file to read and execute. Bash behaves as if the following command were executed:
if [ -n "$BASH_ENV" ]; then . "$BASH_ENV"; fi
but the value of the PATH variable is not used to search for the filename.

如果是一个非交互式非登录Shell,比如运行一个Shell脚本,它会查找BASH_ENV定义的文件读入并执行。

后面其实还有一段说sh和bash不同的之处的一段话,这里就不放了,其中读入这些以上文件的时候不考虑bash开头的那几个文件,做到尽量少的设置,保证与POSIX标准兼容。

事实上,在ArchLinux下,登录Shell也是会读入~/.bashrc文件的,因为~/.bash_profile文件的内容默认是这样的:

#
# ~/.bash_profile
#

[[ -f ~/.bashrc ]] && . ~/.bashrc

同时,在$HOME目录下同时存在~/.bash_profile和~/.profile两个文件,并且两个文件都会被读入,因为我的fcitx配置就是放在~/.profile中的。这与man bash里面的描述有些不一致。可能桌面环境下会有一些区别吧,又或者是用GDM登录的缘故?然后我顺着这条线索去找/etc/gdm下面的配置文件,果然在/etc/gdm/Xsession中发现以下内容:

# First read /etc/profile and .profile
test -f /etc/profile && . /etc/profile
test -f "$HOME/.profile" && . "$HOME/.profile"
# Second read /etc/xprofile and .xprofile for X specific setup
test -f /etc/xprofile && . /etc/xprofile
test -f "$HOME/.xprofile" && . "$HOME/.xprofile"

一切都有好解释了。好了,不再继续写下去了,Dota去!