[TOC]
——主要来自《Red Hat Linux指南:基础与系统管理篇》《Linux命令行与shell脚本编程大全.第3版》

1、shell

要点:命令行、标准输入和标准输出、重定向、管道、后台运行程序、kill:终止后台作业、文件名生成/路径名展开、内置命令
Shell是用户的系统界面,提供了用户与内核进行交互操作的一种接口。它接收用户输入的命令并把它送去内核执行。实际上Shell是一个命令解释器,它解释由用户输入的命令并且把它们送到内核。

1.1 命令行

当在命令提示符后键入命令回车,shell将执行相应的程序。比如,键入ls后回车,shell开始执行名为ls的工具。也可让shell以同样的方式执行其他类型的程序,如shell脚本、应用程序或自己编写的程序。包含命令和参数的行称为命令行。命令是指在命令行上键入的字符,同时还指对应动作所调用的程序。

1.1.1 语法

命令行语法说明了行中各个元素的排列顺序和间隔方式。当用户键入命令回车后,shell将扫描命令行进行语法检查。命令行上基本的语法格式如下:
command [arg1] ... [argn] RETURN
命令行上采用一个或多个空格来隔开每个元素。其中,command为命令的名字,arg1到argn为命令的参数,回车是终止命令的按键。语法格式中的方括号表明被括起来的参数为可选项。并不是所有的命令都需要参数,有些命令就没有参数,有些命令需要可变数目的参数,有些命令则需要特定数目的参数。选项是一种特殊类型的参数,前面通常为一个或两个连字符(“-”或“--”)。

1.命令名

一些有用的Linux命令行仅由命令名组成而不带任何参数。例如,不带任何参数的ls将显示工作目录下的文件列表。多数命令都带一个或多个参数,当使用需要带参数的命令时,若没有带参数,或带了不正确的参数,或参数数目使用错误,系统都会返回用户简短错误信息提示,这些信息称为命令的用法消息(usage message)。

2.参数

命令行上,每一串不含空格字符的字符序列称为记号或字。参数是一种记号,如文件名、文本串、数字或命令处理的其他对象。
下面展示一个cp的命令行:

[root@QUFENGBIN ~]# cp temp tempcopy
参数都有编号,其中命令本身作为参数0,它是命令行参数的开始。在这个例子中,cp为参数0,temp为参数1,tempcopy为参数2。cp至少需要两个字段(还可带多个参数,但不能少于两个),参数1是已存在的文件名,参数2是cp要创建或重写的文件。这两个参数都不是可选的,而是命令运行所必需的。
PS:shell一些关于参数的特殊字符
特殊字符 说明
$$ Shell本身的PID(ProcessID)
$! Shell最后运行的后台Process的PID
$? 最后运行的命令的结束代码(返回值)
$- 使用Set命令设定的Flag一览
$* 所有参数列表。如"$*"用「"」括起来的情况、以"$1 $2 … $n"的形式输出所有参数。
$@ 所有参数列表。如"$@"用「"」括起来的情况、以"$1" "$2" … "$n" 的形式输出所有参数。
$# 添加到Shell的参数个数
$0 Shell本身的文件名
$1~$n 添加到Shell的各参数值。$1是第1参数、$2是第2参数…。
[root@aminglinux_01 test]# cat test.sh
#!/bin/sh
echo "number:$#"
echo "scname:$0"
echo "first :$1"
echo "second:$2"
echo "argume:$@"
[root@aminglinux_01 test]# sh test.sh aa bb
number:2
scname:test.sh
first :aa
second:bb
argume:aa bb

3.选项

选项(option)是改变命令执行效果的参数。可通过指定多个选项使得命令按照不同的方式执行。选项与特定的程序相关,并由命令行上调用的程序解释,而非由shell解释。
按照约定,选项是跟在命令之后其他参数(如文件名)之前的单独的参数。多数命令的选项前面需要加一个连字符,但这个要求是与工具相关的,而与shell无关。GNU程序的选项前通常带两个连字符。例如,--help会生成用法消息。

4.选项的合并

当需要多个选项时,可将多个单字符选项组合成一个参数,以一个连字符开始,在选项前不要加空格。但是,这样合并后的选项之前不能使用两个连字符。对于合并选项的具体规则与具体的运行程序有关。多数命令的选项不分前后顺序。

5.选项的参数

有些工具的选项本身也要带参数。如,gcc(GUN的c编译器)的-o选项必须后跟gcc产生的可执行文件名,通常选项与其参数间用空格隔开,如下:

[root@QUFENGBIN ~]# gcc -o prog prog.c

6.以连字符开始的参数

按照约定,工具的参数(如文件名)是允许以连字符开始的。这样当某个文件的名字为-l时,命令的意义将不明确。如果创建了这类文件,那么,一些命令约定使用--(两个连续的连字符)参数来表示选项的结束(和参数的开始)。如下示例:

[root@QUFENGBIN test]# touch -l
touch: invalid option -- 'l'
Try 'touch --help' for more information.
[root@QUFENGBIN test]# touch -- -l
[root@QUFENGBIN test]# ls -l
total 4
-rw-r--r-- 1 root root  0 Apr 28 11:41 -l
-rw-r--r-- 1 root root 14 Apr 26 16:26 test01
[root@QUFENGBIN test]# ls -- -l
-l
[root@QUFENGBIN test]# ls -l -- -l
-rw-r--r-- 1 root root 0 Apr 28 11:41 -l

1.1.2 处理命令行

当向命令行键入命令时,Linux的tty设备驱动程序(Linux操作系统内核的一部分)将检查每个字符,来确定是否要立即采取动作。当键入的字符不需要采取立即的动作时,设备驱动程序将把字符存储在缓冲区中,等待字符输入。当按下回车键后,设备驱动程序将把命令行传递过程shell处理。

1.分析命令行

当shell处理命令行时,它将把命令行作为一个整体来对待,并将其分成几个组成部分。接着,shell将查找命令的名称。命令行中提示符后的第1项(即参数0)通常为命令名,因此shell将把命令行中从第1个字符到第一个空白字符(TAB或空格)之间的字符串作为命令名。命令名(第1个记号)可采用简单文件名或路径名的方式指定。例如:

[root@QUFENGBIN test]# ls
-l  test01
[root@QUFENGBIN test]# /bin/ls
-l  test01

2.绝对路径名与相对路径名

当在命令行上输入绝对路径名或非简单文件名的相对路径名时(即输入至少包含一条斜杠的路径名),shell将在指定目录下查找具有执行权限的对应文件。例如,输入命令/bin/ls,shell将查找/bin目录下具有执行权限且名为ls的文件。当输入的是一个简单文件名时,shell在一组目录中查找与该文件名匹配且具有执行权限的对应文件。shell并不是在所有目录下搜索,而只在PATH变量设定的路径下搜索。

1.1.3 执行命令行

1.进程

如果shell找到了与命令行上的命令具有相同名字的可执行文件,那么,shell将启动一个新的进程(进程是指Linux命令的执行),并将命令行上的命令名、参数、选项传递给程序(可执行文件)。当命令执行时,shell将等待进程的结束,这时shell处于非活跃状态,称为休眠状态。当程序执行完毕,就将它的退出状态传递给shell,这样shell就返回到活跃状态(被唤醒),显示提示符,等待下一个命令的输入。

2.shell不处理的参数

由于shell不处理命令行上的参数,只是将它们传递给调用的程序,所以shell不知道选项和参数是否对程序有效。所以关于选项和参数的错误消息和用法消息都来自程序自身。也有些命令会忽略无效的选项。

1.2 标准输入输出

标准输出(standard output)是指程序输出信息(如文本)的地方。程序从来都不“知道”它发送到标准输出的信息究竟送往何处。这些信息可以输出到打印机、普通文件或屏幕。默认情况下,shell把命令的结果标准输出到屏幕。shell也可以将输出重定向到其他文件。
标准输入(standard input)是程序信息的来源。而对于标准输出,程序从不“知晓”信息的来源。默认情况下,程序的输入来自键盘输入。
对一个运行的程序来说,除了具有标准输入和标准输出外,通常还有错误消息输出,称为标准错误输出(standard error)。
命令并不知道标准输入来自哪,也不知道标准输出和标准错误输出到哪。

