最近在学习Bash方面的相关知识,了解了Bash的基本分类和其相关的配置文件。在参考网上一些资料的时候,发现网上的一些资料写得很乱而且也不够全面,同时对shell分类的定义也不够本质,甚至某些说法还有一些误导读者的倾向。索性自己根据Bash手册页中的相关内容总结一下。


Bash用法概览:

    bash [option] file


Bash部分选项说明(只取和本篇相关的来说明):

    -c string 如果使用-c选项,将从string处读取命令。如果string后面有参数(具体指令的参arguments)的话,他们将被分配给由$0开始的位置参数(Bash的参数parameters)。

    -i 如果使用-i选项,该shell就是交互的。

    -l 使Bash被调用后的行为犹如登录shell(实际上不是真的存在哪个用户来登录,原文此处使用了完成时态的虚拟语气,编者注)。

    --rcfile file 如果shell是交互的,则从file中执行命令而不是从标准的个人初始化文件~/.bashrc中执行。

    --login 等同于-l

    --noprofile 不会读取系统全局启动文件/etc/profile,也不会读取以下任何一个个人初始化文件~/.bash_profile,~/.bash_login或者~/.profile。默认情况下,当作为登录shell调用时Bash会读取这些文件。

    --posix 将Bash默认不同于POSIX标准的行为进行修改,以匹配POSIX标准(posix模式)。


Bash的调用:

登录shell就是参数零的第一个字符是"-"号,或者以--login选项启动的shell。(注意在登录shell中显示该shell的特殊变量0,如echo $0,会看到这个变量的记录的字符以-开头,编者注释)

交互式shell就是没有以非选项参数及-c选项、标准输入和错误都被连接终端(由isatty(3)决定),或者以-i选项来启动的shell。如果shell是交互的,PS1会被设定同时变量$-包含字符i,一次来实现使用脚本或者启动文件来测试这种状态。

接下来的段落描述了Bash是如何来执行它的启动文件的。如果以下文件中的任何一个存在但是不可以读取,Bash会报告一个错误。文件名中的波浪号用来进行扩展,具体参见扩展章节的波浪号扩展。(众所周知,"~"扩展以后其实就表示用户的家目录啦,编者注)

交互式登录shell,或者带有--login选项的调用

当Bash被当作交互式登录shell或者被当作带有--login选项的非交互式shell调用时,Bash首先读取并从文件/etc/profile中执行命令,如果该文件存在的话。读取该文件之后,Bash按照如下先后顺序搜索文件~/.bash_profile,~/.bash_login,~/.profile,然后读取第一个存在而且可读的文件并且执行其中的命令。可以在shell启动时使用--noprofile选项来禁止这种行为。

如果是以登录shell注销的,Bash会读取并执行文件~/.bash_logout和/etc/bash.bash_logout中的命令,假如文件存在的话。

交互式非登录shell的调用

当要启动非登录交互式shell时,Bash读取并从~/.bashrc中执行命令,如果该文件存在的话。可以使用选项--norc来禁止这中情况。选项--rcfile file会强制Bash从file处读取并执行命令,而不是从~/.bashrc中。

非交互式调用

当Bash以非交互方式启动时,比如运行了一个shell脚本,Bash会在环境中查找变量BASH_ENV,如果BASH_ENV存在的话,Bash就会扩展其值,并且使用该变量的值作为要读取和执行的文件名。Bash的行为犹如下面的命令执行后的结果:

                if    [ -n "$BASH_ENV" ];    then    . "$BASH_ENV";    fi

不过PATH变量中的值不会用来搜索该文件名。

(在shell中,环境的本质定义就是一个形如名称-值字符串数组构成的名称-值对列表。Bash在处理一个脚本时,会fork一个子shell并且将环境变量中的值export到子shell中。如果要使用变量BASH_ENV来指定Bash的启动文件的话,注意要使用文件的全路径,编者注)

带有sh名称调用

如果使用名称sh来调用的话,Bash会尽可能接近地尝试模拟sh历史版本的启动行为,同时也更贴合POSIX标准。当被当作交互式登录shell或者被当作带有--login选项的非交互式shell调用时,Bash首先试图读取并按照如下次序从/etc/profile和~/.profile中执行命令。可以使用选项--noprofile禁止这种行为。通过执行sh的方式来调用交互shell时,Bash查找变量ENV,如果ENV已被定义就扩展其值,同时使用该扩展值作为读取和执行文件的名称。既然使用执行sh的方式调用的shell不会读取任何其它启动文件并从中执行命令,那么,--rcfile就不会产生影响。使用sh名称调用的非交互式shell也不会读取任何其它文件。当使用sh方式调用时,Bash会在读取启动文件之后进入posix模式。

以POSIX模式调用

当Bash进入posix模式时,比如使用了--posix命令行选项,bash对启动文件的处理遵循POSIX标准。在这个模式下,交互式shell扩展ENV变量,同时命令会从以变量扩展值作为名称的文件中读取并被执行。Bash不会再读取其它的启动文件。

被远程shell守护进程调用

当运行时,Bash试图决定链接到网络连接上的标准输入,这些连接一般由远程shell守护进程,通常为rshd,或者是由安全shell守护进程sshd发起。如果Bash决定以这种方式运行,它会从~/.bashrc中读取并执行命令,假如该文件存在而且可读的话。如果以sh的方式调用的话,Bash就不会如此处理。选项--norc可以用来禁用上述的配置方式,同时--rcfile选项可以用来强制读取另外的文件,但是rshd一般不会使用这些选项来调用shell或者不允许这些指定这些选项。

