为什么要使用shell编程? shell脚本在处理自动循环或大的任务方面可节省大量的时间,且功能强大。如果你有处理一个任务的命令清单,不得不一个一个敲进去,然后观察输出结果,再决定它是否正确,如果正确,再继续下一个任务,否则再回到清单一步步观察。一个任务可能是将文件分类、向文件插入文本、迁移文件、从文件中删除行、清除系统过期文件、以及系统一般的管理维护工作等等。创建一个脚本,在使用一系列系统命令的同时,可以使用变量、条件、算术和循环快速创建脚本以完成相应工作。这比在命令行下一个个敲入要节省大量的工作时间。
Shell脚本可以在行命令中接收信息,并使用它作为另一个命令的输入。 【范例】:假设有一个清除日志文件的脚本cleanlog,文件内容如下,如果需要执行这个脚本,首先需要为脚本增加相应的权限:chmod 766 cleanlog #!/bin/sh #name : cleanlog #this is a general cleanlog scrīpt echo "starting cleanlog ... wait" rm /usr/test/log/*.log echo "finished cleanup!"
正则表达式 随着对UNIX和LINUX熟悉程度的不断加深,需要经常接触到正则表达式这个领域,当从一个文件或命令输出中抽取或过滤文本时,可以使用正则表达式( R E),正则表达式是一些特殊或不很特殊的字符串模式的集合。为了抽取或获得信息,我们给出抽取操作应遵守的一些规则。这些规则由一些特殊字符或进行模式匹配操作时使用的元字符组成。也可以使用规则字符作为模式中的一部分进行搜寻。例如,A将查询A,x将查找字母x。 1.正则表达式元字符 元字符 功能 实例 解释 ^ 行开头定位 /^love/ 与所有love开头的行匹配 $ 行末尾定位 /love$/ 与所有love结尾的行匹配,匹配单个字符 /l..e/ 与包含一个l,后跟两个字符,然后跟一个e的行相匹配 * 跟前驱的0个或多个字符相匹配 / *love/ 跟0个或多个空格后面的love模式的行相匹配 [] 与其中的一个相匹配 /[Ll]ove/ 与包含love或者Love的行匹配 [x-z] 与集中一个范围内的一个字符相匹配 /[A-Z]ove/ 与后面跟ove的从A到Z的字相匹配 [^] 与不在集里的字符匹配 /[^A-Z]ove/ 不包括A到Z,后面跟ove的字相匹配 \ 给一个元字符转移 /love\./ 匹配行包括love,跟一个句点 许多用RE元字符的UNIX程序支持的附加元字符(vi 和 grep支持) \< 词开头定位 /\<love/ 匹配行包含用love开头的词 \> 词结尾定位 /love\>/ 匹配行包含love结尾的词 \(..\) 标志与以后用的字符相匹配 /(love\)able\ler/ Able可达9个标志,模式最左边用第一个标志开始。例如,模式love保存作标志l,以后引用作\l;在这个例子中,搜索模式包括后面跟lover的lovable x\{m\} x\{m,\} x\{m,n\} 字符x重复m次 至少m次 m到n次 O\{5,10\} 如果行包含5—10个连续的o则匹配 综合举例,先用vi编辑一个文件testing,内容如下(特别说明,行前面的数字只是为了标识行号,不是实际的内容): 1 |Christian Scott lives here and will put on a Christmas party.| 2 |There are around 30 to 35 people invited.| 3 |They are: | 4 | Tom| 5 |Dan| 6 | Rhonda Savage| 7 |Nicky and Kimerly.| 8 |Steve, Suzanne, Ginger and Larry.| 【案例1】: /^[A-Z]..$/ 搜索行以A至Z的一个字母开头,然后跟两个任意字母,然后跟一个换行符的行。将找到第5行。
【案例2】: /^[A-Z][a-z]*3[0-5]/ 搜索以一个大写字母开头,后跟0个或多个小写字母,再跟数字3,再跟0—5之间的一个数字。将找到第2行。
【案例3】:*[A-Z][a-z][a-z]$/ 搜索以0个或多个空格开头,跟一个大写字母,两个小写字母和一个换车符。将找到第4行的TOM(整行匹配)和第5行。注意,*前面有一个空格。
【案例4】:/^[A-Za-z]*[^,][A-Za-z]*$/ 将查找以0个或多个大写或小写字母开头,不跟逗号,然后跟0个或多个大写或小写字母,然后跟一个换车符。将找到第5行。
awk命令 如果要格式化报文或从一个大的文本文件中抽取数据包,那么awk可以完成这些任务。它在文本浏览和数据的熟练使用上性能优异。整体来说,awk是所有shell过滤工具中最难掌握的,不知道为什么,也许是其复杂的语法,也许是因为awk本身是学习的好例子,但结合awk与其他工具诸如grep和sed,将会使shell编程更加容易。
【案例1】:抽取域功能 先编辑好一个文件比如mylsfile,内容如下,文件总共有9个域,因为域间使用空格区分,所以不必用-F选项划分域:如果我们要不最后一个域的内容输出,我们可以把他重定向向到一个文件:# awk '{print $9}' mylsfile > mylsfilelast drwxr-xr-x 4 root root 4096 11月 3 23:32 . drwxr-xr-x 16 root root 4096 11月 3 14:43 .. -rw-r--r-- 1 root root 0 11月 3 22:58 clean -rwxrw-rw- 1 root root 144 11月 3 22:57 cleanlog -rw-r--r-- 1 root root 0 11月 3 23:32 mylsfile -rw-r--r-- 1 root root 18 11月 3 14:43 mytest.txt drwxr-xr-x 3 root root 4096 11月 3 17:19 test -rw-r--r-- 1 root root 123 11月 3 23:15 testing -rwxrw-rw- 1 root root 10538 11月 3 21:54 test.sh drwxr-xr-x 2 root root 4096 11月 3 15:43 Webmin
【案例2】:匹配正则表达式 操作符 描述 操作符 描述 < 小于 >= 大于等于 <= 小于等于 ~ 匹配正则表达式 == 等于 !~ 不匹配正则表达式 != 不等于 【案例3】:比较域 1.打印awkfile文件中含有Brown的行信息 # awk '$0 ~/Brown/' awkfile 2.打印awkfile文件中第三域中含有48的行信息
# awk '{if($3~/48/) print $0}' awkfile 3.打印awkfile文件中第三域中只有48的行信息 # awk '$3=="48" {print $0}' awkfile 4.打印awkfile文件中第四个域等于Brown-2的行信息 # awk '$4!="Brown-2" {print $0}' awkfile # awk '{if($6<$7) print $0 "$1 Try better at the next time"}' awkfile # awk '{if($6<=$7) print $1}' awkfile 复合模式或复合操作符用于形成复杂的逻辑操作,复杂程度取决于编程者本人。有必要 了解的是,复合表达式即为模式间通过使用下述各表达式互相结合起来的表达式: && AND : 语句两边必须同时匹配为真。 || O R:语句两边同时或其中一边匹配为真。 ! 非求逆
【案例】: 1.打印awkfile文件中第一个域为Johmn而且第四域为Yello-2的行信息 # awk '{if($1=="Johmn" && $4=="Yello-2") print $0}' awkfile 2. 打印awkfile文件第四域为Brown-2或Yello-2的行信息 # awk '{if($4=="Brown-2" || $4=="Yello-2") print $0}' awkfile 1.4 grep命令 相信grep是UNIT和LINUX中使用最广泛的命令之一。grep(全局正则表达式版本)允许 对文本文件进行模式查找。如果找到匹配模式, grep打印包含模式的所有行。grep支持基本正则表达式,也支持其扩展集。grep有三种变形,即: grep G r e p:标准grep命令,本章大部分篇幅集中讨论此格式。 egrep:扩展grep,支持基本及扩展的正则表达式,但不支持\ q模式范围的应用,与之相对应的一些更加规范的模式,这里也不予讨论。 fgrep:快速grep。允许查找字符串而不是一个模式。不要误解单词fast,实际上它与grep 速度相当。
【案例】: 1. -c 只输出匹配行的计数。 # grep -c "who" until_who 2. -i 不区分大小写(只适用于单字符)。 # grep –i "who" until_who 3. -h 查询多文件时不显示文件名。 # grep -h "who" until_who test.txt 4. -l 查询多文件时只输出包含匹配字符的文件名。 # grep -l "who" until_who test.txt 5. -n 显示匹配行及行号。 # grep –n "who" until_who 6. -v 显示不包含匹配文本的所有行。 # grep –v "who" until_who 7. -s 不显示不存在或无匹配文本的错误信息。
# grep -s "who" until_who 8.grep与正则表达式一起搭配使用。比如 # grep '[Ss]ept' grepfile 1.5 sed命令 sed是一个非交互性文本流编辑器。它编辑文件或标准输入导出的文本拷贝。标准输入可 能是来自键盘、文件重定向、字符串或变量,或者是一个管道的文本。sed可以做些什么呢? 别忘了,vi也是一个文本编辑器。sed可以随意编辑小或大的文件,有许多sed命令用来编辑、 删除,并允许做这项工作时不在现场。sed一次性处理所有改变,因而变得很有效,对用户来讲,最重要的是节省了时间。 基本sed编辑命令 p 打印匹配行 = 显示文件行号 a \ 在定位行号后附加新文本信息 i \ 在定位行号后插入新文本信息 d 删除定位行 c \ 用新文本替换定位文本 s 使用替换模式替换相应模式 r 从另一个文件中读文本 w 写文本到一个文件 q 第一个模式匹配完成后推出或立即推出 l 显示与八进制A S C I I代码等价的控制字符 { } 在定位行执行的命令组 n 从另一个文件中读文本下一行,并附加在下一行 g 将模式2粘贴到/pattern n/ y 传送字符 n 延续到下一输入行;允许跨行的模式匹配语句
【案例】: 打印输出until_who文件第二行信息 # sed -n '2p' until_who 打印输出until_who文件的第1到第3行信息 # sed -n '1,4p' until_who 打印输出until_who文件中含有who单词的行信息 # sed -n '/who/'p until_who 匹配元字符$前,必须使用反斜线\屏蔽其特殊含义 # sed -n '/\$/'p until_who 显示整个文件 # sed -n '1,$p' until_who 匹配任意字符 # sed -n '/.*who/'p until_who
下面是一些一行命令集。([]表示空格, [ ]表示t a b键) ‘s / \ . $ / / g’ 删除以句点结尾行 ‘-e /abcd/d’ 删除包含a b c d的行 ‘s / [] [] [] * / [] / g’ 删除一个以上空格,用一个空格代替 ‘s / ^ [] [] * / / g’ 删除行首空格 ‘s / \ . [] [] * / [] / g’ 删除句点后跟两个或更多空格,代之以一个空格 ‘/ ^ $ / d’ 删除空行 ‘s / ^ . / / g’ 删除第一个字符 ‘s /CO L \ ( . . . \ ) / / g’ 删除紧跟C O L的后三个字母 ‘s / ^ \ / / / g’ 从路径中删除第一个\ ‘s / [] / [ ] / / g’ 删除所有空格并用t a b键替代 ‘S / ^ [ ] / / g’ 删除行首所有t a b键 ‘s / [ ] * / / g’ 删除所有t a b键 shell编程基础
用户登陆进入系统后的系统环境变量: $HOME 使用者自己的目录 $PATH 执行命令时所搜寻的目录 $TZ 时区 $MAILCHECK 每隔多少秒检查是否有新的信件 $PS1 在命令列时的提示号 $PS2 当命令尚未打完时,Shell 要求再输入时的提示号 $MANPATH man 指令的搜寻路径
特殊变量: $0 这个程序的执行名字 $n 这个程序的第n个参数值,n=1..9 $* 这个程序的所有参数 $# 这个程序的参数个数 $$ 这个程序的PID $! 执行上一个指令的PID $? 执行上一个指令的返回值
shell中的变元: * 任意字符串 ? 一个任意字符 [abc] a, b, c三者中之一 [a-n] 从a到n的任一字符
几个特殊字符表示 \b 退回
\c 打印一行时没有换行符 这个我们经常会用到 \f 换页 \r 回车 \t 制表 \v 垂直制表 \\ 反斜线本身
判断文件的属性 格式:-操作符 filename -e 文件存在返回0,否则返回1 -r 文件可读返回0,否则返回1 -w 文件可写返回0,否则返回1 -x 文件可执行返回0,否则返回1 -o 文件属于用户本人返回0, 否则返回1 -z 文件长度为0返回0, 否则返回1. -f 文件为普通文件返回0, 否则返回1 -d 文件为目录文件时返回0, 否则返回1 -s 文件长度大于0、非空返回0,否则返回1
测试字符串 字符串1 = 字符串2 当两个字串相等时为真 字符串1 != 字符串2 当两个字串不等时为真 -n 字符串 当字符串的长度大于0(非空串)时为真 -z 字符串 当字符串的长度为0(空串)时为真 # test1="this is a test" # test2="this is a test" # [ "$test1" = "$test2" ] echo $? 得到的结果应该为0,表示两个串的值为相等
测试两个整数关系 数字1 -eq 数字2 两数相等为真 数字1 -ne 数字2 两数不等为真 数字1 -gt 数字2 数字1大于数字2为真 数字1 -ge 数字2 数字1大于等于数字2为真 数字1 -lt 数字2 数字1小于数字2为真 数字1 -le 数字2 数字1小于等于数字2为真
逻辑测试 -a 逻辑与,操作符两边均为真,结果为真,否则为假。 -o 操作符两边一边为真,结果为真,否则为假。 ! 逻辑否,条件为假,结果为真。
shell编程控制结构 任何命令进行时都将返回一个退出状态。如果要观察其退出状态,使用最后状态命令: # echo $?
【案例1】: #!/bin/sh #make a directory mkdir /usr/test/test #copy all logs files cp *.log /usr/test/logs #delete all logs file rm *.logs 上述脚本问题出在哪里?如果目录创建失败或目录创建成功文件拷贝失败,如何处理? 这里需要从不同的目录中拷贝不同的文件。必须在命令执行前或最后的命令退出前决定处理 方法。s h e l l会提供一系列命令声明语句等补救措施来帮助你在命令成功或失败时,或需要处理一个命令清单时采取正确的动作。这些命令语句大概分两类:循环和流控制。
流控制 if、then、else语句提供条件测试。测试可以基于各种条件。例如文件的权限、长度、数值或字符串的比较。这些测试返回值或者为真(0),或者为假(1)。基于此结果,可以进行 相关操作。case语句允许匹配模式、单词或值。一旦模式或值匹配,就可以基于这个匹配条件作其他声明。 最普通的if语句是: if条件 then 命令 fi 使用i f语句时,必须将t h e n部分放在新行,否则会产生错误。如果要不分行,必须使用命 令分隔符。本书其余部分将采取这种形式。现在简单i f语句变为: if 条件;then 命令 fi 注意,语句可以不这样缩排,但建议这样做,因为可以增强脚本的清晰程度。 【案例1】: #!/bin/sh #iftest #this is a iftest case if [ "10" -lt "12" ] ; then #yes 10 is less than 12 echo "Yes, 10 is less than 12 " fi
流控制 if语句测试条件,测试条件返回真(0)或假(1)后,可相应执行一系列语句。i f语句结构对错误检查非常有用。其格式为: if 条件1 then 命令1 elif 条件2 then 命令2 else 命令3 f i if 条件1;then 命令1 elif 条件2;then 命令2 else 命令3 f i 让我们来具体讲解i f语句的各部分功能。 if 条件1 如果条件1为真 then 那么 命令1 执行命令1 elif 条件2 如果条件1不成立 then 那么 命令2 执行命令2 else 如果条件1,2均不成立 命令3 那么执行命令3 fi 完成 【案例2】: #!/bin/sh #name: grepstr echo -n "Enter a list of names:" read list if echo $list | grep "Xulinlin" > /dev/null 2>&1 ; then echo "Xulinlin is here!" else echo "Xulinlin is not in the list" fi #!/bin/sh #name: grepstr echo -n "Enter a list of names:" read list echo $list | grep "Xulinlin" > /dev/null 2>&1 if $? ; then echo "Xulinlin is here!" else echo "Xulinlin is not in the list" fi #!/bin/sh
#ifcp if cp /usr/test/test.txt /usr/test/testbak.txt; then echo "well copyed" else echo "Error, could not copy the files" fi
流控制 if 语句也可以嵌套使用 if 条件表达式1 then if 条件表达式2 then 命令列表 else if 条件表达式3 then 命令列表 else 命令列表 fi fi else 命令列表 Fi
【案例3】: #!/bin/sh #ifparam if [ $# -lt 3 ]; then #less than 3 parameters called,echo a message and exit echo "Usage: 'basename $0' arg1 arg2 arg3" >&2 exit 1 fi #good, received 3 params,echo them echo "arg1: $1" echo "arg1: $2" echo "arg1: $3" #!/bin/sh #ifroot if [ "$LOGNAME" != "root" ]; then echo "You need to be root to run this scrīpt" >&2
exit 1 else echo "Yes indeed you are $LOGNAME proceed" fi #!/bin/sh #ifmkdir #parameter is passed as $1 but reassigned to DIR DIR=$1 #if the string empty ? if [ "DIR" = "" ]; then echo "DIR name is not assigned" >&2 exit 1 fi if [ -d $DIR ] ; then echo "The dir does exist" else echo "The dir does not exist" echo -n "Create it now [y..n] :" read ANS if [ "$ANS" = "y" ] || [ "$ANS" = "Y" ]; then echo "Creating now" mkdir /usr/test/$DIR >/dev/null 2>&1 if [ $? != 0 ]; then echo "Errors Creating the dir $DIR" >&2 exit 1 fi fi fi
Case case语句为多选择语句。可以用case语句匹配一个值与一个模式,如果匹配成功,执行相 匹配的命令。case语句格式如下: case 值i n 模式1 } 命令1 . . . ; ; 模式2) 命令2 . . . ; ; e s a c
c a s e工作方式如上所示。取值后面必须为单词in,每一模式必须以右括号结束。取值可以 为变量或常数。匹配发现取值符合某一模式后,其间所有命令开始执行直至;;。 取值将检测匹配的每一个模式。一旦模式匹配,则执行完匹配模式相应命令后不再继续 其他模式。如果无一匹配模式,使用星号*捕获该值,再接受其他输入。 模式部分可能包括元字符,与在命令行文件扩展名例子中使用过的匹配模式类型相同, 即: * 任意字符。 ? 任意单字符。 [..] 类或范围中任意字符。
【案例1】: #!/bin/sh #caseselect echo -n "Input a number between 1 and 5" read ANS case $ANS in 1) echo "you select 1" ;; 2) echo "you select 2" ;; 3) echo "you select 3" ;; 4) echo "you select 4" ;; 5) echo "you select 5" ;; *) echo "'basename $0' : this is not between 1 to 5" >&2 exit 1 ;; esac #!/bin/sh #caseans echo -n "Do you wish to proceed [y..n] :" read ANS case $ANS in y|Y|yes|Yes) echo "yes is selected" ;; n|N|no|No) echo "no is selected" exit 0 ;; *) echo "'basname $0': unknown response" >&2 exit 1 ;; esac
#!/bin/sh #caseparam if [ $# != 1 ]; then echo "usage: 'basename $0' [start|stop|help]" >&2 exit 1 fi ōPT=$1 case $OPT in start) echo "Starting .. 'basename $0'" ;; stop) echo "Stopping .. 'basename $0'" ;; help) echo "Helping .. 'basename $0'" ;; *) echo "Usage: 'basename $0' [start|stop|help]" ;; Esac 1.7.5 循环结构 f o r循环一般格式为: for 变量名i n列表 d o 命令1 命令2⋯ d o n e
【案例1】: #!/bin/sh #for_i for loop in 1 2 3 4 5 6 7 8 9 do echo $loop done #!/bin/sh #forlist for loop in "beijing shanghai shenzhen guangzhou" do echo $loop done #!/bin/sh
#forparam for params do echo "You supplied $params as a command line option" done echo $params #!/bin/sh #forcount counter=0 for files in * do counter=`expr $counter + 1` done echo "There are $counter files in `pwd` we need to process" until循环执行一系列命令直至条件为真时停止。until循环与while循环在处理方式上刚好相反。一般while循环优于until循环,但在某些时候—也只是极少数情况下, until循环更加有用。 until循环格式为: until 条件 命令1 . . . done 条件可为任意测试条件,测试发生在循环末尾,因此循环至少执行一次—请注意这一点。
【案例1】: #!/bin/sh #until_who IS_ROOT=`who | grep root` until [ "$IS_ROOT" ] do sleep 5 done echo "Watch it. roots in" #!/bin/sh #until_mon LOOK_OUT=`df | grep /logs | awk '{print $5}' | sed 's/%//g'` echo $LOOK_OUT until [ "$LOOK_OUT" -gt "90" ] do echo "Filesystem .. logs is nearly full" | mail root exit 0 done
while循环用于不断执行一系列命令,也用于从输入文件中读取数据,其格式为: while 命令 do 命令1 命令2 . . . done 虽然通常只使用一个命令,但在while和do之间可以放几个命令。命令通常用作测试条 件。 只有当命令的退出状态为0时,do和done之间命令才被执行,如果退出状态不是0,则循 环终止。命令执行完毕,控制返回循环顶部,从头开始直至测试条件为假。
【案例】: #!/bin/sh #whileread echo "Type <CTRL-D> to terminate" echo -n "Input you most like sports" while read SPORT do echo "Yeah, great sports the $SPORT" done #!/bin/sh #breakout while : do echo -n "enter any number [1..5]" read ANS case $ANS in 1|2|3|4|5) echo "well you enterd a number between 1 and 5" ;; *) echo "Wrong number ..bye" break ;; esac done 1.8 shell函数介绍 shell允许将一组命令集或语句形成一个可用块,这些块称为shell函数。 函数由两部分组成: 函数标题。 函数体。 标题是函数名。函数体是函数内的命令集合。标题名应该唯一;如果不是,将会混淆结
果,因为脚本在查看调用脚本前将首先搜索函数调用相应的s h e l l。 定义函数的格式为: 函数名() { 命令1 . . . } 或者 函数名(){ 命令1 . . . } 两者方式都可行。如果愿意,可在函数名前加上关键字function,这取决于使用者。 function 函数名() { ... } 可以将函数看作是脚本中的一段代码,但是有一个主要区别。执行函数时,它保留当前 shell和内存信息。此外如果执行或调用一个脚本文件中的另一段代码,将创建一个单独的 shell,因而去除所有原脚本中定义的存在变量。
【案例1】: #!/bin/sh #func1 hello () { echo "Hello,welcome to 51testing" } echo "Now going to the function hello" hello echo "back from the function"