shell 是什么
shell 的功能只是提供用户操作系统的一个接口,它可以将我们输入的指令与 kernel 沟通,好让 kernel 可以控制硬件来正确无误的工作。比如,我们使用 shell 程序可以操作像 man、chmod、chown、vi 等独立的应用程序,让这些应用程序来调用 kernel 来运行所需的工作。
其实,只要能够操作应用程序的接口都能够称为 shell。狭义的 shell 指的是命令行方面的软件,比如 bash;而广义上的 shell 则包括了图形接口的软件,因为图形接口也可以操作各种应用程序来调用 kernel 来工作。不过,一般看到的 shell 都是狭义方面的。
配置 shell
常见的 shell
目前 Linux 可以使用的 shell,我们可以查看 /etc/shells 这个文件,至少就有以下这几个 shell 可以使用。虽然各种 shell 的功能都差不多,但是在某些语法的下达上会有所不同。
- /bin/sh (已经被 /bin/bash 所取代)
- /bin/bash (就是 Linux 默认的 shell)
- /bin/tcsh (整合 C Shell ,提供更多的功能)
- /bin/csh (已经被 /bin/tcsh 所取代)
第一个流行的 shell 是由 Steven Bourne 发展出来的,为了纪念他所以就称为 Bourne shell,或直接简称为 sh;
而后来另一个广为流传的 shell 是由柏克莱大学的 Bill Joy 设计依附于 BSD 版的 Unix 系统中的 shell,这个 shell 的语法有点类似 C 语言,所以又叫做 C shell ,简称为 csh !由于在学术界 Sun 主机势力相当的庞大,而 Sun 主要是 BSD 的分支之一,所以 C shell 也是另一个很重要而且流传很广的 shell 之一 。
为什么系统上合法的 shell 要写入 /etc/shell 这个文件?这是因为系统某些服务在运行过程中,会去检查使用者能够使用的 shells,而这些 shell 的查询就是借由了 /etc/shells 这个文件。比如,某些 FTP 网站会去检查使用者的可用 shell,而如果你不想要让这些使用者使用 FTP 以外的主机资源时,可以给予该使用者一些怪怪的 shell,从而让使用者无法以其他服务登陆主机。举例来说,CentOS 7.x 的 /etc/shells 里头就有个 /sbin/nologin 文件的存在,当指定了这个 shell 之后,那么使用者就无法以其他服务来登陆主机了。
选择 shell
当登陆 linux 的时候,系统会给我一个 shell 来让我工作,而这个登陆取得的 shell 就记录在 /etc/passwd 这个文件内。
$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
......
如上所示,每一行的最后一个数据就是用户登录之后的可以取得的默认 shell。比如,当你使用 root 用户登录时,默认取得的就是 /bin/bash 这个 shell。当使用系统账号 bin 或者 daemon 登录的时候,则是使用 /sbin/nologin 这个怪怪的 shell。
Bash 简述
/bin/bash 是 Linux 默认的 shell,是 GNU 计划中重要的工具软件之一,也是 Linux distribution 的标准 shell。bash 主要相容于 sh,并且依据一些使用者需求而加强的 shell 版本。bash 主要的优点有下面几个:
-
上下键切换就可以找到前/后一个输入的指令,同时能记住使用过的指令(默认的指令记忆功能可以到达 1000 个)。执行过的指令记录位于用户主目录中的
.bash_history
,不过.bash+history
中记录的是前一次登陆以前所执行过的指令,而这一次登陆执行的指令被暂存在内存中,当退出系统后,这些暂存的指令才会被记录到.bash_history
中。 -
命令与文件补全,也就是 Tab 按键的补全功能。
安装 bash-completion 软件则可以在某些指令后面使用 tab 键时,可以进行“选项/参数的补齐”功能。
-
命名别名设置功能(alias)。
-
任务的前台、后台控制。
-
程序化脚本(shell script)。
-
万用字符(wildcard)。
Bash 的操作环境
指令搜索顺序
系统里面其实有不少的指令,比如通过 alias 声明的指令、内置的指令等等。那么,当一个指令被下达之后,指令搜索的顺序如下:
- 以相对/绝对路径方式执行的指令,如 “/bin/ls” 或 “./ls”;
- 由 alias 方式声明的指令;
- 由 bash 内置的指令;
- 通过 PATH 这个变量的顺序搜索找到的第一个指令;
比如,当我们使用 /bin/ls 方式执行的时候,那么执行就是的 /bin 这个目录中的 ls。当我们使用 ls 的时候,那么会先看看 alias 中有没有设置 ls 这个别名,假如没有那么看看是不是 bash 内置的命令,假如也没有则再去 PATH 这个变量设置的路径中依次查找。
上面这个顺序,我们还可以通过 type -a ls
查询得到,从上到下的顺序就是上述的顺序。
root@dawnL~# alias echo='echo -n'
root@dawn:~# type -a echo
echo is aliased to `echo -n'
echo is a shell builtin
echo is /bin/echo
bash 的环境配置文件
系统中有一些环境配置文件的存在,bash 启动的时候会直接读取这些配置文件,以规划好 bash 的操作环境。 这些配置文件又可以分为系统的配置文件以及使用者个人偏好配置文件,在以 login 或 non-login 的方式启动 bash 的时候这两种方式会读取不同的文件。
login 与 non-login shell
login 和 non-login 这两种方式在启动 bash 的时候,读取的配置文件是不同的。而这两种方式的区别在于有没有 login。
-
login shell:取得 bash 前需要完整的登陆流程,就称为 login shell。比如,你要由 tty1 ~ tty6 登陆,需要输入使用者的账号和密码,那么此时取得的 bash 就是 login shell。
login shel 会读取以下两个配置文件:
- /etc/profile :系统整体配置文件(最好不要修改这个文件)
- ~/.bash_profile 或 ~/.bash_login 或 ~/.profile 文件:使用者个人配置文件,需要修改的话,可以修改这个文件
-
non-login shell:取得 bash 的时候不需要重复登录。比如,以 X window 登录 Linux 之后再以图形化方式启动终端机,此时那个终端机并没有要求再次输入账号与密码。这个 bash 的启动就是 non-login。或者,在启动的 bash 中再次输入 bash 这个命令来启动一个 bash,此时也不要求输入账号密码,那么再次输入 bash 之后启动的那个 bash 就是 non-login。
/etc/profile
这个配置文件是系统整体配置文件,系统上的使用者以 login 方式启动 bash 时就会加载这个文件,所以最好不要修改这个文件。但是,假如想要帮所有使用者设置整体的 bash 环境,那么就可以改这里。
/etc/profile 中还会调用另外的配置文件,比如 Ubuntu 16.04 中会
- 调用 /etc/profile.d/*.sh 文件,其实就是调用 /etc/profile.d/ 这个目录中扩展名为 .sh 的文件。但是,需要注意的,只有使用者具有 r 的权限,那么相应的文件才会被 /etc/profile 调用起来。/etc/profile.d 这个目录中的 sh 文件的内容会根据 distribution 的不同而会有所差异,具体的内容可以查看相应的文件。比如,Ubuntu 16.04 中就会对 PATH 进行设置、tab 键的自动补齐功能等。
需要记住的是:bash 的 login shell 情况下所读取的整体环境配置文件其实只有 /etc/profile,但是这个文件还会调用出其他的配置文件。如果有一个软件包,系统上只安装一份,供所有开发者使用,建议在/etc/profile.d下创建一个新的xxx.sh,配置环境变量。
~./bash_profile
bash 在读取完整体环境设置的 /etc/profile 文件之后,接下来则会读取使用者的个人配置文件。在 login shell 的 bash 环境中,所读取的个人偏好配置文件其实主要有三个,依序是:
- ~/.bash_profile
- ~/.bash_login
- ~/.profile
其实 bash 的 login shell 设置只会读取上面三个文件的其中一个,但是读取顺序是按照上面的顺序。也就是说,如果 ~/.bash_profile 存在,那么其他两个文件无论存不存在都不会被读取;如果 ~/.bash_profile 不存在,才会去读取 ~/.bash_login;如果前两个文件都不存在,才会去读取 ~/.profile。需要那么多文件,其实是因应其他 shell 转换过来的使用者的使用习惯而已。
在 Ubuntu 16.04 中,root 用户存在的是 ~/.profile 这个文件。这个文件的内容如下所示,我们可以看到这个文件又会去加载 ~/.bashrc 这个配置文件,也就是说 ~/.profile 这个文件又会再调用 ~/.bashrc 这个文件的内容来对 bash 进行设置。
# ~/.profile: executed by Bourne-compatible login shells.
if [ "$BASH" ]; then
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
fi
tty -s && mesg n || true
/etc/profile 和 ~/.profile 这两个文件都是在取得 login shell 的时候才会读取的配置文件,假如将自己的偏好配置写入上述的两个文件后,需要先登出再登录之后,这设置才会生效。那么,我们可以利用 source 这个指令(或者小数点)来让配置生效哦,就是
source 相关配置文件
即可。
~/.bashrc
~/.bashrc 是在 login shell 的时候会被加载的个人配置文件。其实 non-login shell 的时候,也会读取这个文件,而且是仅读取这个文件。如果一个服务器多个开发者使用,大家都需要有自己的sdk安装和设置,那么最好就是设置它。
上述的整个加载过程如下图所示(以 centos 为例),其中实线的方向是主线流程,虚线的方向则是被调用的配置文件。
其他配置文件
- ~/.bash_logout 这个文件记录了当我登出 bash 之后,系统需要再帮我做哪些事情才离开。默认情况下,bash 只是帮我们清理掉屏幕的消息而已。
万用字符与特殊符号
在 bash 的操作环境中还支持万用字符,如下所示。
符号 | 意义 |
---|---|
* | 代表“ 0 个到无穷多个”任意字符 |
? | 代表“一定有一个”任意字符 |
[ ] | 同样代表“一定有一个在括号内”的字符(非任意字符)。例如 [abcd] 代表“一定有一个字符, 可能是 a, b, c, d 这四个任何一个” |
[ - ] | 若有减号在中括号内时,代表“在编码顺序内的所有字符”。例如 [0-9] 代表 0 到 9 之间的所有数字,因为数字的语系编码是连续的! |
[^] | 若中括号内的第一个字符为指数符号 (^) ,那表示“反向选择”,例如 [^abc] 代表 一定有一个字符,只要是非 a, b, c 的其他字符就接受的意思。 |
除了万用字符之外,bash 环境中的特殊字符还有下面这些。因此,你的“文件名”尽量不要使用到下面这些字符。
符号 | 内容 |
---|---|
# | 注解符号:这个最常被使用在 script 当中,视为说明!在后的数据均不执行 |
\ | 跳脱符号:将“特殊字符或万用字符”还原成一般字符 |
| | 管线 (pipe):分隔两个管线命令的界定(后两节介绍); |
; | 连续指令下达分隔符号:连续性命令的界定 (注意!与管线命令并不相同) |
~ | 使用者的主文件夹 |
$ | 取用变量前置字符:亦即是变量之前需要加的变量取代值 |
& | 工作控制 (job control):将指令变成背景下工作 |
! | 逻辑运算意义上的“非” not 的意思! |
/ | 目录符号:路径分隔的符号 |
>, >> | 数据流重导向:输出导向,分别是“取代”与“累加” |
<, << | 数据流重导向:输入导向 (这两个留待下节介绍) |
' ' | 单引号,不具有变量置换的功能 ($ 变为纯文本) |
" " | 具有变量置换的功能! ($ 可保留相关功能) |
反引号 | 两个“ ` ”中间为可以先执行的指令,亦可使用 $( ) |
( ) | 在中间为子 shell 的起始与结束 |
{ } | 在中间为命令区块的组合! |
终端机的环境设置:stty、set
终端机的环境设置是指终端机登陆之后的一些字符设置的功能,比如利用 backspace 这个键来删除命令列上的字符;利用 [ctrl]+c 来强制终止一个指令的运行等等。
那么如何查阅目前的一些按键内容呢?我们可以使用 stty(setting tty)指令来查阅,如下所示。如果出现 表示 [Ctrl] 那个按键的意思。比如,intr = C 表示利用 [ctrl] + c 来达成的。
- intr : 送出一个 interrupt (中断) 的讯号给目前正在 run 的程序 (就是终止!);
- quit : 送出一个 quit 的讯号给目前正在 run 的程序;
- erase : 向后删除字符,
- kill : 删除在目前命令行上的所有文字;
- eof : End of file 的意思,代表“结束输入”。
- start : 在某个程序停止后,重新启动他的 output
- stop : 停止目前屏幕的输出;
- susp : 送出一个 terminal stop 的讯号给正在 run 的程序。
$ stty -a
speed 38400 baud; rows 32; columns 105; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = M-^?; eol2 = M-^?; swtch = <undef>;
start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; discard = ^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 -flusho
-extproc
那么如何修改这些按键内容呢?我们可以使用 stty 指令来修改按键内容,比如想要用 [ctrl]+h 来进行字符的删除,那么可以使用 stty erase ^h
进行设置。此时,按下 backspace 这个键则会出现 ^? 字样,想要恢复回使用 bashspace 键进行删除的话,那么则使用 stty erase ^?
进行设置。
这里不建议修改 tty 的环境,因为 bash 的环境已经设置的很友好了,不需要额外的设置或者修改。另外,将 bash 默认的组合键完整的汇总一下:
组合按键 | 执行结果 |
---|---|
Ctrl + C | 终止目前的命令 |
Ctrl + D | 输入结束 (EOF),例如邮件结束的时候; |
Ctrl + M | 就是 Enter 啦! |
Ctrl + S | 暂停屏幕的输出 |
Ctrl + Q | 恢复屏幕的输出 |
Ctrl + U | 在提示字符下,将整列命令删除 |
Ctrl + Z | “暂停”目前的命令 |
除了 stty 之外,bash 还有自己的一些终端机设置值,这些终端设置值需要利用 set 来进行设置。set 除了设置变量之外,还可以设置整个指令输入/输出的环境,比如记录历史命令、显示错误内容等。
set [-uvCHhmBx]
-u :默认不启用。若启用后,当使用未设置变量时,会显示错误讯息;
-v :默认不启用。若启用后,在讯息被输出前,会先显示讯息的原始内容;
-x :默认不启用。若启用后,在指令被执行前,会显示指令内容(前面有 ++ 符号)
-h :默认启用。与历史命令有关;
-H :默认启用。与历史命令有关;
-m :默认启用。与工作管理有关;
-B :默认启用。与刮号 [] 的作用有关;
-C :默认不启用。若使用 > 等,则若文件存在时,该文件不会被覆盖。
# 显示目前所有的 set 设置值,$- 变量的内容就是 set 的所有设置了,bash 默认是 himBH
$ echo $-
himBH
# 设置“若使用未定义变量时,则显示错误讯息”。默认情况下,未设置/未声明的变量都将是“空的”,假如没有设置 -u 参数,是不会报错的。但是,显示启用 -u 参数之后就会报错了,这其实挺有用的。
$ set -u
# 取消这个参数,- 改成 + 即可
$ set +u
bash 的进站与欢迎讯息
/etc/issue 和 /etc/issue.net
/etc/issue 这个文件设置的将是在终端机接口(tty1~tty6)登陆时会显示的信息。比如,我的 Ubuntu 16.04 中的内容就如下所示,
Ubuntu 16.04.6 LTS \n \l
issue 文件的内容也是可以使用反斜线作为变量取用的,你可以 man issu 配合 man agetty 得到下面这样的结果:
issue 内的各代码意义 |
---|
\d 本地端时间的日期; |
\l 显示第几个终端机接口; |
\m 显示硬件的等级 (i386/i486/i586/i686...); |
\n 显示主机的网络名称; |
\O 显示 domain name; |
\r 操作系统的版本 (相当于 uname -r) |
\t 显示本地端时间的时间; |
\S 操作系统的名称; |
\v 操作系统的版本。 |
/etc/issue.net 这个文件则是提供给 telnet 这个远端登陆程序用的。当我们使用 telnet 连接主机时,主机的登陆画面就会显示 /etc/issue.net 而不是 /etc/issue。
/etc/motd
如果想要让使用者登陆后取得一些信息,那么这些信息可以加入到 /etc/motd 中去。在 Ubuntu 16.04 中,进站显示信息的配置文件还存在于 /etc/update-motd.d/ 这个目录中。
Bash 使用和常用指令
反斜线
如果指令串太长的话,可以使用反斜线来进行两行输入,如下所示。输入时需要注意的是\
后面是紧接着的 [Enter] 键的,这两者中间是没有空格的(其实就是使用 \
来对 [Enter] 进行转义,让 [Enter] 不再具有开始执行的功能,好让执行继续在下一行输入)。当顺利对 [Enter] 进行转换之后,下一行最前面就会主动出现 >
符号,表示可以继续输入指令。
$ cp /var/spool/mail/root /etc/crontab \
> /etc/fstab /root
快速编辑按钮
当下达的指令特别长,或者输入了一串错误的指令时,想要快速地将这串指令删掉,那么除了的 delete 键之外,还可以使用以下这些快捷键。
组合键 | 功能 |
---|---|
[ctrl]+u/[ctrl]+k | 分别是从光标处向前删除指令串 ([ctrl]+u) 及向后删除指令串 ([ctrl]+k)。 |
[ctrl]+a/[ctrl]+e | 分别是让光标移动到整个指令串的最前面 ([ctrl]+a) 或最后面 ([ctrl]+e) |
环境变量相关命令
echo
export
set
unset
env
type
为了方便 shell 的操作,bash 已经“内置”了很多指令,比如 cd、umask。
type:查看某个指令(不是一般文件)是来自外部指令还是内置在 bash 中的
type [-tpa] name
:不加任何选项与参数时,type 会显示出 name 是外部指令还是 bash 内置指令
-t:当加入 -t 参数时,type 会将 name 以下面这些字眼显示出他的意义:
file :表示为外部指令;
alias :表示该指令为命令别名所设置的名称;
builtin :表示该指令为 bash 内置的指令功能;
-p:如果后面接的 name 为外部指令时,才会显示完整文件名;
-a:会将包含 alias 的情况依次列出来,顺序依次为:alias 设置了的指令;内置的指令;PATH 变量的路径中包含的指令
history
history:获取你在终端输入的命令历史(内存中的内容)
- 当我们以 bash 登陆系统之后,系统会主动的从主目录中的 ./bash_history 中文件读取之前曾下过的命令,放到缓存中。新输入的命令,都将先缓存在内存中。history 显示的其实是内存中的内容。
- ./bash_history 这个文件中会记录的数据量跟 HISTFILESIZE/HISTSIZE 这个变量设置值有关(实测发现跟这两个变量都有关系)。在我登出之后,那么会将内存中最近的 HISTFILESIZE 条记录写入到文件中,默认是 ./bash_history 这个文件。当然可以使用 history -w 在不登出之前就将内存中的内容写入文件中。
- 当以 root 用户开了好几个 bash,由于每个 bash 在登陆之后都会有自己的 1000 条记录在缓存中,等登出之后才会将这些记录写入到文件中,所以后退出的 bash 中的记录会把之前退出的 bash 写入的记录给覆盖掉,那么因此最后登出的 bash 使用过的命令才会是最后写入的数据。
n:数字,列出最近输入的 n 条命令(内存中的内容)。
-c:将目前 shell 中的所有 history 内容清除(内存中的内容)。
-a:将内存中新增的 history 指令添加到 histfiles 中,若没有加 histfiles,则默认写入 ~/.bash_history。
-r:将 histfiles 的内容读到目前这个 shell 的 history 内存中。
-w:将目前的 history 内存内容写入 histfiles 中。
# 列出目前最近的 3 条数据
$ history 3
$ echo ${HISTSIZE}
1000
!:利用这个来执行 history 中的命令
!number :执行第 number 条指令的意思;
!command :由最近的指令向前搜寻“指令串开头为 command”的那个指令,并执行;
!! :就是执行上一个指令(相当于按↑按键后,按 Enter)
# 执行第 66 条指令
$ !66
# 执行上一条指令
$ !!
# 执行最近的以 al 开头的指令
$ !al
alias
alias:给命令指定一个别名,主要用在命令比较长的时候。格式为 {别名='指令 选项...' },那么输入了别名就相当于输入了“指令 选项”,别名可以是已经存在的命令。
unalias:撤销某个别名
alias [-L]
无任何参数:显示别名的设置情况
-L:更详细的显示别名设置的情况
# 显示目前有哪些的命令别名
alias
# 将 ls -al | more 重命名为 lm,那么之后只需要输入 lm 就相当于输入老人
alias lm='ls -al | more'
# 撤销 lm 这个命名
unalias lm
# 设置 rm 为 rm -i(这是已经存在的命令的情况)
alias rm='rm -i'