有效和实际UID/GIDs不相等时的调用

如果shell启动时,有效用户(组)id同实际用户(组)id不相等,同时-p选项未被支持,没有任何启动文件会被读取,shell的函数不会从环境中继承,如果SHELLOPTS,BASHOPTS,CDPATH和GLOBIGNORE这些变量出现在环境中,都会被忽略掉,同时有效用户id会按照实际用户id的值来设置。如果在在调用时支持了-p选项,Bash的启动行为是相同的,但是有效用户id不会被重置。

(特别注意:在GNU Bash中${N}被定义作位置参数(Positional Parameters),N为整数,但不含0,变量$-和$0被当作特殊变量(Special Parameters)对待,编者注释)


由上述对Bash原生文档的阐述,我们可以得到如下的结论:

  1. 在Bash中判断shell类型的最根本方式是查看特殊变量$-和$0的值,如果变量$0值的首字符是"-"的话,则表示该shell是登录shell。如果变量$-的值中含有字符"i"的话,则表示该shell是交互式shell。如果变量PS1的值为空值则表示该shell是一个非交互式shell。使用脚本启动的shell的未必不是一个登录shell,比如在脚本的shebang(脚本的首行)处增加--login选项。

  2. 使得Bash成为交互式shell的两种方法:

       不用含有非选项的方式(比如指定了一个file作为调用参数)且没有使用-c选项来启动Bash

       启动Bash时指定了-i选项

   3. 同交互式登录shell和带有--login选项的非交互式shell相关的配置文件如下所示,注意使用范围

       /etc/profile    对所有用户全局有效

       /etc/profile.d/    该目录下有很多脚本,/etc/profile会将其中的文件都执行一遍

       ~/.bash_profile    以下3个配置文件只对具体用户有效,而且有搜索顺序,只执行找第一个可以执

       ~/.bash_login      行的文件

       ~/.profile

    4. 交互式非登shell的配置文件为~/.bashrc,只能够对具体用户生效,但是很多Linux发行版,比如

        RedHat会在~./.bashrc文件中执行文件/etc/bashrc中的内容,这样使得/etc/bashrc中的配置对

        全体用户生效

    5. 使用sh script的方式来调用shell不同于bash script和./script,这在以上的说明中可以看出来,但

        是在CentOS中/bin/sh默认是指向/bin/bash的软连接。Bash是如何判断是否以sh的方式调用自己

        的呢,这点没想明白,还望高人指点。

    6. 如果Bash以一个非交互式shell启动,比如crond启动了一个自动执行的脚本,Bash是不会读取任何

        原先讲述的那些启动配置文件的,当然如果给脚本增加--login选项,那么Bash是会读取登录shell

        的启动配置文件的。

    7.查看/etc/bashrc,发现里面有执行/etc/profile.d/目录下所有脚本的段落。其实更新bash程序以后

       (比如yum升级),/etc/profile文件会被新的Bash程序所更新,但是/etc/profile.d/中的脚本就不会

       受到影响,所以考虑把同Bash无关的一些全局配置放到这儿。

    8.先来看下CentOS6中~/.bash_profile、~/.bashrc/、/etc/bashrc这几个文件的片段:    

       ~/.bash_profile文件

                                # Get the aliases and functions
                                if [ -f ~/.bashrc ]; then
                                        . ~/.bashrc
                                fi

       ~/.bashrc/文件

                                # Source global definitions
                                if [ -f /etc/bashrc ]; then
                                        . /etc/bashrc
                                fi

       /etc/bashrc文件

                                # Only display echos from profile.d scripts if we are no login shell
                                # and interactive - otherwise just process them to set envvars
                                for i in /etc/profile.d/*.sh; do
                                        if [ -r "$i" ]; then
                                                if [ "$PS1" ]; then
                                                        . "$i"
                                                else
                                                        . "$i" >/dev/null 2>&1
                                                fi
                                        fi
                                done

       这几个文件的配置片段讲得很清楚,~/.bash_profile会去读取并执行~/.bashrc中的配置,

       ~/.bashrc会去读取并执行/etc/bashrc中的配置,而/etc/bashrc则会判断当前的shell是否为交互式

       shell,如果是的话就去执行目录/etc/profile.d/下的所有以sh为扩展名的脚本。也就是说,在

       CentOS6的系统中(也可以理解为RedHat),如果Bash当作交互式非登录shell调用时,最终同样会

       执行/etc/profile.d目录中的环境初始化脚本;如果Bash当作交互式登录shell或者带有--login选项

       调用时,也会访问~/.bashrc和/etc/bashrc文件。执行流程类似如下过程:

        交互式登录shell

        /etc/profile-->/etc/profile.d/*.sh-->~/.bash_profile-->~/.bashrc-->/etc/bashrc

        带有--login选项的非交互式shell

        /etc/profile-->~/.bash_profile-->~/.bashrc-->/etc/bashrc-->/etc/profile.d/*.sh

        (这所以上面2中情况分开写,是因为/etc/profile中的下划线处的判断语句

        for i in /etc/profile.d/*.sh ; do
            if [ -r "$i" ]; then
                if [ "${-#*i}" != "$-" ]; then
                    . "$i"
                else
                    . "$i" >/dev/null 2>&1
                fi
            fi
        done

        ${-#*i}的具体语法请参阅gnubash帮助手册,此处不在赘述)

        交互式非登录shell

        ~/.bashrc-->/etc/bashrc-->/etc/profile.d/*.sh