1.2.1 作为文件的屏幕

除了普通文件、目录文件、硬链接和软链接之外,Linux还有一种文件类型:设备文件(device file)。设备文件驻留在Linux文件结构中(通常位于目录/dev中),用来代表外围设备,如终端模拟器、显示屏、打印机和硬盘驱动器。
在who工具显示的内容中,登录名后的设备名即为屏幕的文件名。如果打开了多个窗口,每个打开的窗口都有对应的设备名。在这些窗口中运行tty工具即可得到它们各自的名称。可以把这个设备文件看作是一个文本文件进行读写。向该文件写入会在屏幕上显示写入的内容,而从该文件读取就是从键盘上读取键入的内容。

[root@QUFENGBIN test]# tty
/dev/pts/0
[root@QUFENGBIN test]# who
root     tty1         2018-04-17 15:44
root     pts/0        2018-04-28 11:34 (111.113.5.18)

chsh:改变登录shell
系统管理员在建立用户账户时,将确定用户第一次登录系统或打开GUI环境下终端模拟器窗口时使用的shell(见/etc/passwd文件)。

root:x:0:0:root:/root:/bin/bash
.... .....
mysql:x:1000:1000::/home/mysql:/bin/false
www:x:1001:1001::/home/www:/bin/false
apache:x:48:48:Apache:/usr/share/httpd:/sbin/nologin

但在登录系统后,用户可以自己决定运行哪个shell。键入要使用的shell名(如bash,tcsh,或另一个shell),然后回车,则出现的提示符即为新设定shell给出的,输入exit命令可退回到上一个shell。
使用工具chsh可以永久地修改登录shell。首先输入命令chsh,然后在提示符后输入口令和要使用的shell的绝对路径名(如/bin/bash或者/bin/tcsh,或者另一个shell的路径名)。

1.2.2 作为标准输入的键盘和作为标准输出的屏幕

当第一次登录时,shell将其标准输出发送到代表屏幕的设备文件中,采用这种方式输出可以把输出内容在屏幕上显示出来。shell还将代表键盘的设备文件作为标准输入的来源,这样命令会把在键盘上键入的任何内容作为输入接收。

1.2.3 重定向

重定向(redirection)是指改变shell标准输入来源和标准输出去向的各种方式。例如,默认情况下,shell将cat的标准输入关联到键盘,标准输出关联到屏幕。但也可以让shell重定向任何命令的标准输入或标注输出,方法就是将输入或输出与某命令或某文件关联,而不再是与代表键盘或屏幕的设备文件进行关联。

1.重定向标准输出

通过重定向符号(>)可以将shell命令的输出重定向到指定的文件而不再是屏幕。重定向的命令格式为:

command [arguments] >filename 

其中,command为可执行程序(如应用程序或者是工具),arguments是可选参数,filename是shell要重定向输出到的普通文件名。
重定向可能覆盖文件!在重定向命令执行前,如果该文件已经存在,那么shell将重写并覆盖其原来的内容。

2.重定向标准输入

与重定向标准输出一样,也可以重定向标准输入。通过重定向标准输入符号(<)可以使shell将命令的输入重定向为来自指定的文件而不再是键盘。重定向标准输入的命令格式为:

command [arguments] 

其中,command为可执行程序(如应用程序或者是工具),arguments是可选参数,filename是shell要重定向输入来自的普通文件名。
将文件或者标准输入作为输入的工具:将命令cat的输入重定向到文件的执行结果与命令cat后跟文件名作为参数的执行结果相同。像cat这样具有这种特性的工具属于Linux中的一类工具,这类工具还包括lpr、sort和grep。这类工具首先检测调用它们的命令行。如果命令行上存在文件名,那么这类工具就把指定的文件作为输入;否则,如果命令行上没有指定文件名,那么这类工具就把标准输入作为输入。这种功能特性是该类工具自身所具有的,而与shell或者是操作系统无关。

3.noclobber:避免文件的重写

shell提供了一种称为noclobber的特性,该特性可防止重定向时不经意地重写了已存在的文件。

[root@QUFENGBIN test]# set -o noclobber
[root@QUFENGBIN test]# echo "noclobber test" > test01
-bash: test01: cannot overwrite existing file
[root@QUFENGBIN test]# set +o noclobber
[root@QUFENGBIN test]# echo "noclobber test" > test01
[root@QUFENGBIN test]# cat test01
noclobber test

在重定向输出符号后跟管道符号,即使用符号组合“>|”可以忽略noclobber的设置。

[root@QUFENGBIN test]# set -o noclobber
[root@QUFENGBIN test]# echo "noclobber test" > test01
-bash: test01: cannot overwrite existing file
[root@QUFENGBIN test]# echo "noclobber test" >| test01
[root@QUFENGBIN test]# cat test01
noclobber test
[root@QUFENGBIN test]# set +o noclobber

4.向文件追加标准输出

使用追加输出符号(>>)可以向某个文件末尾添加新的内容,并且不改变原来已有内容。

[root@QUFENGBIN test]# cat test01
noclobber test
[root@QUFENGBIN test]# echo "noclobber test" > test01 ; cat test01
noclobber test
[root@QUFENGBIN test]# echo "noclobber test" >> test01 ; cat test01
noclobber test
noclobber test

5./dev/null:使数据消失

设备/dev/null是一个数据接收器(data sink),通常被称为位桶(bit bucket)。可以将不想看到或者是不想保存的数据重定向到/dev/null,这样数据将不留痕迹的消失。

[root@QUFENGBIN test]# echo "noclobber test" > /dev/null

当从/dev/null中读取数据时,将得到一个空字符串。

[root@QUFENGBIN test]# ls -l test01
-rw-r--r-- 1 root root 30 Apr 28 14:59 test01
[root@QUFENGBIN test]# cat /dev/null > test01
[root@QUFENGBIN test]# ls -l test01
-rw-r--r-- 1 root root 0 Apr 28 15:05 test01

1.2.4 管道

shell使用管道将一个命令的输出直接连接到另一个命令的输入。管道(pipe,有时被称为pipeline)的功能实现类似于下面的过程:首先将一个命令的标准输出重定向到一个文件,然后将该文件作为另一个命令的标准输入。管道不会单独处理每条命令,并且不需要中间文件。管道的符号为一条竖线(|),命令行语法格式为:

command_a [arguments] | command_b [arguments]

上面的命令行得到的结果与下面的这一组命令得到的结果相同:

command_a [arguments] > temp
command_b [arguments] < temp
rm remp

任何Linux工具都可以使用管道从命令行上指定的文件中接受输入,也可以从标准输入接受输入。
有些使用管道的命令仅从标准输入接受输入,如工具tr(transtate,转换)就只能从标准输入接受输入(即tr只能通过标准输入接受输入,而无法通过命令行参数来接受输入)。
使用tr的最简单格式:tr string1 string2
tr工具从标准输入接受输入,查找与string1匹配的字符,找到一个匹配就将string1的字符替换为string2中对应字符。tr工具将它的输出发送到标准输出。

[root@QUFENGBIN test]# tr abc ABC < test01
ABCdefgABCDEFG
[root@QUFENGBIN test]# cat test01 | tr abc ABC
ABCdefgABCDEFG
[root@QUFENGBIN test]# tr abc ABC test01
tr: extra operand ‘test01’
Try 'tr --help' for more information.

1.过滤器
过滤器(filter)是将输入数据流处理后在输出数据流的一类命令。包含过滤器的命令行用一个管道将某个命令的标准输出连接到过滤器的标准输入,用另一个管道将过滤器的标准输出连接到另一个命令的标准输入。并不是所有的工具都可以用作过滤器。
2.tee:向两个方向输入
tee工具将标准输入复制到文件和标准输出。该工具被命名为tee是因为:它只有一个输入,但输出到两个方向。

[root@QUFENGBIN test]# who | tee who.out | grep root
root     tty1         2018-04-17 15:44
root     pts/0        2018-04-28 14:50 (111.113.5.18)
[root@QUFENGBIN test]# cat who.out
root     tty1         2018-04-17 15:44
root     pts/0        2018-04-28 14:50 (111.113.5.18)

1.3 在后台运行程序

