# Bash Shell 的操作环境

在我们登陆主机的时候,屏幕上会有一些说明文字,告知我们的 Linux 版本之类的信息,还可以显示一些欢迎等信息。此外,我们习惯的环境变量、命令别名等,是否可以在登录后就主动帮我设置好?

这些设置又分为系统全局配置和个人账户级配置,仅是文件放置位置不同

# 路径与指令搜寻顺序

前面讲到过使用 alias 可以建立别名,比如创建了一个 ls 的别名,其实 ls 有少的指令,那么到底是哪一个会被选中执行呢?基本上,指令运行顺序可以这样看:

  1. 以相对、绝对路径执行命令,例如 /bin/ls./ls
  2. 由 alias 找到该指令来执行
  3. 由 bash 内置的指令来执行
  4. 通过 $PATH 这个变量的顺序搜索到第一个指令执行

举例来说:

  • /bin/ls:该指令运行后,没有颜色
  • ls:该指令运行后输出的内容有颜色,因为是使用别名 alias ls=‘ls --color=auto’

也可以使用 type -a ls 来查询指令搜寻的顺序

# 范例:设置 echo 的命令别名为 echo -n,然后观察 echo 执行的顺序
[mrcode@study ~]$ alias echo='echo -n'
[mrcode@study ~]$ type -a echo
echo is aliased to `echo -n'
echo is a shell builtin
echo is /usr/bin/echo
1
2
3
4
5
6

可以看到上面的顺序与本节总结的执行顺序一致

# bash 的进站与欢迎信息:/etc/issue、/etc/motd

# 进站信息 /etc/issue

在 tty1~tty6 登录时,会有几行提示字符,这个就是进站画面,该字符串在 /etc/issue 中配置的

[mrcode@study ~]$ cat /etc/issue
\S
Kernel \r on an \m

1
2
3
4

如上的变量引用使用的是反斜杠,变量可以通过 man issue 中查看到 agetty ,再 man agetty 得到如下的信息,代码变量信息如下

  • \d:本地端时间的日期
  • \l:显示第几个终端机接口
  • \m:显示硬件的等级(i386、i486、i586...)
  • \n:显示主机的网络名称
  • \O:显示 domain name
  • \r:操作系统的版本(相当于 uname -r)
  • \t:显示本地端时间的时间
  • \S:操作系统的名称
  • \v:操作系统的版本
# 练习:如果想在 tty3 的进站画面看到如下显示,该如何设置才能达到效果?

CentOS Linux 7 (Core)(terminal:tty3)
Date:2019-12-01 18:00:00
Kernel 3.10.0-229.e17.x86_64 on an x86_64
Welcome!

使用 root 身份参考上面的变量说明修改 /etc/issue 文件达成效果

vim /etc/issue

\S (terminal: \l)
Date: \d \t
Kernel \r on an \m
Welcome!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

怎么登录 tty 和切换 tty 请参考之前的章节,记得,进站画面是切换到 tty 时顶部显示的信息,而不是登录后显示的信息。

该文件中的规则就是使用反斜杠引用上面的变量,其他的你可以随意操作,比如写个字符画等,搞得个性一点

当使用 telnet 登录主机时,是不会显示 /etc/issue 中的配置,而是显示 /etc/issue.net 中的配置

# 欢迎信息 /etc/motd

想要使用者登录后,取得一些信息,例如使用注意事项信息,就可以修改 /etc/motd 文件

[root@study ~]# vi /etc/motd 
Hello everyone,
这是欢迎信息中文测试

# 重新登录后会看到如下的信息
Last login: Sun Dec  1 17:37:58 2019 from 192.168.0.105
Hello everyone,
这是欢迎信息中文测试
[mrcode@study ~]$ 

1
2
3
4
5
6
7
8
9
10

# bash 的环境配置文件

我们一进入 bash 就取得了一堆有用的变量,这是因为系统有一些环境配置文件的存在,让 bash 在启动时直接读取这些配置文件,以规划好 bash 的操作环境。而这些配置文件分为全局系统配置和用户个人偏好配置

# login 与 non-login shell

在介绍 bash 的配置文件前,一定要先知道 login shell 与 non-login shell ,重点就在于有没有登录(login)

  • login shell:取得 bash 时需要完整的登录流程,就称为 login shell

    举例来说,你要由 tty1~tty6 登录,需要输入用户的账户与密码,此时取得的 bash 就称为「login shell」

  • non-login shell:取得 bash 接口的方法不需要重复登录的举动

    比如:你以 x window 登录 linux 后,再以 X 的图形化接口启动终端机,此时该终端机并没有再次输入账户与密码,那么该 bash 的环境就称为 non-login shell

    再比如:你再原本的 bash 环境下再次下达 bash 这个指令,同样也没有输入账户密码,那第二个 bash(子程序)也是 non-login shell

上面两种情况取得的 bash 配置文件不一致。由于我们需要登录系统,所以先谈谈 login shell 会读取哪些配置文件?一般来说,login shell 其实只会读取这两个配置文件

  1. /etc/profile:系统整体配置,最好不要修改这个文件
  2. ~/.bash_profile~/.bash_login~/.profile:属于使用者个人设置

# /etc/profile (login shell 才会读)

该文件相对于现在我们来看,可能还不太能看得懂,里面是利用使用者的标识符(UID)来决定很多重要的变量数据,这也是 每个使用者登录取得 bash 时一定会读取的配置文件 ,也就是系统级全局配置,主要变量如下:

  • PATH:会依据 UID 决定 PATH 变量要不要含有 sbin 的系统指令目录
  • MAIL:依据账户设置好使用者的 mailbox 到 /var/spool/mail/账号名
  • USER:根据用户的账户设置该变量类容
  • HOSTANME:依据主机的 hostname 指令决定此变量内容
  • HISTSIZE:历史命令记录数量。CentOS 7.x 设置为 1000
  • umask:包括 root 默认为 022 而一般用户为 002 等

/etc/profile 可不止会做这些事情,还会呼叫外部的设置数据,在 CentOS 7.x 默认情况下,下面的数据会依序被呼叫进来:

# /etc/profile.d/*.sh

通配符方式,加载该目录内所有的 sh 文件,另外,使用者需要具有 r 的权限,那么该文件就会被 /etc/profile 调用。

在 CentOS 7.x 中,该目录下的文件规范了 bash 操作窗口的颜色、语系、ll 与 ls 指令的命令别名、vi 的命令别名、which 的命令别名等。如果你需要帮所有使用者设置一些共享的命令别名时,可以在该目录下自行建立后缀为 .sh 的文件,并将所需要的数据加入即可

# /etc/local.conf

该文件是由 /etc/profile.d/lang.sh 呼叫进来的,这也是我们决定 bash 预设使用何种语系的重要配置文件!文件里最重要的就是 LANG、LC_ALL 这些变量的设置,前面讨论过

# /usr/share/bash-completion/completions/*

tab 键补全,除了命令补齐、文档名补齐外,还可以进行指令的选项、参数补齐功能。就是从这个目录里面找到对应的指令来处理的。

该目录下的内容是由 /etc/profile.d/bash_completion.sh 文件载入的

# ~/.bash_profile (login shell 才会读)

bash 在读完了整体环境设置的 /etc/profile ,并借此加载其他配置文件后,接下来则是会读取使用者的个人配置文件。在 login shell 的 bash 环境中,所读取的个人偏好配置文件其实主要有 3 个,依序分别是:

  1. ~/.bash_profile
  2. ~/bash_login
  3. ~/,profile

其实 bash 的 login shell 设置只会读取上面三个文件中的一个,而读取的顺序则是依照上面的顺序。

什么意思呢?是当第一个文件不存在时,读取第二个,那么当第一个文件存在时,后面的都不读取了

为什么会有这么多的文件?是因为其他 shell 转换过来的使用者习惯不同,而做的兼容。

# 看看 mrcode 的 .bash_profile 的内容
# 具体路径为 /home/mrcode/.bash_profile 
[mrcode@study ~]$ cat ~/.bash_profile 
# .bash_profile

# Get the aliases and functions
if [ -f ~/.bashrc ]; then			# 判断并读取 ~/.bashrc
        . ~/.bashrc
fi

# User specific environment and startup programs
# 下面再处理个人化设置
PATH=$PATH:$HOME/.local/bin:$HOME/bin	

export PATH

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

该文件设置了 PATH,并使用 export 将 PATh 变成环境变量,看配置是通过累加方式将用户家目录下的 ~/bin/ 目录添加进 PATH 了,这就意味着,你可以将可执行文件放到 ~/bin/ 下,执行时,就不需要写全路径了

上面的文件内容中有一段 if...then... 代码,该代码后续再 shell sript 中讲解,这里判断 ~/.bashrc 文件是否存在,存在则加载。

bash 配置文件的读入方式是通过 source 指令来读取的。这个后续讲解,最后来看看整个 login shell 的读取流程

image-20191201181925883

实线的方向是主线流程,虚线的方向则是被加载的配置文件。从上图来看,CentOS 的 login shell 环境下,最终被读取的配置文件是 ~/.bashrc 文件,所以可以将自己的偏好设置写入该文件即可。

下面还要讨论 source 与 ~/.bashrc

# source : 读取环境配置文件的指令

由于 /etc/profile~/.bash_profile 都是在取得 login shell 的时候才会读取的配置文件,所以将自己的偏好设置写入上述文件后,通常都是需要注销后再登录,才会生效。可以使用 source 指令达到立即生效

source 配置文件名
1
# 范例:将 家目录的 ~/.bashrc 的设置读入目前的 bash 环境中
[mrcode@study ~]$ source ~/.bashrc 
[mrcode@study ~]$ . ~/.bashrc 
# 使用 source 或则 小数点的语法 都能将内容读取到当前的 shell 环境中
1
2
3
4

source 还可以用于不同环境配置文件的场景中,比如,我的工作环境分为 3 个,那么需要分别编写属于 3 个项目的环境变量配置文件,当需要该环境时,直接使用 source 加载进来

# ~/.bashrc (non-login shell 会读)

在非登录情况下取得 bash 环境配置文件时,仅会读取 ~/.bashrc 文件

[mrcode@study ~]$ cat ~/.bashrc 
# .bashrc

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

# Uncomment the following line if you don't like systemctl's auto-paging feature:
# export SYSTEMD_PAGER=

# User specific aliases and functions
1
2
3
4
5
6
7
8
9
10
11
12

注意看,不同身份账户不同,这也解释了个人偏好配置文件是什么

[root@study ~]# cat ~/.bashrc 
# .bashrc

# User specific aliases and functions
# 使用者的个人设置
alias rm='rm -i'
alias cp='cp -i'
alias mv='mv -i'

# Source global definitions
# 整体环境的设置
if [ -f /etc/bashrc ]; then
        . /etc/bashrc
fi
1
2
3
4
5
6
7
8
9
10
11
12
13
14

CentOS 7.x 中为什么会主动加载 /etc/bashrc 文件呢?是因为 /etc/bashrc 帮我们的 bash 定义出下面的数据:

  1. 依据不同的 UID 规范出 umask 的值
  2. 依据不同的 UID 规范出提示字符(就是 PS1 变量)
  3. 加载 /etc/profile.d/*.sh 的设置

需要注意的是,/etc/bashrc 是 CentOS 特有的(Red Hat 系统),其他的 distribution 可能不是该名称。由于 ~/.bashrc会加载 /etc/bashrc/etc/profile.d/*.sh 所以,当你不小心删除了 ~/.bashrc 那么这些都不能读取了,你的 bash 提示字符可能就变成下面这个样子了

-bash-4.2$
1

原因是,没有加载 /etc/bashrc 来规范 PS1 d的变量,这种情况也不会影响你的 bash 使用。可以复制 /etc/skel/.bashrc 文件复制到 ~/.bashrc ,恢复回来

# 其他相关配置文件

事实上还有一些配置文件可能会影响到你的 bash 操作

# /etc/man_db.conf

该文件对于系统管理员来说,是一个很重要的文件,它规范了使用 man 时, man page 的路径到哪里去寻找。

如果你是以 tarball 的方式来安装你的数据库,那么你的 man page 可能会放置在 /usr/local/softpackage/man 中,softpackage 是套件的名称,这个时候就需要手动将该路径加到 /etc/man_db.conf 中。否则 man 就会找不到相关的说明文档

# ~/bash_history

在讲解「历史命令」时提到过该文件,预设情况下,历史命令就记录在该文件中。每次登陆 bash 后,bash 会先读取这个文件,将所有的历史指令读入内存,因此,当我们登陆 bash 后就可以查知上次使用过哪些指令

# ~/.bash_logout

该文件则记录了:当我注销 bash 后,系统再帮我做完师门动作后才离开的意思。你可以读取下该文件的内容,预设情况下,注销时,bash 只是帮我们清掉屏幕的信息而已。

不过,你也可以将一些备份或则是其他你认为重要的工作写在这个文件中(如:清空暂存盘)

# 终端机的环境设置:stty、set

前面讲解过可以在 tty1~tty6 这 6 个文字终端机(terminal)环境中登录,登录的时候可以取得一些字符设置的功能。比如

  • 使用退格键(删除键)来删除命令行上的字符
  • ctrl + c 来强制终止一个指令的执行
  • 当时呼入错误时,会有声音跑出来警告

以上功能都是在登录终端机时,自动获取终端机的输入环境设置实现的

事实上,目前我们使用的 Linux distributions 都帮我们制作了最棒的使用者环境了,但是在某些 Unix like 机器中,还是可能需要手动修改配置

# setting tty  
stty [-a]
参数 a:将目前所有的 stty 参数列出来
1
2
3
# 范例 1 :列出所有的按键与按键内容
[root@study ~]# stty -a
speed 38400 baud; rows 19; columns 126; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S;
susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0;
-parenb -parodd -cmspar cs8 -hupcl -cstopb cread -clocal -crtscts
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc -ixany -imaxbel -iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke
# 以上特殊字符 ^ 表示 Ctrl,^C 表示 ctrl + c
1
2
3
4
5
6
7
8
9
10

下面是几个重要的含义:

  • intr:送出一个 interrupt 中断信号给目前正在 run 的程序
  • quit:送出一个 quit 信号给目前正在 run 的程序
  • erase:向后删除字符
  • kill:删除在目前指令列上的所有文字
  • eof:End of file 的意思,代表「结束输入」
  • start:在某个程序停止后,重新启动它的 output
  • stop:停止目前屏幕的输出
  • susp:送出一个 terminal stop 的喜好给正在 run 的程序

比如要设置 ctrl + h 来进行字符的删除

stty erase ^h
# 默认可以看到使用 ^? 但是实际测试的时候,改不回去了
1
2
错误操作问题:在 windows 下 ctrl + s 是保存功能,在 Linux 使用 vim 时,使用 ctrl + s 整个画面死锁,不能动了,是什么原因?

通过 stty -a 可以看到 ctrl + s 是 stop 功能,停止目前屏幕的输出了,恢复输出的话就是 start,ctrl + q
1
2
3

除了 stty 之外,bash 还有自己的一些终端机设置

set [-uvCHhmBx]
1

选项与参数:

  • u:预设不启用。若启用后,当使用未设置变量时,会显示错误信息
  • v:预设不启用。若启用后,在信息被输出前,会先显示信息的原始内容
  • x:预设不启用。若启用后,在指令被执行前,会显示指令内容(前面有 ++ 符号)
  • h:预设启用。与历史命令有关
  • H:预设启用。与历史命令有关
  • m:预设启用。与工作管理有关
  • B:预设启用。与括号[] 的作用有关
  • C:预设不启用。若使用 > 等,则若文件存在时,该文件不会被覆盖
# 范例 1: 显示目前所有的 set 设置
[mrcode@study ~]$ echo $-
himBH

# 范例 2:若使用未定义变量时,则显示错误信息
[mrcode@study ~]$ set -u  
[mrcode@study ~]$ echo $mrcode   
-bash: mrcode: unbound variable
[mrcode@study ~]$ set +u		# 关闭该功能使用 + 号
[mrcode@study ~]$ echo $mrcode

[mrcode@study ~]$ 

# 范例 3:执行前,显示该指令内容
[mrcode@study ~]$ set -x
++ printf '\033]0;%s@%s:%s\007' mrcode study '~'
[mrcode@study ~]$ echo ${HOME}
+ echo /home/mrcode
/home/mrcode
++ printf '\033]0;%s@%s:%s\007' mrcode study '~'

#要输出的指令都会被先打印到屏幕上,前面会多出 + 号
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

另外,还有其他的按键设置功能,前一小节提到的 /etc/inputrc 这个文件里面设置。还有例如 /etc/DIR_COLORS* 与 /usr/share/terminfo/* 等,也都是与终端机有关的环境配置文件。但是这里不建议修改 tty 的环境,因为 bash 的环境以及设置的很亲和了。

bash 默认的组合键汇总如下

组合按键 功能
ctrl + c 终止目前的命令
ctrl + D 输入结束(EOF),例如邮件结束的时候
ctrl + M Enter
ctrl + S 暂停屏幕的输出
ctrl + Q 恢复屏幕的输出
ctrl + U 在提示字符下,将整列命令删除
ctrl + Z 暂停 目前的命令

# 通配符与特殊符号

在 bash 操作环境中,通配符(wildcard)是非常有用的,利用 bash 处理数据就更方便了。下面是一些常用的通配符:

符号 含义
* 代表「0 个到无穷多个」任意字符
? 代表「一定有一个」任意字符
[] 代表「一定由一个在括号内」的字符(非任意字符)。例如[abcd] 则表示一定由一个字符,可能是 a、b、c、d 中的任意一个
[-] 若有减号在括号中时,表示「在编码顺序内的所有字符」。例如[0-9],表示 0~9 之前所有数字
[^] 若括号中的第一个字符为指数符号 ^,表示反向旋转,例如[^abc],表示不包含 a、b、c

实践练习

# 范例 1:找出 /etc 下一 cron 开头的文件名
[mrcode@study ~]$ ll -d /etc/cron*    # -d 仅显示目录
drwxr-xr-x. 2 root root  54 Oct  4 18:25 /etc/cron.d
drwxr-xr-x. 2 root root  57 Oct  4 18:25 /etc/cron.daily

# 范例 2:找出 etc 下刚好是 5 个字母的目录名
[mrcode@study ~]$ ll -d /etc/?????
drwxr-x---. 3 root root   83 Oct  4 18:38 /etc/audit
drwxr-xr-x. 4 root root   71 Oct  4 18:25 /etc/avahi

# 范例 3:找出 etc 下目录名含有数字的目录
[mrcode@study ~]$ ll -d /etc/*[0-9]*    # 记得通过 ** 来模糊匹配
drwxr-xr-x. 4 root root   78 Oct  4 18:22 /etc/dbus-1
-rw-r--r--. 1 root root 5725 Aug  6 21:44 /etc/DIR_COLORS.256color

# 范例 4:找出 etc 下,目录名开头不是小写的目录
[mrcode@study ~]$ ll -d /etc/[^a-z]*
ls: cannot access /etc/[^a-z]*: No such file or directory
# 看到没有找到不是小写的目录,换成非大写的,出来结果了
[mrcode@study ~]$ ll -d /etc/[^A-Z]*
drwxr-xr-x. 3 root root   101 Oct  4 18:23 /etc/abrt
-rw-r--r--. 1 root root    16 Oct  4 18:31 /etc/adjtime

# 范例 5:将范例 4 中找到的文件复制到 /tmp/upper 中
[mrcode@study ~]$ mkdir /tmp/upper; cp -a /etc/[^a-z]* /tmp/upper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

除了通配符外,bash 环境中的特殊符号还有以下项,这里进行整理:

符号 含义
# 批注、注释符号
\ 跳脱符号、转义符号
| 管线 pipe:分割两个管线命令的节点(后续介绍)
; 连续指令下达分隔符:连续性命令的节点。与管线命令不相同
~ 用户的家目录
$ 取用变量前导符
& 工作控制(job control):将指令变成背景下工作
! 逻辑运算意义上的「非」not 的意思
/ 目录符号:路径分割的符号
>>> 数据流重导向:输出导向,分别是「覆盖」和「追加」
<<< 数据流重导想:输入导向(下个章节讲解)
'' 单引号,不具有变量替换功能,$ 变为纯文本
"" 双引号,具有变量替换功能,$ 可保留相关功能
`` 两个 「`」中间为可以先执行的指令,也可以使用 $()
() 在中间为 子 shell 的起始与结束
{} 在中间为命令区块的组合

以上是 bash 环境中常见的特殊符号整理,理论上,文件名尽量不要使用上述字符