Shell笔记一
Shell 编程基础
获取脚本的参数
1 | # $# 代表传入函数的参数个数 |
将脚本加上可执行权限:
1 | chmod -x ./2.sh |
以 > 改变标准输出:
1 | # 以 < 改变标准输入(这条命令将会复制/tmp/a.txt 文件到/tmp/b.txt) |
以 >> 追加文件:
1 | cat 2.sh >> 3.sh |
文件描述符
理解文件描述符、系统文件表和内存索引节点表
文件描述符表
用户区的一部分,除非通过使用文件描述符的函数,否则程序无法对其进行访问。对进程中每个打开的文件,文件描述符表都包含一个条目。系统文件表
为系统中所有的进程共享。对每个活动的 open, 它都包含一个条目。每个系统文件表的条目都包含文件偏移量、访问模式(读、写、或读-写)以及指向它的文件 描述符表的条目计数。每个进程的文件表在系统文件表中的区域都不重合。理由是,这种安排使每个进程都有 它自己的对该文件的当前偏移量。内存索引节点表
对系统中的每个活动的文件(被某个进程打开了),内存中索引节点表都包含一个条目。几个系统文件表条目可能对应于同一个内存索引节点表(不同进程打开同一个文件)。
习惯上,标准输入(Standard Input)的文件描述符是 0,标准输出(Standard Output)是 1, 标准错误(Standard Error)是 2。这也是当我们重定向标准错误时,使用(2>)的原因。
特殊文件的妙用
/dev/null
我们可以把/dev/null 想象为一个“黑洞”。它类似于一个只写文件。所有写入它的内容都不可读取。但是,对于命令行和脚本来说,/dev/null 却非常有用。如:
1 | # 读取/tmp/b.txt 文件,但是将读取的内容输出到/dev/null |
如果是重定向标准输出,直接使用>就可以了,或者也可以用(1>)表示,而如果是重新向标准错误,则用 2>。如果是标准输入呢?那就要用(0<)表示。而(&>)则代表标准输出和标准错误。
/dev/zero
类似于/dev/null,/dev/zero 也是一个伪文件,但事实上它会产生一个 null 流(二进制的 0 流,而不是 ASCII 类型)。如果你想把其他命令的输出写入/dev/zero 文件的话,那么写入的内容会消失,而且如果你想从/dev/zero 文件中读取一连串 null 的话,也非常的困难,/dev/zero 文件的主要用途就是用来创建一个指定长度,并且初始化为空的文件,这种文件一 般都用作临时交换文件。
/dev/tty
/dev/tty 是一个很实用的文件。当程序打开这个文件时,UNIX/Linux 会自动将它重定向到当
前所处的终端。输出到此的信息只会显示在当前工作的终端显示器上。在某些时候例如,设定了 脚本输出到/dev/null 时,而你又想在当前终端上显示一些很重要的信息,你就可以调用这个设备, 写入重要信息。这样做可以强制信息显示到终端。
1 | printf“Enter new passwd:” # 提示输入 |
一切皆文件\
Linux 文件类型常见的有:普通文件、目录、字符设备文件、块设备文件、符号链接文件等。
普通文件
我们用 ls-lh 来查看某个文件的属性,可以看到有类似-rw-r–r– ,值得注意的,它的第一个符 号是-,这样的文件在 Linux 中就是普通文件。这些文件一般是用一些相关的应用程序创建,例如 图像工具、文档工具、归档工具或 cp 工具等。这类文件的删除方式是用 rm 命令。目录
当我们在某个目录下执行命令,看到有类似 drwxr-xr-x 命令时,这样的文件就是目录,目录 在 Linux 是一个比较特殊的文件。注意,它的第一个字符是 d。创建目录可以用 mkdir 命令或 cp 命令。cp 可以把一个目录复制为另一个目录。删除目录用 rm 或 rmdir 命令。字符设备或块设备文件
如果进入/dev 目录,列一下文件,会看到类似如下的格式:1
2
3
4
5alloy@ubuntu:~/LinuxShell/ch2$ ls-la /dev/tty
crw-rw-rw-1 root tty 5, 0 5 月 14 16:47 /dev/tty
crw-rw-rw-1 root tty 5, 0 04-19 08:29 /dev/tty
alloy@ubuntu:~/LinuxShell/ch2$ ls-la /dev/sda1
brw-rw----1 root disk 8, 1 5 月 14 11:39 /dev/sda1看到 /dev/tty 的属性是 crw-rw-rw-。注意,前面第一个字符是 c,表示字符设备文件,如猫等串口设备。
看到/dev/sda1 的属性是 brw-r—–。注意,前面的第一个字符是 b,表示块设备,如硬盘、光驱等设备。
这种文件,是用 mknode 来创建,用 rm 来删除。目前,在最新的 Linux 发行版本中,一般不用自己来创建设备文件,因为这些文件是和内核是相关联的。套接口文件
当我们启动 MySQL 服务器时,会产生一个 mysql.sock 的文件。1
alloy@ubuntu:~/LinuxShell/ch2$ ls-lh /var/lib/mysql/mysql.sock
注意,这个文件的属性的第一个字符是 s。我们了解一下就行了。
符号链接文件
1 | alloy@ubuntu:~/LinuxShell/ch2$ ls-lh setup.log |
当我们查看文件属性时,会看到有类似 lrwxrwxrwx 的命令。注意,第一个字符是 l,这类文件是链接文件。是通过 ln-s 源文件产生新文件名。这和 Windows 操作系统中的快捷方式有点相似。
编程的基础元素
字符串操作符
- 替换运算符
变量运算符 | 替换 |
---|---|
${varname:-word} | 如果 varname 存在且非 null,则返回 varname 的值;否则,返回 word。用途:如果变量未定义,则返回默认值范例:如果 loginname 未定义,则${loginname:-ollir}的值为 ollir |
${varname:=word} | 如果 varname 存在且非 null,则返回 varname 的值;否则将其置为 word,然后返回其值。用途:如果变量未定义,则设置变量为默认值 word。范例:如果 loginname 未定义,则${loginname:-ollir}的值为 ollir,并且 loginname 被设 置为 ollir |
${varname:?message} | 如果 varname 存在且非 null,则返回 varname 的值;否则打印message,并退出当前脚 本。省如果省略 message 的话,Shell 返回 parameter null or not set。用途:用于捕捉由于变量未定义而导致的错误。范例:如果 loginname 未定义,则${loginname:”undefined!”}则显示 loginname:undefined!,然后退出 |
${varname:+word} | 如果 varname 存在且非 null,则返回 word;否则返回 null。用途:用于测试变量存在。范例:如果 loginname 已定义,则${loginname:+1}返回 1 |
- 模式匹配运算符
变量运算符 | 替换 |
---|---|
${varname#pattern} | 如果模式匹配变量取值的开头处,则删除匹配的最短部分,并返回剩下部分。范例:${path#/*/}为 prince/desktop/long.file.name 这个范例删除了字符串开头/的部分 |
${varname##pattern} | 如果模式匹配变量取值的开头处,则删除匹配的最长部分,并返回剩下部分。范例:${path#/*/}为 long.file.name这个范例提取了文件路径中的文件名 |
${varname%pattern} | 如果模式匹配变量取值的结尾处,则删除匹配的最短部分,并返回剩下部分。范例:${path%.*}为/home/prince/desktop/long.file 这个范例去除文件路径中最后一个点号(.)之后的部分 |
${varname%%pattern} | 如果模式匹配变量取值的结尾处,则删除匹配的最长部分,并返回剩下部分。范例:${path%.*}为/home/prince/desktop/long 这个范例去除范例中第一个点号(.)之后的部分 |
${varname/pattern/string} ${varname//pattern/string} | 将 varname 中匹配模式的最长部分替换为 string。第一种格式中,只有匹配的第一部分 被替换;第二种格式中,varname 中所有匹配的部分都被替换。如果模式以#开头,则 必须匹配varname 的开头,如果模式以%开头,则必须匹配 varname 的结尾。如果 string 为空,匹配部分被删除。如果 varname 为@或*,操作被依次应用于每个位置参数 并且扩展为结果列表。范例:${path//prince/ollir}则为:/home/ollir/desktop/long.file.name 这个范例将字符串 prince 替换成 ollir |
${varname//pattern/string} 例子:
1 | # PATH 以换行符展示 |
位置变量
比较多得是 $n,$#,$0,$?。如例所示:
1 |
|
Shell 内置了一个 shift 命令,shift 命令可以“截去”参数列表最左端的一个。执行了 shift 命 令后,$1 的值将永远丢失,而$2 的旧值会被赋值给$1,依此类推。
条件测试
字符串比较
操作符 | 如果…则为真 |
---|---|
Str1 = str2 | str1 匹配 str2 |
Str1 != str2 | str1 不匹配 str2 |
Str1 < str2 | str1 小于 str2 |
Str1 > str2 | str1 大于 str2 |
-n str1 | str1 为非 null(长度大于 0) |
-z str1 | str1 为 null(长度为 0) |
文件属性检查
操作符 | 如果…则为真 |
---|---|
-b file | file 为块设备文件 |
-d file | file 为目录 |
-e file | file 存在 |
-f file | file 为一般文件 |
-r file | file 可读 |
-w file | file 可写 |
-x file | file 可执行 |
-s file | file 非空 |
-O file | 你是 file 的所有者 |
file1 -nt file2 | file1 比 file2 新 |
file1 -ot file2 | file1 比 file2 旧 |
case
语法如下:
1 | case expression in |
case 语句常常被用于对 单个参数有大量判断语句的情形。一个例子:
1 | # 判断文件后缀,然后根据文件后缀选择不同的读取方式。 |
for 循环
语法如下:
1 | for name [in list] # 遍历list中的所有对象 |
一个例子:
1 | # 遍历当前目录中所有 mp3 文件,mpg123 时命令行程序,播放mp3文件 |
注意,本例中 list 上的两个反单引号(``)。执行反单引号之间的命令,引用结果作为字符串。
在 for 循环中,如果 in list 被省略,则默认为 in “$@”,即命令行参数的引用列表。
while/until 循环
语法如下:
1 | while condition |
while 语句与 until 语句唯一不同的地方在于,如何判断 condition 的退出状态。在 while 语句中,当 condtion 的退出状态为真时,循环继续运行,否则退出循环。而在 until 中,当 condition 的退出状态为真时,循环退出,否则继续执行循环体。一个例子:
1 | # 遍历 PATH 路径 |
综合例子:
1 |
|
在 Shell 中,有 getopt 命令,可以简化选项处理。使用 getopt 重写:
1 |
|
可以明显看出简化了很多。首先,在 case 中对 $opt 的测试仅仅是字母,开头的 - 被去除了;然后,循环中的 shift 也被 getopt 处理了,不需要自己控制;再次,– 的 case 也不见了,getopt 自动处理;最后,针对不合法选项的处理默认下 getopt 也会显示错误信息。
正则表达式
一般字符
一般字符包括文字和数字字符、空白字符和 标点符号字符。一般字符匹配的就是它们自身。
转义的 meta 字符
当 meta 字符无法表示自己而我们需要这些字符时,转义符号的作用就体现出来了:在字符前置一个反斜杠 ()。例如,.只表示一个点,而不是任意字符;[匹配左方括号,而\表示反斜杠本身。如果将转义字符置于一般字符前,则转义字符会被忽略。
.(点号)字符
.(点号)字符 点号字符表示“任一字符”。例如,”.hina”正则表达式匹配 china,也匹配 China,但是它也同时匹配 dhina
方括号表达式
例如,[cC]hina 只匹配 china 和 China。这是最简单的方括号表达式的用法,即直接将字符列表置于方括号中。如果将^符号至于方括号的开头([^abc]),就是取反的意思。即不在方括号中出现的任意字符。例如,[^abd]hina 匹配除了 abd 三个小写字母外的任意字母,加上 hina。
星号 meta 字符的应用
ab*c 正则表达式匹配如下字符串:ac,abc,abbc,abbbc…你一定看出来了,星号 meta 字符匹配零个或多 个星号前面的单个字符。注意,匹配零个或多个字符并不是任意字母,例如,ab*c 就不匹配 adc。
a.*c 当点号和星号一起用时是表示字母 a 和 c 中匹配任意长度的字符串,例如,ac, abc, adc, abbc, acccc 等。
a.c 它的含义是字母 a 和字母 c 之间匹配任意一个字母,但是只能是一个,不能多也不能少。例如,acc, abc, aac, a!c等。
区间表达式的应用
ab{3}c a 字母和 c 字母之间的 b 字母重现 3 次,即,ab{3}c 正则表达式匹配 abbbc。
ab{3,}c a 字母和 c 字母之间的 b 字母重现至少 3 次,即,ab{3}c 正则表达式匹配 abbbc,abbbbc, abbbbbc…
ab{3,5}c a 字母和 c 字母之间的 b 字母重现 3~5 次,即,ab{3}c 正则表达式匹配 abbbc,abbbbc, abbbbbc。
ab?c 只匹配两种:ac 和 abc。
ab+c 匹配 abc,abbc,abbbc。。。但不匹配 ac。
^abc 匹配字符串开头的 3 个字母 abc,例如,abcxxxABCabcxxxefg。
efg$ 匹配结尾处的 efg。和开头一样,$符号锚定了字符串的结尾,即 abcxxxABCabcxxxefg。
如果将字符^和$一起使用,则两者之间的正则表达式就匹配了整个或整行正则表达式。有时 我们使用^$来匹配空的字符串或者空行。