前台:当在前台运行命令时,shell将一直等到命令执行完毕。才会给出提示符使得你可继续输入下一个命令。当命令在后台运行时,就不必等待该命令完成,可直接输入另一个命令。
作业:作业(job)是由一个或者(可通过管道连接的)多个命令组成的序列。前台只能有一个作业位于窗口或屏幕中,但可以有多个作业在后台运行。同一时间运行多个作业是Linux的重要特性,这常被称为多任务特性。
作业编号与PID号:如果在命令行的末尾输入与符号(&)后回车,那么shell将在后台运行这个作业。同时,shell会给这个作业分配一个作业编号(是个小数字),并将其显示在方括号内。在作业编号之后,shell将显示进程标识(process identification,PID)号,该号是由操作系统分配的一个大数。每个大数后面都标识了后台运行的一条命令。然后,shell将显示另一个提示符,这是便可以键入另一个命令。当后台作业运行结束时,shell将显示一个消息,这个消息的内容为:已结束作业的作业编号和运行该作业的命令行。

1.将作业从前台移至后台

CONTROL+Z:程序挂起键,shell把前台的作业挂起(阻止其继续运行),并终止作业中的进程,将进程的标准输入与键盘隔开。用bg命令后跟作业编号可以把挂起的作业放到后台执行。如果仅有一个作业被挂起。那么可以不必指明作业编号。只有前台作业可以从键盘获得输入。为了将键盘和后台某个正运行的作业连接起来,必须把该后台作业移至前台。用fg命令后跟作业编号可以把后台的作业移至前台。不带任何参数的fg命令可以将后台唯一的作业移至前台。

2.kill:终止后台作业

命令行上输入kill后跟进程的PID号(或者后跟%和作业编号),可以将后台正在运行的进程(或作业)终止,使用中断键(通常CONTROL+C)是不能实现其功能的。

1.4 文件名生成/路径名展开

通配符和通配:当输入包含特殊字符(也称为元字符)的部分文件名时,shell可以生成与已有文件的名字匹配的文件名。这些特殊的字符也常常被称为通配符(wildcard)。当某个特殊字符作为参数出现在命令行上时,shell将该参数扩展为有序的文件名列表,并将列表传递给命令行上调用的程序。包含特殊字符的文件名称为模糊文件引用(ambiguous file reference),因为它们不与任何一个特定文件相关联。对这些文件名操作的过程称为路径名展开(path expansion)或者通配(globbing)。

1.4.1 特殊字符?

问号(?)是shell生成文件名的特殊字符,它与已有文件名中的某个单独字符匹配。

[root@QUFENGBIN test]# ls test0?
test01
[root@QUFENGBIN test]# echo wh?.out
who.out
[root@QUFENGBIN test]# cat wh?.out
root     tty1         2018-04-17 15:44
root     pts/0        2018-04-28 14:50 (111.113.5.18)

1.4.2 特殊字符*

星号(*)的功能与问号的类似,不同之处在于,星号可以跟文件名中的任意多个(包括0个)字符匹配。

1.4.3 特殊字符[]

用方括号将一个字符列表括起来使得shell与包含列表中每个单独字符的文件名进行匹配。例如,test?可匹配test后跟任何一个字符的文件名,而方括号更严格些,test[01]仅与test0、test1匹配。这里,方括号定义了一个字符类(character class),该类由括号内的所有字符组成。
每个定义字符类只能替换文件名中的一个字符。方括号和其中的内容合起来的功能就如同问号一样,但是只能用字符类中的一个成员替换。
ps:左方括号后直接跟叹号(!)或脱字符(^)也可以定义字符类,该类与任何不在方括号内的字符匹配。例如,[^ab]*与不以a或b开始的文件名匹配。

[root@QUFENGBIN test]# ls
aa  ab  ac  ad  ba  bb  bc  bd  cc  dd
[root@QUFENGBIN test]# ls *[^ab]
ac  ad  bc  bd  cc  dd
[root@QUFENGBIN test]# ls [b-d]*
ba  bb  bc  bd  cc  dd

下面的例子表示ls工具不能翻译模糊文件引用。第一个ls命令带有参数aa*,shell将其扩展为匹配的文件名aa,并将该名字传递给ls。第二个命令将*转义,shell不再将*看作特殊字符,并将其传递给ls,ls报错。大多数工具和程序像ls一样也不能解释模糊文件引用,该解释工作由shell完成。

[root@QUFENGBIN test]# ls aa*
aa
[root@QUFENGBIN test]# ls aa\*
ls: cannot access aa*: No such file or directory
[root@QUFENGBIN test]# ls aaa*
ls: cannot access aaa*: No such file or directory

注意:模糊文件引用由shell进行扩展,而不是shell调用的程序进行扩展。上面的例子都不能“看到”模糊文件引用。shell对模糊文件引用进行扩展,并将扩展得到的文件列表传递给工具。在下面的echo例子中验证了这一点,因为它显示了参数而不是模糊文件引用。

[root@QUFENGBIN test]# ls
aa  ab  ac  ad  ba  bb  bc  bd  cc  dd
[root@QUFENGBIN test]# echo a?
aa ab ac ad
[root@QUFENGBIN test]# echo *
aa ab ac ad ba bb bc bd cc dd
[root@QUFENGBIN test]# echo a*
aa ab ac ad
[root@QUFENGBIN test]# echo .*
. ..
[root@QUFENGBIN test]# echo [a-m]*
aa ab ac ad ba bb bc bd cc dd
[root@QUFENGBIN test]# echo [x-z]*
[x-z]*
[root@QUFENGBIN test]# echo *[a-d]
aa ab ac ad ba bb bc bd cc dd
[root@QUFENGBIN test]# echo *[x-z]
*[x-z]
[root@QUFENGBIN test]# echo [a-d]\*
[a-d]*
[root@QUFENGBIN test]# echo [x-z]\*
[x-z]*

1.5 内置命令(内建命令)

外部命令
外部命令,有时候也被称为文件系统命令,是存在于bash shell之外的程序。它们并不是shell程序的一部分。外部命令程序通常位于/bin、/usr/bin、/sbin或/usr/sbin中。ps是一个外部命令,你可以使用which和type命令找到它。
04cfde2995914294fb7e96b6d702a7d9.png
当外部命令执行时,会创建出一个子进程。这种操作被称为衍生(forking)。
内置命令(内建命令)
内建命令和外部命令的区别在于前者不需要使用子进程来执行。它们已经和shell编译成了一体,作为shell工具的组成部分存在。不需要借助外部程序文件来运行。可以利用 type 命令来了解某个命令是否是内建的。每个shell都有自己的内置命令集合。
输入命令“man bash”,再输入命令“/^SHELL BUILTIN COMMANDS”,shell将在内置命令部分搜索始于SHELL的行,从而可以查看内置命令的man页内容。

2、Bourne Again Shell

要点:初始化文件、重定向标准错误输出、编写简单的shell脚本、作业控制、操作目录栈、参数和变量、进程、命令历史机制、重新执行和编辑命令、别名、函数、控制bash特性和选项、处理命令行
Bourne Again Shell是一种命令解释器,同时也是一种高级编程语言。作为命令解释器,它们通过提示符响应并处理用户在命令行界面上输入的命令。而作为一种编程语言,它们将处理存放在所谓shell脚本文件中的命令。

2.1 shell基础

内容包括编写和使用初始化文件、重定向标准错误输出、编写和执行简单的shell脚本、命令分割和分组、实现作业控制和操作目录栈。

2.1.1 初始化文件

当启动shell时,它将运行初始化文件初始化自己。具体运行哪个文件取决于该shell是一个登录shell还是一个非登录shell的交互式shell(比如通过命令bash),又或者是一个非交互式shell(用来执行shell脚本)。要想运行初始化文件中的命令,用户必须具备读权限。

1.登录shell

登录shell本来就属于交互式shell。
/etc/profile:shell首先执行/etc/profile中的命令。通过设置这个文件,超级用户可以为全系统内的所有bash用户建立默认特征。
.bash_profile、.bash_login和.profile:然后shell依次查找~/.bash_profile、~/.bash_login和~/.profile,并执行它找到的首个文件中的命令。可以将命令放置在这些文件中的某个里以覆盖掉/etc/profile文件中的默认设置。
.bash_logout:当用户注销时,bash执行文件~/.bash_logout中的命令。这个文件包含了退出会话时需要执行的清理任务(比如删除临时文件)常用到的命令。

