好文推荐 | 链接 |
---|---|
Linux系统介绍及安装教程 | https://blog.csdn.net/weixin_45692705/article/details/119869250?spm=1001.2014.3001.5501 |
深入理解Linux系统 | https://blog.csdn.net/weixin_45692705/article/details/119882786?spm=1001.2014.3001.5501 |
无法想象没有Shell 的 Linux 会是什么样子。从一开始,Linux就是黑客们的玩具。在Linux的世界里,没有什么是不可控的。如果想要成为一名高级Linux用户,那么Shell编程是必须跨过的一道坎。本章将从正则表达式开始,逐步介绍Shell编程的基本知识。这些内容对于没有任何编程经验的读者可能有点困难,不过想一想将要接触到的激动人心的技术,请打起精神来!
Shell 教程 | 菜鸟教程:https://www.runoob.com/linux/linux-shell.html
正则表达式(regexps)
这个词背后的历史似乎很难考证。一直以来,这个说法被人们广泛应用,没有人关心,或许也没有必要关心它是怎么来的。在很多时候,“正则表达式”又被称做“模式”,所以千万别被这两件不同的外衣搞糊涂了。至少在Linux 中,“模式”和“正则表达式”讲的是同一件事情。
那么究竟什么是正则表达式?简单地说,这是一组对正在查找的文本的描述。例如一个生活中的例子,老师对调皮捣蛋的学生说:“把单词表中 a开头、t结尾的单词抄写10遍交给我。”那么对于正在抄写单词的学生而言,“a开头、t结尾的单词”就是“对正在查找的文本的描述”。同样可以告诉Shell,“把当前目录下所有以e结尾的文件名列出来”,这是正则表达式擅长做的事情。
egrep工具
- Linux egrep命令用于在文件内查找指定的字符串。
- egrep执行效果与"grep-E"相似,使用的语法及参数可参照grep指令,与grep的不同点在于解读字符串的方法。
- egrep是用extended regular expression语法来解读的,而grep则用basic regular expression语法解读,extended regular expression比basic regular expression的表达更规范。
/usr/share/dict/words
中包含了多达98568个单词,看起来无论老师所说的“单词表”是什么,Linux给出的估算只多不少。egrep "^a.*t$" /usr/share/dict/words ##列出文件words中a开头t结尾的所有单词
egrep "^a.*t$" words | wc -w
.
用于匹配除换行符之外的任意一个字符。下面这条正则表达式可以匹配诸如cat、sat、 bat这样的字符串。.at
.
能够匹配的字符范围是最大的。上面这个正则表达式还能够匹配#at
、~at
,at
这样的字符串。很多时候,需要缩小选择范围使匹配更为精确。为了限定at前的那个字符只能是小写字母,可以这样写正则表达式。[a-z]at
[
用于指定一个字符集。]
无论中有多少东西,在实际工作时只能匹配其中的一个字符。下面这条表达式用于匹配 a或b或c,而不能匹配 ab、abc、bc 或者3个字母间其他任意的组合。[abc]
-
描述一个范围。下面这条表达式匹配所有的英文字母。[a-zA-z]
[0-9]
egrep '[a-z]at' /usr/share/dict/words
可以看到,这条命令不仅会列出 cat、sat这样的单词,而且列出了zupamate、zumatic这样的词。因为这些单词中包含了mat、cat这些符合正则表达式[a-z]at的字符串,于是也被匹配了(尽管这可能不是用户的初衷)。
为了让[a-z]at
能够严格地匹配一个单词,需要为它加上一对分隔符\<
和>
。
\<[a-z]at\>
\<[a-z]atl>
的行。egrep '\<[a-z]at\>' /usr/share/dict/words
奇怪的是像 bat's
这样的行也被匹配了。事实上,如果有a#$bat
、bat!!!
、!@#S#@!SR%@!bat#@!$%^
这样的行,它们也同样会被匹配。这就涉及正则表达式中对 “单词” 的定义:
“单词” 指的是两侧由非单词字符分隔的字符串。非单词字符指的是字母、数字、下划线以外的任何字符。
仔细分析一下上面这些例子。第二十九行中 bat 分别由行首和行尾分隔,因此符合单词的定义,可以被匹配。a#$bat
中的 bat分别由标点$
和行尾分隔,符合单词的定义,可以被匹配;!@#$#@!SR%@!bat#@!$%^
中的bat 分别由标点!和#分隔,同样符合单词的定义。而 Alcatraz中的cat分别被字母1和r分隔,就不符合匹配条件了。
egrep "^[[:upper:]]t$" /usr/share/dict/words
[L:upper:]
就是一个字符类,表示所有的大写字母,等价于[A-Z]。POSIX正则表达式中的字符类
类 | 匹配字符 |
---|---|
[[:alnum:]] | 文字,数字字符 |
[[:alpha:]] | 字母字符 |
[[:lower:]] | 小写字母 |
[[:upper:]] | 大写字母 |
[[:digit:]] | 小数 |
[[:xdigit:]] | 十六进制数字 |
[[:punct:]] | 标点符号 |
[[:blank:]] | 制表符和空格 |
[[:space:]] | 空格 |
[[:cntrl:]] | 所有控制符 |
[[:pring:]] | 所有可打印的字符 |
[[:graph:]] | 除空格外所有可打印的字符 |
^
和S
分别用于匹配行首和行尾。下面这条正则表达式用于匹配所有以 a开头、t结尾、a和t之间包含一个小写字母的行。^a[a-z]t$
A
和$
不必同时使用。下面这条表达式匹配所有以数字开头的行。^[0-9]
$
这样的写法将匹配空行。而$^
则是没有意义的,系统不会对这个表达式报错,但也不会输出任何东西。.
在正则表达式中表示 除换行符之外的任意一个字符,那么如何匹配句点.
本身呢?这就需要用到转义字符\
。\
可以取消所有元字符的特殊含义。\.
匹配句点.
,\[
匹配左方括号[
……而为了匹配\
,就要用\\
来指定。www\.google\.cn
*
表示在它前面的模式应该重复0次或者多次。下面这条正则表达式用于匹配所有以a开头、以t结尾的行。^a.*t$
简单地讲解一下这条表达式: ^a
匹配以a
开头的行。.
匹配一个字符(除了换行符),*
指定之前的那个字符(由.
匹配)可以重复0次或多次;t$
匹配以t结尾的行。
与此相类似的两个元字符是+
和?
。+
指定重复1次或更多次;?
指定重复0次或1次。
下面这条正则表达式匹配所有在单词hi后面隔了一个或几个字符后出现单词Jerry的行。
\<hi\>.+\<Jerry\>
使用花括号{ }
可以明确指定模式重复的次数。例如,{3}
表示重复3次,{3,}
表示重复3次或者更多次,{5,12}
则表示重复的次数不少于5次,不多于12次。下面这条正则表达式匹配所有不少于8位的数。
\<[1-9][0-9]{7,}\>
[1-9]
开始,是因为没有哪个超过7位的数是以0开头的。相应地,最高位后面的数字应该重复7次或更多次。用于重复模式的元字符
元字符 | 描述 |
---|---|
* | 重复0次或更多次 |
+ | 重复1次或更多次 |
? | 重复0次或1次 |
{n} | 重复n次 |
{n,} | 重复n次或更多次 |
{n,m} | 重复不少于n次,不多于m次 |
子表达式
也被称为分组
,这不是什么新的概念。小学生都知道,为了计算1+3的和与4的乘积,必须用括号把1+3括起来。正则表达式也一样。请看下面这个例子:
(or){2,}
匹配所有or重复2次或更多次的行。如果去掉or两边的括号,那么这条正则表达式匹配的将是字母o后面紧跟两个或更多个字母r的行。egrep "or{2,}" /usr/share/dict/words
[^y]
[^aeiou]
^
在表示行首和反义时在位置上的区别。下面的例子匹配所有不以字母y开头的行。^[^y]
^ht$
|
表示)就用来完成 “或” 的组合。下面这条正则表达式用于匹配以字母h开头,“或者” 以字母t结尾的行。^h|t$
Jan(uary| |\.) |Feb(uary| |\.)|Mar(ch| |\.)|Apr(il| |\.)|May( |\.)|Jun(e|\.)|Jul(y| |\.)|Aug(ust| |\.)|Sep(tember| |\.)|Oct(ober| |\.)|Nov(ember| |\.)|Dec(ember| |\.)
Jan(uary| |\.)
|
分隔了3个字符(串),分别是uary、空格、句点(注意,描述句点需要使用转义符号\
)。这意味着January或者Jan或者Jan.这样的字符串都能够被匹配。I
来分隔。\
加上子表达式的编号来指代该分组匹配到的内容。这样的说法看上去有点不知所措,不妨来看几个例子。(\<.*\>).?( )*\1
(\<.*\>)
:匹配任意长度的单词。第 1 个子表达式。
.?
:匹配 0 个或 1 个标点符号。由于在句点之前匹配的是单词,因此句点.
在这里只能匹配标点。
( )*
:匹配 0 个或多个空格。第 2 个子表达式。
\1
:指代第 1 个子表达式匹配到的模式。如果第 1 个子表达式匹配到单词 cart,那么这里也自动成为 cart。
这是最古老、最经典的入门程序,用于在屏幕上打印一行字符串Hello World!
。借用这个程序,来看一看一个基本的 Shell程序的构成。
使用文本编辑器建立一个名为hello的文件。
vim hello
#! /bin/bash #Display a line echo "Hello World"
chmod +x hello ##为脚本加上可执行权限 ./hello ##执行脚本
#! /bin/bash
Display a line
以#
号开头的行是注释,Shell 会直接忽略#
号后面的所有内容。保持写注释的习惯无论对别人(在团队合作时)还是对自己(几个月后回来看这个程序)都是很有好处的。
和几乎所有编程语言一样,Shell脚本会忽略空行。用空行分割一个程序中不同的任务代码是一个良好的编程习惯。
echo "Hello"
# ! /bin/bash #将一个字符串赋给变量 output log="monday" echo "The value of logfile is:" #美元符号 ($) 用于变量替换 echo $log
=
将一个变量右边的值赋给这个变量时,直接使用变量名就可以了(注意在这赋值变量时=
左右两边没空格)。log ="monday"
$
用于对一个变量进行解析。Shell在碰到带有$
的变量时会自动将其替换为这个变量的值。unset count
$
用于解析变量。如果希望输出这个符号,那么就应该使用转义字符)
,告诉Shell 忽略特殊字符的特殊含义。注意这两个的区别
{}
来限定一个变量的开始和结束。在紧跟变量输出字母后缀时,就必须要使用这个功能。$
符号带有数字的变量中。简单地说,第一个参数存放在$1
,第二个参数存放在$2
……依次类推。当存取的参数超过10个的时候,就要用花括号把这个数字括起来,例如${13}
、${20}
等。$0
,这个变量用来存放脚本自己的名字。有些时候,例如创建日志文件时这个变量非常有用。下面来看一个脚本,用于显示传递给它的参数。#! /bin/bash echo "\$0 = *$0*" echo "\$1 = *$1*" echo "\$2 = *$2*" echo "\$3 = *$3*"
$3
的值是空的。如下:
字符 | 说明 |
---|---|
$* | 包含参数列表 |
$@ | 包含参数列表,同上 |
$# | 包含参数的个数 |
$@
最常见的用法。#! /bin/bash #显示有多少文件需要列出 echo "$# file(s) to list" #将参数列表中的值逐一赋给变量file for file in $@ do ls -l $file done
$
、` 和 ” 仍然保持其特殊含义。下面的脚本quote显示这3个引号的不同之处。
[root@localhost ~]# vim quote #! /bin/bash log=Saturday #双引号会对其中的 “$” 字符进行解释 echo "Today is $log" #单引号不会对特殊字符进行解释 echo 'Today is $log' #倒引号会运行其中的命令,并把命令输出作为最终结果 echo "Today is 'date'"
+
、-
这样的符号,用于告诉计算机执行怎样的运算。Shell 中用到的运算符
运算符 | 含义 |
---|---|
-,+ | 单目负,单目正 |
!,~ | 逻辑非,按位取反 |
*,/,% | 乘,除,取模 |
+ | 加,减 |
<<,>> | 按位左移,按位右移 |
<,>,<=,>= | 大于,小于 ,大于等于,小于等于 |
==,!= | 等于,不等于 |
& | 按位与 |
^ | 按位异或 |
| | 按位或 |
&& | 逻辑与 |
|| | 逻辑或 |
=,+=,-=,*=,/=,%=,&=,^= ,|= ,<<=,>>= | 赋值,运算并赋值 |
( 7 + 8 ) / ( 6 − 3 ) (7 + 8) / (6 - 3) (7+8)/(6−3)
==
和=
在大部分情况下不存在差异,大家将会在后文中逐渐熟悉如何进行表达式的判断。num=$[$num+1]
$[]
的使用方式非常灵活,可以接受不同基数的数字(默认情况下使用十进制)。可以采用[base#]n来表示从二到三十六进制的任何一个n值,例如2#10就表示二进制数10(对应于十进制的2)。
下面的几个例子显示了如何在$[]
中使用不同的基数求值。
1
、+和2
之间要有空格,否则expr会简单地将其当做字符串输出。最简单的if结构如下:
if test-commands then commands fi
上面这段代码首先检查表达式test-commands是否为真。如果是,就执行commands所包含的命令——commands可以是一条,也可以是多条命令。如果test-commands为假,那么直接跳过这段if结构(以fi作为结束标志),继续执行后面的脚本。
下面这段程序提示用户输入口令。如果口令正确,就显示一条欢迎信息。
#! /bin/bash echo "Enter password:" read password if ["$password" = "mypassswd"] then echo "Welcome!!" fi
if test-command-1 then commands-1 elif test-command-2 then commands-2 elif test-command-3 then commands-3 ... else commands fi
case word in pattern-1) commands-1 ;; pattern-2) commands-2 ;; ... pattern-N) commands-N ;; esac
;;
只在case结构中出现,Shell一旦遇到这条命令就跳转到case结构的最后。也就是说,如果有多个模式都匹配变量word,那么Shell 只会执行第一条匹配模式所对应的命令。与此类似的是,C语言提供了break语句在switch结构中实现相同的功能,Shell只是继承了这种书写“习惯”。区别在于,程序员可以在C程序的switch结构中省略break语句(用于实现一种几乎不被使用的流程结构),而在Shell的case结构中省略;;
则是不允许的。a=b
这样判断上能够提供更简洁、可读性更好的代码结构。在Linux 的服务器启动脚本中,case结构用于判断用户究竟是要启动、停止还是重新启动服务器进程。下面是从 openSUSE 中截取的一段控制SSH 服务器的脚本(/etc/init.d/sshd) 。/etc/init.d/sshd start
,那么Shell将执行下面这段命令:通过startproc启动SSH守护进程。echo -n "Starting SSH daemon" ## Start daemon with startproc(8). If this fails ## the echo return value is set appropriate. startproc -f -p $SSHD_PIDFILE $SSHD_BIN $SSHD_OPTS -o "PidFile=$SSHD_PIDFILE" #Remember status and be verbose rc_status -v
*)
,星号(*)
用于匹配所有的字符串。在上面的例子中,如果用户输入的参数不是start、stop或是restart 中的任何一个,那么这个参数将匹配*)
,脚本执行下面这行命令,提示用户正确的使用方法。echo "Usage: $0 {start|stop|restart|}"
*)
所在的位置很重要。如果上面这段脚本将*)
放在case 结构的开头,那么无论用户输入什么,脚本只会说Usage: $o{start/stopprestart}
这一句话。#!/bin/bash if ./testscript -1 then echo "test exit -1" fi if ./testscript 0 then echo "test exit 0" fi if ./testscript 1 then echo "test exit 1" fi
#!/bin/bash exit $@
"$password" = "john"
这样的表达式就不能直接放在 if语句的后面。为此需要额外引入一个命令,用于判断表达式的真假。test命令的语法如下:test expr
test "$password" = "john"
[
进行条件测试。后者的语法如下:[ expr ]
必须提醒读者注意的是,在Shell编程中,空格的使用绝不仅仅是编程风格上的差异。
password="john" test "$password" = "john" [ "$password" = "john"]
第一条是赋值语句,在password、= 和"john"之间没有任何空格;第2条是条件测试命令,在test、“Spassword”、=和"john"之间都有空格;第3条也是条件测试命令(是test命令的另一种写法),在[、"$password"、=、"john"和]之间都有空格。
一些C程序员喜欢在赋值语句中等号“=”的左右两边都加上空格,因为这样看上去会比较清晰,但是在 Shell中这种做法会导致语法错误。
password = "john" bash: password: 找不到命令
同样地,试图去掉条件测试命令中的任何一个空格也是不允许的。去掉“[”后面的空格是语法错误,去掉等号(=)两边的空格会让测试命令永远都返回0(表示真)。
之所以会出现这样的情况是因为Shell首先是一个命令解释器,而不是一门编程语言。空格在Shell这个“命令解释器”中用于分隔命令和传递给它的参数(或者用于分隔命令的两个参数)。使用whereis命令查找test和“[”可以看到,这是个存放在/usr/bin目录下的“实实在在”的程序文件。
test和[
命令可以对以下3类表达式进行测试;
字符串比较
用于字符串比较选项
选项 | 描述 |
---|---|
-z str | 当字符串 str 长度为 0 时返回真 |
-n str | 当字符串 str 长度大于 0 时返回真 |
str1 = str2 | 当字符串 str1 和 str2 相等时返回真 |
str1 != str2 | 当字符串 str1 和 str2 不相等时返回真 |
#!/bin/bash read password if [ -z "$password"] then echo "Please enter the password" fi
if [ -z ]
毫无疑问,在这种情况下Shell就会报错。从清晰度和可移植性的角度考虑,为字符串变量加上引号是一个好的编程习惯。
用于比较两个字符串是否相等的操作在上文中已经作了介绍。不过需要注意的是,Shell对大小写敏感,只有两个字符串完全相等
才会被认为是相等
的。下面的例子说明了这一点。
#!/bin/bash if [ "ABC" = "abc" ] then echo "ABC"=="abc" else echo "ABC"!="abc" fi if [ "ABC" = "ABC" ] then echo "ABC"=="ABC" else echo "ABC"!="ABC" fi
文件测试
选项 | 描述 |
---|---|
-b file | 当file是块设备文件时返回真 |
-c file | 当file是块设备文件时返回真 |
-d pathname | 当file是块设备文件时返回真 |
-e pathname | 当file是块设备文件时返回真 |
-f file | 当file是块设备文件时返回真 |
-g pathname | 当file是块设备文件时返回真 |
-h file | 当file是块设备文件时返回真 |
-p file | 当file是块设备文件时返回真 |
-r pathname | 当file是块设备文件时返回真 |
-s file | 当file是块设备文件时返回真 |
-u pathname | 当file是块设备文件时返回真 |
-w pathname | 当file是块设备文件时返回真 |
-x pathname | 当file是块设备文件时返回真 |
-o pathname | 当file是块设备文件时返回真 |
if [ -x /sbin/unconfigured.sh ] then /sbin/unconfigured.sh fi
数字比较
test int1 option int2
[ int1 option int2 ]
用于数字比较的选项
选项 | 对应的英语单词 | 描述 |
---|---|---|
-eq | equal | 如果相等,返回真 |
-ne | not equal | 如果不相等,返回真 |
-lt | lower than | 如果 int1 小于 int2,返回真 |
-le | lower or equal | 如果 int1 小于或等于 int2,返回真 |
-gt | greater than | 如果 int1 大于 int2,返回真 |
-ge | greater or equal | 如果 int1 大于或等于 int2,返回真 |
if [ $status -eq 0 ]; then log_success_msg "SMBD is running" else log_failure_msg "SMBD is not running" fi
复合表达式
操作符 | 描述 |
---|---|
! expr | “非” 运算,当expr为假时返回真 |
expr1 -a expr2 | “与” 运算,当expr1和expr2同时为真时才返回真 |
expr1 -o expr2 | “或” 运算,expr1或expr2为真时返回真 |
#!/bin/bash if [ -f $@ -a -x /usr/bin/vi ] then cp $@ $@.bak vi $@ fi
- 首先执行
-f $@
测试命令,如果S@
变量(也就是用户输入的参数)对应的文件存在,那么该测试返回真(0)﹔否则整条测试语句返回假,直接跳出if语句块。- 如果第一个条件为真,就执行
-x /usr/bin/vi
测试命令。如果/usr/bin/vi文件可执行,那么该测试返回真(0),同时整条测试语句返回真(0)。否则整条测试语句返回假,直接跳出if语句块。- 如果整条测试语句返回真,那么就执行if语句块中的两条语句。
if [ ! -z "$password" -o -f ~/.public_key ] then exit 0 else echo "Please enter the password:" read password fi
- 首先执行
! -z "Spassword"
测试命令,如果字符串 password 不为空,那么该测试语句返回真(0),同时整条测试语句返回真(0),不再判断-f~/.public_key
。- 如果第一个条件为假,就执行
-f ~/.public_key
测试命令。如果主目录下的.public_key文件存在,那么该测试返回真(0),同时整条测试语句返回真(0)﹔否则整条测试语句返回假,直接跳出if语句块。- 如果整条测试语句返回真,那么就执行
exit 0
﹔否则执行else语句块中的语句。
提示:注意-a (与)和-o(或)在什么情况下会判断第2条语句。前者在第1条语句为真的时候才判断第2条语句(因为如果第1条语句就不成立,那么整条测试语句一定不会成立)﹔后者在第1条语句为假的情况下才判断第2条语句(因为如果第1条语句为真,那么整条测试语句一定成立)。记住这一点,在后面的“复合命令”中还会碰到。
Shell的条件操作符&&
和||
可以用来替代test和[命令内建的-a
和-o
。如果选择使用Shell的条件操作符,那么上面的第一个例子可以改写成这样:
if [ -f $@ ] && [ -x /usr/bin/vi ] then cp $@ $@.bak vi $@ fi
&&
连接的是两条[(或者test)命令,而-a操作符是在同一条[(或者test)命令中使用的。类似地,上面使用-o操作符的脚本可以改写成这样:if [ ! -z "$password" ] || [ -f ~/.public_key ] then exit else echo "Please enter the password:" read password fi
while test=commands do commands done
#!/bin/bash sum=0 number=1 while test $number -le 100 do sum=$[ $sum + $number ] let number=$number+1 done echo "The summary is $sum"
简单地分析一下这段小程序。在程序的开头,首先将变量 sum和 number初始化为О和1,其中变量sum保存最终结果,number则用于保存每次相加的数。测试条件$number-le 100
告诉Shell 仅当number 中的数值小于或等于100的时候才执行包含在do和done之间的命令。注意,每次循环之后都将number 的值加上1,循环在number达到101的时候结束。
保证程序能在适当的时候跳出循环是程序员的责任和义务。在上面这个程序中,如果没有let number=$number+1
这句话,那么测试条件将永远为真,程序就陷在这个死循环中了。
while语句的测试条件未必要使用test(或者[ ])命令。在 Linux 中,命令都是有返回值的。例如,read 命令在接收到用户的输入时就返回0,如果用户用Ctrl+D快捷键输入一个文件结束符,那么read命令就返回一个非0值(通常是1)。利用这个特性,可以使用任何命令来控制循环。下面这段脚本从用户处接收一个大于0的数值n,并且计算1+2+3+……+n。
#!/bin/bash echo -n "Enter a number(>0):" while read n do sum=0 count=1 if [ $n -gt 0 ] then while [ $count -le $n ] do sum=$[ $sum + $count ] let count=$count+1 done echo "The summary is $sum" else echo "Please enter a number greater than zero" fi echo -n "Enter a number(>0):" done
until test-commands do commands done
单从字面上理解,whilc说的是当test-commands为真(值为0),就执行commands
。而until 说的是执行commands,直到 test-commands为真(值为0)
,这句话顺过来讲可能更容易理解。当test-commands为假(非0值),就执行commands
。
但愿大家没有被上面这些话搞糊涂。下面这段脚本麻烦Shell 再做一次那个著名的体力劳动,不同的是这次改用until语句。
#! /bin/bash sum=0 number=1 until ! test $number -le 100 do sum=$[ $sum + $number ] let number=$number+1 done echo "The summary is $sum"
while test $number -le 100
和
until ! test $number -le 100
for variable [in list] do commands done
值表
是一系列以空格分隔的值。Shell每次从这个列表中取出一个值,然后运行do/done之间的命令,直到取完列表中所有的值。下面这段程序简单地打印出1~9之间(包括1和6)所有的数。#! /bin/bash for i in 1 2 3 4 5 6 7 8 9 do echo $i done
seq 9
#! /bin/bash for i in 'seq 9' do echo $i done
#! /bin/bash count=0 for file in 'ls' do if ! [ -d $file ] then let count=$count+1 fi done echo "There are $count files"
#! /bin/bash read first second echo $first echo $second
#! /bin/bash exit 1
#! /bin/bash trap 'echo "Type quit to exit"' INT while [ "$input" != 'quit' ] do read input done
#! /bin/bash trap 'echo "Goodbye"; exit' EXIT echo "Type 'quit' to exit" while [ "$input" != "quit" ] do read input done
echo "Goodbye"; exit
,即先执行echo"Goodbye"
显示提示信息,再执行exit退出脚本。这条复合命令在脚本捕捉到EXIT信号的时候执行。EXIT信号在脚本退出的时候被触发。下面是该脚本的执行效果。&&
和||
。事实上,&&
和||
更多地被用来创建命令表,命令表可以利用一个命令的退出值来控制是否执行另一条命令。下面这条命令取自系统的rc脚本。[ -d /etc/rc.boot ] && run-parts /etc/rc.boot
[ -d /etc/rc.boot ]
,判断目录/etc/rc.boot是否存在。如果该测试命令返回真,就继续执行run-parts /etc/rc.boot
调用run-parts命令执行/etc/rc.boot 目录中的脚本。如果测试命令[ -d /etc/rc.boot ]
返回假(即/etc/rc.boot目录不存在),那么run-parts命令就不会执行。因此上面这条命令等价于:if [ -d /etc/rc.boot ] then run-parts /etc/rc.boot fi
表示形式 | 说明 |
---|---|
a && b | “与” 命令表。当且仅当a执行成功,才执行b |
a || b | “或” 命令表。当且仅当a执行失败,才执行b |
a;b | 顺序命令表。先执行a,再执行b |
其他常用的Shell命令
命令 | 描述 |
---|---|
cut | 以指定的方式分割行,并输出特定的部分 |
diff | 找出两个文件的不同点 |
sort | 对输入的进行排序 |
uniq | 删除已经排好序的输入中的重复行 |
tr | 转换或删除字符 |
wc | 统计字符,单词和行的数量 |
substr | 提取字符串中的一部分 |
seq | 生成整数数列 |
Beijing 010 Shanghai 021 Tianjin 022 Hangzhou 0571
diff badpro badpro2 7c7 < sleep 2s --- > sleep 6s
7c7
表示 badpro的第7行和 badpro2的第7行是不同的。紧跟着diff列出了这两行不同的地方,左箭头<
后面紧跟着badpro中的内容,右箭头>
后面紧跟着badpro2中的内容,两者之间使用一些短划线分隔。Beijing 010 Shanghai 021 Tianjin 022 Hangzhou 0571 Shanghai 021
# sort city.txt | uniq Beijing 010 Hangzhou 0571 Shanghai 021 Tianjin 022
ABC DEF GHI jkl mno pqr StU vwx yz 12A Cft pOd Hct Yoz cc4
tr "A-C" "[Z*]" < alph.txt
wc命令的常用选项
选项 | 描述 |
---|---|
-c 或 --bytes | 显示字节数 |
-l 或 --lines | 显示行数 |
-L 或 --max-linc-length | 显示最长 行的长度 |
-w 或 --words | 显示单词数 |
–help | 显示帮助信息 |
步长
。下面的命令生成0~9的数列,递减排列,每次减3。
- 在用户的主目录下添加目录.trash 用作“回收站”﹔
- 在每次删除文件和目录前向用户确认;
- 将需要“删除”的文件和目录移动到~/.trash中。
下面是这个脚本的完整代码
##建立回收站机制 ##将需要删除的文件移动到~/.trash中 #!/bin/bash if [ ! -d ~/.trash ] then mkdir ~/.trash fi if [ $# -eq 0 ] then #提示 delete 的用法 echo "Usage: delete filel [file2 file3 ...]" else echo "You are about to delete these files:" echo $@ #要求用户确认是否删除这些文件,回答 N 或 n 放弃删除,其他字符表示确认 echo -n "Are you sure to do that [Y/n]:" read reply if [ "$reply" != "n" ] && [ "$reply" != "N" ] then for file in $@ do #判断文件或目录是否存在 if [ -f "$file" ] || [ -d "$file" ] then mv -b "$file" ~/.trash/ else echo "$file: No such file or directory" done #如果用户回答 N或n else echo "No file removed" fi fi
cp delete /bin/
不过,这个delete并不是那么完美。例如它不能够处理文件名中存在空格的情况。
大家可以尝试改进这个脚本程序,来满足自己的需求。事实上,如果感到Linux 中的某些命令不够顺手,完全可以“改造”它。然后通过定义别名和环境变量让系统认识这些修改。
整合不易还请大家一键三连评论一下~