2.交互式非登录shell

在交互式非登录shell中并不执行前面提到的初始化文件中的命令。然而,交互式非登录shell从登录shell继承了由这些初始化文件设置的shell变量。
/etc/bashrc:尽管不是通过bash直接调用,许多~/.bashrc文件还是调用/etc/bashrc。这种安排使得超级用户可以为全系统内的非登录bash shell建立默认特性。
.bashrc:交互式非登录shell执行~/.bashrc文件中的命令,而登录shell的初始化文件(比如.bash_profile)通常会运行这个文件。这样,登录shell和非登录shell都可以使用.bashrc中的命令。

3.非交互式shell

非交互式shell(如那些运行shell脚本的shell)并不执行前面描述的初始化文件中的命令。然而,这些shell从登录shell那里继承了由这些初始化文件设置的shell变量。
BASH_ENV:非交互式shell查找环境变量BASH_ENV(或者当shell作为sh调用时为ENV),并执行由该变量命名的文件中的命令。

4.建立初始化文件

尽管有很多种初始化文件和shell,但是用户通常只需要主目录下的.bash_profile和.bashrc文件。.bash_profile中通过以下命令将为登录shell执行.bashrc(如果该文件存在)中的命令。

if [ -f ~/.bashrc ]; then
    . ~/.bashrc
fi

5..(句点)或者source:在当前shell中运行初始化文件

在编辑诸如.bashrc这类的初始化文件之后,要使这些修改起作用,用户没有必要注销然后再次登录,可以使用内置命令“.”(句点)或者source(这是两个相同的命令)。与其他命令一样,在命令行上,“.”后面必须有一个空格。内置命令“.”或者source用起来类似于运行一个shell脚本,但是这些命令将该脚本作为当前进程的一部分运行。因此,当使用“.”或者source运行脚本的时候,在脚本中改变的变量也将影响到运行该脚本的shell。可以使用“.”或者source命令来运行任何shell脚本,而不仅仅是初始化文件,但是可能会带来副作用(比如可能修改用户依赖的shell变量的值)。如果将初始化文件作为常规shell脚本运行,并且不使用“.”或source内置命令,那么启动脚本中创建的变量将只在运行该脚本的子shell中起作用。

2.1.2 符号命令

Bourne Again Shell以多种方式使用符号(、)、[、]和$。为了避免混淆,下表列出了每种符号最通用的用法。
符号 命令
() 子shell
$() 命令替换
(()) 算术表达式计算,let的同义词(当被括起来的值中包含等号时使用)
$(()) 算术展开(不用于被括起来的值中包含等号的情形)
[] test命令
[[]] 条件表达式,类似于[],但是添加了字符串比较

2.1.3 重定向标准错误输出

除了标准输出之外,命令还可以将输出发送到标准错误输出(standard error)。命令将错误消息发送到标准错误输出,这样就可以避免与发送到标准输出的信息混淆在一起。
与处理标准输出一样,默认情况下,shell 将命令的标准错误输出发送到屏幕上。除非将标准输出和标准错误输出中的某一个重定向,否则不能区分命令的输出到底是标准输出还是标准错误输出
文件描述符:文件描述符(file descriptor)是程序发送输出和获取输入的地方。当执行一个程序时,运行该程序的进程打开了 3 个文件描述符,分别是:0(标准输入)、1(标准输出)和 2(标准错误输出)重定向标准输出符号(>)是 1> 的简写,它通知 shell 将标准输出重定向。类似地,< 是 0< 的简写,表示将标准输入重定向。符号 2> 将标准错误输出重定向
下面是个例子:
当运行 cat 时,如果所带参数中的某个文件不存在,而另一个文件存在,那么 cat 将发送一条错误消息到标准错误输出,同时还将已存在的那个文件复制一份到标准输出。除非将它们重定向,否则两条消息都将出现在屏幕上。

[root@QUFENGBIN test]# cat y
This is y
[root@QUFENGBIN test]# cat x
cat: x: No such file or directory
[root@QUFENGBIN test]# cat x y
cat: x: No such file or directory
This is y

将命令的标准输出重定向时,发送到标准错误输出的输出结果将不受影响,仍然出现在屏幕上。

[root@QUFENGBIN test]# cat x y > hold
cat: x: No such file or directory
[root@QUFENGBIN test]# cat hold
This is y
[root@QUFENGBIN test]# cat x y 1> hold1 2>hold2
[root@QUFENGBIN test]# cat hold1
This is y
[root@QUFENGBIN test]# cat hold2
cat: x: No such file or directory

复制文件描述符:在下一个例子中,1> 将标准输出重定向到文件hold。然后,2>&1 声明文件描述符 2 为文件描述符 1 的副本。结果是, 标准输出和标准错误输出均被重定向到文件hold中。

root@QUFENGBIN test]# cat x y 1>hold 2>&1
[root@QUFENGBIN test]# cat hold
cat: x: No such file or directory
This is y

在上面这个示例中,1>hold 放在了 2>&1 的前面。如果将它们的顺序颠倒的话,在标准输出重定向到文件 hold 之前,标准错误输出就已经拷贝了标准输出的一个副本。这样一来,就只有标准输出被重定向到文件hold。

[root@QUFENGBIN test]# cat x y 2>&1 1>hold
cat: x: No such file or directory
[root@QUFENGBIN test]# cat hold
This is y
[root@QUFENGBIN test]# cat x y 2>&1
cat: x: No such file or directory
This is y

Bourne Again Shell所支持的重定向操作符如下表。

操作符 含义
将标准输入重定向为文件 filename
>filename 除非文件 filename 已存在并且设置了 noclobber 标记,否则标准输出将被重定向到文件 filename 。如果文件 filename 不存在且没有设置 noclobber 标记,那么重定向操作将创建该文件
>|filename 即使文件 filename 已存在并且设置了 noclobber 标记,仍将标准输出重定向到该文件
>>filename 除非文件 filename 已存在并且设置了 noclobber 标记,否则标准输出将被重定向到文件 filename,并将内容添加到原文件的末尾。如果文件 filename 不存在且没有设置 noclobber 标记,那么将创建该文件
<&m 从文件描述符m复制标准输入
[n]>&m 从文件描述符m复制标准输出或者文件描述符n(如果命令中指定了n)
[n]<&- 关闭标准输入或者文件描述符n(如果指定了n)
[n]>&- 关闭标准输出或者文件描述符n(如果指定了n)

2.1.4 编写一个简单的shell脚本

shell 脚本是包含 shell 可执行命令的文件。shell 脚本中的命令可以是用户在 shell 提示符后面输入的任何命令。除了可以使用用户在命令行下面输入的命令之外,shell 脚本还可以使用控制流(control flow)命令(亦称为控制结构(control structure))。使用这组命令可以改变脚本中命令的执行顺序。

1.chmod:使文件可执行

任何用户想把文件名作为命令行执行,都必须具备执行访问权。如果该文件是一个 shel l脚本,用户尝试执行这个文件时,还必须具备读访问权限。而在执行一个二进制可执行文件(已编译程序)时,并不需要读访问权限。

[root@QUFENGBIN test]# ll whoson
-rw-r--r-- 1 root root 41 Apr 30 20:09 whoson
[root@QUFENGBIN test]# cat whoson
date
echo "User Currently Logged In"
who
[root@QUFENGBIN test]# whoson
-bash: whoson: command not found
[root@QUFENGBIN test]# ./whoson
-bash: ./whoson: Permission denied
[root@QUFENGBIN test]# chmod u+x whoson
[root@QUFENGBIN test]# ./whoson
Mon Apr 30 21:36:18 CST 2018
User Currently Logged In
root     tty1         2018-04-17 15:44
root     pts/0        2018-04-30 11:55 (118.74.57.231)
[root@QUFENGBIN test]# whoson
-bash: whoson: command not found
[root@QUFENGBIN test]# PATH=$PATH:.
[root@QUFENGBIN test]# whoson
Mon Apr 30 21:36:53 CST 2018
User Currently Logged In
root     tty1         2018-04-17 15:44
root     pts/0        2018-04-30 11:55 (118.74.57.231)

2.#! 指定shell

在 shell 脚本文件的第一行可以放置一行特殊的字符串,告诉操作系统使用哪个 shell 来执行这个文件。因为操作系统在试图 exec 文件之前检查该程序的开头字符串,这些字符让操作系统不必进行失败的尝试。如果脚本的前两个字符是 #! ,那么系统将这两个字符后面的那些字符作为用来执行该脚本的命令解释器的绝对路径名。它可以是任何程序的路径名,而不仅仅是 shell 。

3.#开始一行注释

4.执行 shell 脚本(fork、exec、source)

fork 和 exec 系统调用:用户在命令行上输入一条命令之后,shell 将 fork 一个新的进程,以创建当前 shell 进程的一个副本(子shell)。这个新的进程将试图 exec(execute,执行)该命令。与 fork 一样,exec 例程也是由操作系统执行(系统调用)。如果该命令是一个二进制可执行程序,比如编译好的 C 程序,那么 exec 执行成功,系统调用该可执行程序将新创建的子 shell 覆盖掉。而如果这个命令是一个 shell 脚本,exec 执行失败。当 exec 失败时,将会假设该命令是一个 shell 脚本,子 shell 将执行脚本中的命令。与登录 shell 期望从命令行读取输入不同,子 shell 从文件(shell 脚本)中获取输入。所以,如果不具备 shell 脚本文件的执行权限,那么,用户可以使用 bash 命令来 exec 一个 shell 直接运行该脚本,这样就可以运行脚本中的命令。

5.fork、exec、source 区别

fork  ( ~/test/test01.sh) :如果 shell 中包含执行命令,那么子命令并不影响父级的命令,在子命令执行完后再执行父级命令。子级的环境变量不会影响到父级。
说明:fork 是最普通的, 就是直接在脚本里面用 ~/test/test01.sh 来调用 test01.sh 这个脚本。运行的时候开一个 sub-shell 执行调用的脚本,sub-shell 执行的时候, parent-shell 还在。sub-shell 执行完毕后返回 parent-shell.。sub-shell 从 parent-shell 继承环境变量,但是 sub-shell 中的环境变量不会带回 parent-shell。
exec (exec ~/test/test01.sh):执行子级的命令后,不再执行父级命令。
说明:exec 与 fork不同,不需要新开一个 sub-shell 来执行被调用的脚本。被调用的脚本与父脚本在同一个 shell 内执行。但是使用 exec 调用一个新脚本以后, 父脚本中 exec 行之后的内容就不会再执行了。这是 exec 和source 的区别。
source (source ~/test/test01.sh):执行子级命令后继续执行父级命令,同时子级设置的环境变量会影响到父级的环境变量。
说明:与 fork 的区别是不新开一个 sub-shell 来执行被调用的脚本,而是在同一个 shell 中执行。 所以被调用的脚本中声明的变量和环境变量,都可以在主脚本中得到和使用。

[root@QUFENGBIN test]# chmod u+x test01.sh
[root@QUFENGBIN test]# chmod u+x test02.sh
[root@QUFENGBIN test]# cat test01.sh
#!/bin/bash
A=B
echo "PID for test01.sh before exec/source/fork:$$"
export A
echo "test01.sh: \$A is $A"
case $1 in
        exec)
                echo "using exec…"
                exec ./test02.sh ;;
        source)
                echo "using source…"
                . ./test02.sh ;;
        *)
                echo "using fork by default…"
                ./test02.sh ;;
esac
echo "PID for test01.sh after exec/source/fork:$$"
echo "test01.sh: \$A is $A"
[root@QUFENGBIN test]# cat test02.sh
#!/bin/bash
echo "PID for test02.sh: $$"
echo "test02.sh get \$A=$A from test01.sh"
A=C
export A
echo "test02.sh: \$A is $A"
[root@QUFENGBIN test]# ./test01.sh
PID for test01.sh before exec/source/fork:1259
test01.sh: $A is B
using fork by default…
PID for test02.sh: 1260
test02.sh get $A=B from test01.sh
test02.sh: $A is C
PID for test01.sh after exec/source/fork:1259
test01.sh: $A is B
[root@QUFENGBIN test]# ./test01.sh exec
PID for test01.sh before exec/source/fork:1261
test01.sh: $A is B
using exec…
PID for test02.sh: 1261
test02.sh get $A=B from test01.sh
test02.sh: $A is C
[root@QUFENGBIN test]# ./test01.sh source
PID for test01.sh before exec/source/fork:1262
test01.sh: $A is B
using source…
PID for test02.sh: 1262
test02.sh get $A=B from test01.sh
test02.sh: $A is C
PID for test01.sh after exec/source/fork:1262
test01.sh: $A is C

2.2 参数和变量

什么是环境变量
bash shell 通过一个叫做环境变量(environment variable)的特性来存储有关 shell 会话和工作环境的信息(这也是它们被称为环境变量的信息)。这项特性允许你在内存中存储数据,一遍程序或 shell 在运行的脚本能够轻松的访问它们。在 bash shell 中,环境变量分为两类:全局变量、局部变量
全局环境变量
全局环境变量对于 shell 会话和所有生成的子 shell 都是可见的。局部变量则只对于创建它们的 shell 可见。Linux 系统在开始 bash 会话前就设置了一些全局环境变量。系统环境变量基本上都使用全大写字母,以区别于普通用户的环境变量。要查看全局环境变量,可使用 env 命令或者 printenv 命令。
变量
在 shell 中,shell 参数(shell parameter)与用户可访问的某个值相关,有几种不同的 shell 参数。参数的名字由字母、数字和下划线组成,常被称为 shell 变量(shell variable),或者简称为变量(variable)。变量名必须以字母或者下划线开头,而不能是数字。
用户创建的变量
用户命令或赋值的 shell 变量称为用户创建的变量(user-created variable)。用户可以在任何时候修改用户创建的变量的值,或者将其设置为只读。还可以将用户创建的变量变成全局的。全局变量(又称为环境变量)可以被任何shell和从最初shell创建的其它程序访问。这里有一个命名约定,即全局变量只使用大写字母,而其它变量则使用大小写混合命名
关键字变量
关键字 shell 变量(keyword shell variable,简称为关键字变量)对于 shell 而言,具有特殊的意义,它们的名字一般比较短而且有助于记忆。当用户启动 shell 的时候(比如登录),shell 将从环境中继承几个关键字变量。HOME 和 PATH 就属于这样的变量。
位置参数和特殊参数
位置参数和特殊参数的名字并不像变量名。其中,大多数参数的名字都只由一个字符组成(比如1、?和#等),并且像其他所有变量一样,在引用它们时一般在其名字前面加上美元符号(如$1、$?和$#)。这些参数的值反映了用户与 shell 交互的不同方面。无论何时,用户输入的一行命令中的每个参数都将成为位置参数(positional parameter)的值。用户使用位置参数可以访问命令行参数,在编写 shell 脚本时将用到这项功能。内置命令 set 可以用来对位置参数赋值。其他经常需要用到的 shell 脚本值,比如最后一次执行的命令名、命令行参数的个数以及最近执行命令的状态,这些值均保存在特殊参数(special parameter)中。用户不能对特殊参数赋值。

2.2.1 用户创建的变量/引用变量

[root@aminglinux_01 ~]# person=alex
[root@aminglinux_01 ~]# echo person
person
[root@aminglinux_01 ~]# echo $person
alex

命令echo $person显示变量person的值,而不是显示$person,这是因为不会将$person作为参数传递给echo。由于名字开头出现$,因而shell识别出这是一个变量的名字,并将变量的值代入,同时将该值传递给echo。内置命令echo显示该变量的值,而不是它的名字,然而echo绝不会知道用户调用它时使用了变量
引用$:如果将开头的$用单引号引起来,就可以阻止shell代入变量的值。双引号不能阻止代入(会把引号之间的所有字符代入,包括单引号),而单引号和反斜杠符号\都可以阻止代入。

[root@aminglinux_01 ~]# echo $person
alex
[root@aminglinux_01 ~]# echo "$person"
alex
[root@aminglinux_01 ~]# echo '$person'
$person
[root@aminglinux_01 ~]# echo \$person
$person
[root@aminglinux_01 ~]# echo "'$person'"
'alex'
[root@aminglinux_01 ~]# echo "'\$person'"
'$person'

空格符:双引号不能阻止变量替换,但是可以关闭大多数其他字符的特殊含义。

[root@aminglinux_01 ~]# person="alex and jenny"
[root@aminglinux_01 ~]# echo $person
alex and jenny
[root@aminglinux_01 ~]# person=alex and jenny
-bash: and: 未找到命令

当引用一个包含制表符和多个相连空格符的变量时,需要使用引号来保留这些空格。如果没有将该变量用引号引起来,在将其传递给工具之前,shell将把每个空白字符构成的串压缩成单个空格符:

[root@aminglinux_01 ~]# person="alex    and     jenny"
[root@aminglinux_01 ~]# echo $person
alex and jenny
[root@aminglinux_01 ~]# echo "$person"
alex    and     jenny

赋值中的路径名展开:当引用一个包含未被引号引起来的特殊字符的变量时,所有 shell 均将这些字符解释为特殊字符。

[root@aminglinux_01 test]# ls
1.txt  2.txt  alex.1  alex.2
[root@aminglinux_01 test]# memo=alex*
[root@aminglinux_01 test]# echo $memo
alex.1 alex.2
[root@aminglinux_01 test]# echo "$memo"
alex*

PS:语法 $VARIABLE 是 ${VARIABLE} 的特殊情形,后者要更加通用,它将变量名用${}括起来。花括号将变量名隔离起来。将一个变量和一个字符串连接起来的时候,花括号是必要的:

[root@aminglinux_01 test]# PREF=counter
[root@aminglinux_01 test]# WAY=$PREFclock
[root@aminglinux_01 test]# FAKE=$PREFfeit
[root@aminglinux_01 test]# echo $WAY $FAKE

[root@aminglinux_01 test]# WAY=${PREF}clock
[root@aminglinux_01 test]# FAKE=${PREF}feit
[root@aminglinux_01 test]# echo $WAY $FAKE
counterclock counterfeit

Bourne Again Shell使用特殊的变量$1、$2、$3直到$9,通过位置参数引用命令行中的参数。如果需要引用第9个以后的参数,就必须使用花括号:${10}。命令的名字保存在$0中。
unset:删除变量

2.2.2 变量属性

1. readonly :使变量值不可更改

可以使用内置命令readonly确保某个变量的值不被改变。

[root@aminglinux_01 test]# person=jenny
[root@aminglinux_01 test]# echo $person
jenny
[root@aminglinux_01 test]# readonly person
[root@aminglinux_01 test]# person=alex
-bash: person: 只读变量

当不带参数使用内置命令readonly时,它会显示所有只读变量的列表。

[root@aminglinux_01 test]# readonly
declare -r BASHOPTS="checkwinsize:cmdhist:expand_aliases:extquote:force_fignore:histappend:hostcomplete:interactive_comments:login_shell:progcomp:promptvars:sourcepath"
declare -ir BASHPID
declare -ar BASH_VERSINFO='([0]="4" [1]="2" [2]="46" [3]="2" [4]="release" [5]="x86_64-redhat-linux-gnu")'
declare -ir EUID="0"
declare -ir PPID="1018"
declare -r SHELLOPTS="braceexpand:emacs:hashall:histexpand:history:interactive-comments:monitor"
declare -ir UID="0"
declare -r person="jenny"

2. declare 和 typeset :为变量赋予属性

内置命令 declare 和 typeset(这是同一个命令的两个名字)可以用来设置 shell 变量的属性和值。下表列出了5种属性。
属性 含义
-a 声明一个数组变量
-f 声明一个函数名变量
-i 声明一个整型变量
-r 声明变量为只读,也可使用 readonly
-x 输出变量(设置为全局变量),也可用 export

列出变量属性:如果不带任何参数或者选项,那么内置命令 declare 将列出所有 shell 变量。不带任何参数运行 set 命令,也会得到同样结果。如果内置命令 declare 带有选项,但是没有变量名作为参数,那么该命令将列出所有具有指定属性集合的 shell 变量。

2.2.3 关键字变量

关键字变量既可以通过继承而来,也可以在 shell 启动时声明并初始化。

1. HOME : 用户主目录

默认情况下,用户主目录就是用户登录之后的工作目录。当用户创建其账号时,其主目录就已经创建下来了,这个文件名存放在 /etc/passwd 中。

[root@aminglinux_01 test]# grep root /etc/passwd
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin

代字符(~):shell 使用 HOME 的值来展开路径名,该路径名使用简写形式(代字符 ~ )来表示用户的主目录

[root@aminglinux_01 test]# cd
[root@aminglinux_01 ~]# pwd
/root
[root@aminglinux_01 ~]# echo ~
/root

2. PATH :shell 查找程序的路径

在向 shell 中输入一个绝对路径名或者相对路径名,而不是一个简单的文件名作为命令时,shell 就会在指定的这个目录下,用指定文件名查找可执行文件。如果该路径名对应的文件不存在,shell 将会报告“ command not found ”(命令找不到)错误。如果指定文件存在,但是用户没有执行权限,或者是用户没有 shell 脚本的读权限和执行权限,shell 将报告“ Permission denied ”(权限禁止)错误。
如果使用简单的文件名作为命令,shell 将搜索某些目录,以查找用户想要执行的程序。shell 在几个目录中搜索文件,查找与该命令具有相同的名字、且用户具有执行权限(对于编译好的程序)或者具有读权限和执行权限(对于shell 脚本)的文件。shell 变量 PATH 控制着这些搜索路径。

[root@QUFENGBIN ~]# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/usr/local/mysql/bin:/usr/local/openssl/bin:/root/bin

3. MAIL :保存电子邮件的地方

变量 MAIL 包含了保存用户邮件的文件的路径名。保存用户邮件的文件就是该用户的 mailbox ,通常是 /var/spool/mail/name ,其中 name 是用户的登录名。如果设置了 MAIL 但是没有设置 MAILPATH,那么邮件到达 MAIL 指定的文件时,shell 将提醒用户。
变量 MAILPATH 包含了一个用冒号隔开的文件名列表。如果设置了这个变量,那么当这个列表中的任何一个文件发生改变的时候(比如邮件到达时),shell 都将提醒用户。可以在该列表中任何一个文件名后面加上一个问号(?),问号后面跟着一条消息。如果有新邮件,shell 就会显示该消息。它取代了用户登录系统时因有邮件而出现的“ you have mail ”消息。
变量 MAILCHECK 规定了shell以多大的额度(以秒计)检查新邮件。默认值是60秒。如果将该值设置为0,shell 将在显示每个提示符之前检查新邮件。

[root@QUFENGBIN ~]# echo $MAIL
/var/spool/mail/root
[root@QUFENGBIN ~]# echo $MAILPATH

[root@QUFENGBIN ~]# echo $MAILCHECK
60

4. PS1 :用户主提示符

Bourne Again Shell 默认提示符是一个美元符号($)。如果以 root 身份运行 bash ,那么提示符是 # 号。变量 PS1 保存了 shell 用来提示用户输入命令的提示符串。当用户修改 PS1 的值时,用户的提示符就会发生改变。

[root@QUFENGBIN ~]# echo $PS1
[\u@\h \W]\$
[root@QUFENGBIN ~]# PS1="[\u@\h \W \!]$ "
[root@QUFENGBIN ~ 7]$

PS1="[\u@\h \W !]$ "显示的提示符的格式为[user@host directory event]$,其中user是用户名,host为本机域名中第1个点号(.)之前的主机名,directory为工作目录的基名,event为当前命令的事件编号。

[root@QUFENGBIN ~ 11]$ PS1="\h \$"
QUFENGBIN $PS1="\@ \u $ "
10:45 AM root $ PS1="\$ "
$
下表给出了一些 PS1 符号
符号 在提示符中的显示
\$ 如果以root身份运行,就显示为#,否则就是$
\w 工作目录的路径名
\W 工作目录的基名
\! 当前事件(历史)编号
\d 按照“工作日/月/日期”格式显示的日期
\h 机器的主机名,不包括域名
\H 机器全名,包括域名
\u 当前用户的用户名
\@ 按照“12小时,AM/PM”格式显示的当前时间
\T 按照12小时制“HH:MM:SS”格式显示当前时间
\A 按照24小时制“HH:MM”格式显示当前时间
\t 按照24小时制“HH:MM:SS”格式显示当前时间

5. PS2 :用户次提示符

[root@QUFENGBIN ~]# echo $PS2
>
[root@QUFENGBIN ~ 17]$ echo 'hello world
> !!!'
hello world
!!!
[root@QUFENGBIN ~ 18]$ PS2="next: "
[root@QUFENGBIN ~ 19]$ echo "hello world
next: 2"
hello world
2

6. PS3 :菜单提示符、PS4 :调试提示符

[root@QUFENGBIN ~]# echo $PS3

[root@QUFENGBIN ~]# echo $PS4
+

7. IFS :分隔输入字段(分词)

IFS(Internet Field Separator,内部字段分隔符)shell变量指定了在命令行中用来分隔参数的字符,其默认值为空格符、制表符和换行符无论IFS的值是什么,用户总可以使用一个或者多个空格符或者制表符分隔命令行中的不同参数,这里假设这些字符并没有被引用或者转义。     
当为IFS指派字符值的时候,这些字符也可以分隔字段,但是只有在展开的时候才可以这样。这种命令行解释方式称为分词(word splitting)。

[root@QUFENGBIN ~]# a=x:y:z
[root@QUFENGBIN ~]# cat $a
cat: x:y:z: No such file or directory
[root@QUFENGBIN ~]# IFS=":"
[root@QUFENGBIN ~]# cat $a
cat: x: No such file or directory
cat: y: No such file or directory
cat: z: No such file or directory

shell 根据在 IFS 中发现的分隔符划分命令行中所有被展开的词。如果不进行展开,就不会进行分词。

[root@QUFENGBIN ~]# IFS="p"
[root@QUFENGBIN ~]# export VAR
[root@QUFENGBIN ~]# aa=export
[root@QUFENGBIN ~]# echo $aa
ex ort

尽管连续出现的多个空格或制表符被视为一个分隔符,但是只要出现别的字段分隔符字段,那么每一次出现就被视为一个分隔符。

8. CDPATH :扩大cd的范围

使用 CDPATH 变量,用户可以用一个简单的文件名作为参数传递给内置命令 cd ,就将工作目录改变到某个目录,而这个目录并不是工作目录的子目录。
如果没有设置 CDPATH,而只是指定了一个简单的文件名作为调用 cd 的参数,那么 cd 将在工作目录中查找具有与该参数相同的文件名的子目录。而如果设置了 CDPATH,cd 将在 CDPATH 列表中的目录搜索具有与该参数相同的文件名的子目录。如果 cd 找到了一个子目录,那么该目录就成为工作目录。
如果 CDPATH 中没有包含工作目录,并且在 CDPATH 中均搜索失败,那么 cd 将在工作目录下搜索。如果希望 cd 首先搜索工作目录,那么可将空字符串作为 CDPATH 的第1项。空字符串用两个冒号(::)表示。如果内置命令cd 的参数是一个绝对路径——以斜杠(/)开头——则 shell 不考虑 CDPATH 。

2.2.4 shell特殊变量

2.3 特殊字符(待补充)

字符 用途 字符 用途
换行符 启动命令的执行 ; 分隔命令
() 将命令分组给子shell执行,或者是标示函数 & 在后台执行命令
| 管道 > 重定向标准输出
>> 追加标准输出 < 重定向标准输入
<< Here文档 * 模糊文件引用中的零个或者多个字符组成的串
? 模糊文件引用中的任何单个字符 \ 引用后面的字符
' 引用字符串,阻止所有替换 " 引用字符串,只允许变量替换和命令替换
`...` 执行命令替换 [] 模糊文件引用中的字符类别
$ 引用某个变量 .(内置句点) 执行命令(只在行首)
# 开始一行注释 {} 用来封装函数体
;(内置空串) 返回true &&(布尔“与”) 只有左边的命令成功(返回的退出状态值为0)才执行右边的命令
||(布尔“或”) 只有左边的命令失败(返回非零退出状态值)才执行右边的命令 !(布尔“非”) 反转命令的退出状态值
$() 执行命令替换(优先形式) [] 计算算术表达式的值

2.4 进程

进程(process)是一条命令在Linux上的执行。用户登录时启动的shell与其他一样,也是一条命令,或者进程。当用户在命令行中输入一个Linux工具名时,就启动了一个进程。当用户运行一个shell脚本的时候,系统创建另一个shell进程,并为脚本的每行命令创建另外的进程。根据用户调用脚本方式的不同,脚本既可以由当前shell运行,也可由当前shell的一个子shell运行。后一种方式更加普遍。如果用户输入一个shell内置命令如cd,此时系统并不会创建进程。

2.4.1 进程结构

fork系统调用:进程结构类似于文件结构,它也是一个层次式结构,有父进程、子进程甚至根进程(root)。父进程可以创建(fork)子进程,子进程又可以创建其他进程。术语fork,就像道路中的岔道口一样,将一个进程变成两个。创建一个新的进程的系统操作例程(或者系统调用)叫做fork。
当系统启动时,Linux开始执行,它首先启动init进程。这个进程称为自发进程(spontaneous process),其PID编号为1。这个进程在进程结构中的地位与文件结构中根目录的地位相同:它是系统进程和所有用户进程的祖先。

2.4.2 shell的父子关系

用于登录某个虚拟控制器终端或在GUI中运行终端仿真器时所启动的默认的交互shell,是一个父shell。在CLI提示符后输入 /bin/bash 命令或其他等效的 bash 命令时,会创建一个新的shell程序。这个shell程序被称为子shell(child shell)。子shell也拥有CLI提示符,同样会等待命令输入。运行shell脚本也能够创建出子shell。
2aa93f97e4a20a6f634d8b4d1e971e66.png
171bfb4c6388e18ceda240c76f703c06.png

2.4.3 进程列表

可以在一行中指定要依次运行的一系列命令。这可以通过命令列表来实现,只需要在命令之间加入分号(;)即可。
e0993c7c87f0f3662e323041591a8b96.png
在上面的例子中,所有的命令依次执行,不存在任何问题。不过这并不是进程列表。命令列表要想成为进程列表,这些命令必须包含在括号里。
尽管多出来的括号看起来没有什么太大的不同,但起到的效果确是非同寻常。括号的加入使命令列表变成了进程列表,生成了一个子shell来执行对应的命令。
说明 进程列表是一种命令分组(command grouping)。另一种命令分组是将命令放入花括号中,并在命令列表尾部加上分号(;)。语法为 { command; } 。使用花括号进行命令分组并不会像进程列表那样创建出子shell。
要想知道是否生成了子shell,得借助一个使用了环境变量的命令。这个命令就是 echo $BASH_SUBSHELL 。如果该命令返回 0 ,就表明没有子shell。如果返回1 或者其他更大的数字,就表明存在子shell。
90e874d0d1ca76de59b7914c23d0b060.png

2.5 命令历史机制

历史机制是一项对C shell的改造功能,它维护了用户最近发出的命令行(亦称为事件)的列表。内置命令history显示了历史列表的内容

2.5.1 控制历史机制的变量

HISTSIZE变量的值决定了在某次会话期间历史列表保存的事件数目。该值的范围正常情况下是100到1000。
当从shell中退出时,最近执行的命令将保存在HISTFILE变量指定的文件中(默认是~/.bash_history)。下一次启动shell时,将用这个文件来初始化历史列表。变量HISTFILESIZE的值决定了保存在HISTFILE中的历史行数(不一定与HISTSIZE相同)。HISTSIZE保存了会话期间记忆的事件数目,HISTFILESIZE则保存了会话之间记忆的数目,HISTFILE指定了保存历史列表的文件。

[root@aminglinux_01 ~]# echo $HISTSIZE      #在一次会话期间保存的最大事件数目
1000
[root@aminglinux_01 ~]# echo $HISTFILE      #历史文件的位置
/root/.bash_history
[root@aminglinux_01 ~]# echo $HISTFILESIZE  #会话之间保存的事件最大数目
1000

事件编号:Bourne Again Shell连续地为每行命令指派了一个事件编号(event number)。如果在PS1中包含“!”,则可以将事件编号作为bash提示符的一部分显示出来。

[root@aminglinux_01 ~]# echo $PS1
[\u@\h \W]\$
[root@aminglinux_01 ~]# PS1="[\u@\h \W \!]\$"
[root@aminglinux_01 ~ 193]$

输入history命令可以显示历史列表中的事件。事件列表按照最早的事件排在列表顶部的顺序排列。

[root@aminglinux_01 ~ 194]$history | tail
  185  info bash builtin
  186  info bash
  187  man bash
  188  echo $HISTSIZE
  189  echo $HISTFILE
  190  echo $HISTFILESIZE
  191  echo $PS1
  192  PS1="[\u@\h \W \!]\$"
  193  history
  194  history | tail

2.5.2 重新执行和编辑命令

可以重新执行历史列表中的任何事件。可以采用3种方式来浏览、修改和重新执行前面已执行的命令,即使用内置命令fc、使用感叹号(!)命令以及Readline库(暂不介绍),该库使用vi编辑器来编辑和执行事件。

1. fc:显示、编辑和重新执行命令

2. 使用感叹号(!)引用事件

!!命令重新执行前一个事件,!\$符号表示前一个命令行中最后一个字。可以使用事件的绝对编号、相对编号或者事件中包含的文本来引用该事件。所有的事件引用(称为事件标志符)均以感叹号(!)开头。感叹号后面的一个或多个字符指定某个事件。
事件标志符:事件标志符指定了命令历史中的某条命令。如下表:
标志符 含义
! 除非后面紧跟着空格符、换行符、=或者(,否则立即开始某个历史事件
!! 前一个命令
!n 历史列表中编号为n的命令
!-n 往前第n条命令
!string 最近以string开头的命令行
!?string[?] 包含string的最近命令行,最后的?是可选的
!# 当前命令(目前敲入的部分)
!{event} event为一个事件标示符。花括号将event与左右的文本隔开。比如,!{-3}3 表示后面跟着3的第3个最近执行的命令
[root@aminglinux_01 ~ 205]$ls -l 3.txt
-rw-r--r--. 1 root root 25 5月   7 13:18 3.txt
[root@aminglinux_01 ~ 206]$!!
ls -l 3.txt
-rw-r--r--. 1 root root 25 5月   7 13:18 3.txt
[root@aminglinux_01 ~ 206]$!205
ls -l 3.txt
-rw-r--r--. 1 root root 25 5月   7 13:18 3.txt
[root@aminglinux_01 ~ 206]$!-1
ls -l 3.txt
-rw-r--r--. 1 root root 25 5月   7 13:18 3.txt
[root@aminglinux_01 ~ 206]$history 10
  197  fc -l
  198  type -a fc
  199  ll /usr/bin/fc
  200  type -a fc
  201  history | tail
  202  history
  203  history | tail
  204  ll
  205  ls -l 3.txt
  206  history 10
[root@aminglinux_01 ~ 207]$!l
ls -l 3.txt
-rw-r--r--. 1 root root 25 5月   7 13:18 3.txt
[root@aminglinux_01 ~ 208]$!?ll?
ll
总用量 8
-rw-r--r--. 1 root root   25 5月   7 13:18 3.txt
-rw-------. 1 root root 1418 4月  14 21:55 anaconda-ks.cfg
drwxr-xr-x. 2 root root   60 5月   7 15:45 test

3. 字标志符

字标志符(word designator)指定了事件中的某个字或一组字。如下表:
字标志符 含义
n 第n个字。一般情况下,字0就是命令名
^ 第1个字。(紧随命令名)
$ 最后那个字
m-n 最后那个字从编号m到n的所有字,如果忽略m,那么m默认为0(0-n)
n* 从第n个到最后那个之间的所有字
* 除命令名之外的所有字。与1*相同
% 最近的匹配?string?搜索的那个字

这些字的编号从0开始(命令行上的第一个字,通常指命令名),接着是1(紧随命令名的第1个字),直到n(命令行上的最后一个字)。
为了指定前一个事件中某个特定的字,在事件标志符(如!14)后面跟着一个冒号和一个表示该字在这个命令中的标号。如,!14:3指定了事件14中命令名后第3个字。使用一个用连字符隔开的两个字标志符,可以指定字范围。

[root@aminglinux_01 ~ 209]$echo 1 2 3 4 5
1 2 3 4 5
[root@aminglinux_01 ~ 210]$echo !209:2
echo 2
2
[root@aminglinux_01 ~ 211]$echo !209:^
echo 1
1
[root@aminglinux_01 ~ 212]$!209:0 !209:$
echo 5
5
[root@aminglinux_01 ~ 213]$echo !209:2-4
echo 2 3 4
2 3 4
[root@aminglinux_01 ~ 214]$!209:0-$
echo 1 2 3 4 5
1 2 3 4 5

如果一个事件只包含了单个命令,那么字编号就与参数编号对应。如果事件包含了多条命令,那么对于第一条之后的命令来说,这种对应关系不再成立。

[root@aminglinux_01 ~ 215]$!209 ; echo 6 7 8 9
echo 1 2 3 4 5 ; echo 6 7 8 9
1 2 3 4 5
6 7 8 9
[root@aminglinux_01 ~ 216]$echo !215:7
echo echo
echo
[root@aminglinux_01 ~ 217]$echo !215:4-8
echo 4 5 ; echo 6
4 5
6
[root@aminglinux_01 ~ 218]$echo !215:6-7
echo ; echo

[root@aminglinux_01 ~ 219]$echo !$
echo echo
echo

4. 修饰符

有时候需要改变正要执行的命令的某些方面。通过在字标志符后面或者事件标志符后面(如果没有字标志符)放置一个或多个修饰符,就可以修改事件或事件的某个字。每个修饰符前面必须有一个冒号(:)。
替换修饰符:替换修饰符(substitute modifier)要比其他修饰符复杂。下面有个例子:

[root@aminglinux_01 ~ 222]$cad ~/3.txt
-bash: cad: 未找到命令
[root@aminglinux_01 ~ 223]$!!:s/cad/cat
cat ~/3.txt
this is a test
test
THIS

替换修饰符的语法:[g]s/old/new/
old为原字符串(并非正则表达式),new表示代替old的那个字符串。替换修饰符用new替换old的第1次出现。在s前面放上g将造成全局替换,即将old的所有出现都替换掉。
其他修饰符:除替换修饰符外,修饰符还可以对由事件标志符和可选字标志符选取的事件部分进行简单的编辑。可以使用多个修饰符,彼此之间用冒号(:)隔开。

[root@aminglinux_01 ~ 227]$ls ~/3.txt
/root/3.txt
[root@aminglinux_01 ~ 228]$!!:p
ls ~/3.txt
[root@aminglinux_01 ~ 228]$!!:h:p
ls ~
下表列出了除替换修饰符之外的事件修饰符
修饰符 功能
e(extension) 删除掉除文件扩展名之外的所有内容
h(head) 删除路径名的最后部分
p(print-not) 显示命令,但不要执行
q(quote) 引用该替换,以防止对其进行进一步的替换
r(root) 删除文件扩展名
t(tail) 删除掉路径名中除末尾之外的所有元素
x 与q类似,除了单独引用替换中的每个字

2.6 函数

shell函数类似于shell脚本,里面存放了一系列可在稍后执行的命令。然而,因为shell将函数存放在物理内存(RAM)而不是磁盘文件中,所以,shell访问函数的速度要比访问脚本的速度快得多。shell还对函数预处理(解析),因此其启动速度也要比脚本快得多。最后,shell函数的执行和调用是在同一个shell中进行的。
shell函数的声明可以放在~/.bash_profile初始化文件中,或者放在使用该函数的脚本中,又或者直接放在命令行。可以使用内置命令unset删除函数。一旦用户注销,shell将不再保持这些函数。
PS:如果某个shell变量和函数名称相同,那么可用unset删除shell变量。再次用相同的命令,就会删除这个函数。
声明一个shell函数的语法如下:
[function] function_name()
{
commands
)

2.7 控制bash的特性和选项

2.8 处理命令行