linux_shell笔记(八)

来源:互联网 发布:全国小龙虾消费数据 编辑:程序博客网 时间:2024/04/30 23:52

<!--@page { margin: 2cm }P { margin-bottom: 0.21cm }-->

Bash 程序设计

控制结构:控制结构有if...thenfor...inwhileuntilcase语句。与控制结构配合使用的还有breakcontinue语句也可用于调整shell脚本中的命令执行顺序。

if...then

if test-command

then

command

fi

test内置命令:if测试test-command返回状态,并基于这个状态转移控制。if语句的结束由fi标记。

echo -n “word1:”

read word1

echo -n “word2:”

read word2

if test“$word1” = ”$word2”

then

echo “Match”

fi

echo “End ofprogram.”

 

上面echo利用'-n'选项来表示输出echo参数后不用换行。

bashtest是一个内置命令,也就是它是shell的一部分。同时还有一个单独的工具test。通常某个命令的工具版本不存在,而内置命令版本可用时,使用后者。test测试语句中使用的'='两边的空白字符不可以省略。

 

test的操作符'-eq'比较两个整数,特殊变量$#表示命令行参数的个数。

if test $# -eq0

then

echo “Youmust supply at least one argument.”

exit1

fi

echo “Programrunning.”

可以利用'infotest'得到完整的test用法信息:

测试文件类型:'-bFILE' '-c FILE' '-d FILE' '-f FILE' '-h FILE' '-L FILE' '-pFILE' '-S FILE' ‘-t FD’

访问权限测试:'-gFILE' '-k FILE' '-r FILE' '-u FILE' '-w FILE' '-x FILE' '-OFILE'

'-G FILE'

文件特征比较:'-eFILE' '-s FILE' 'FILE1 -nt FILE2' 'FILE1 -ot FILE2'

'FILE1 -efFILE2'

字符串测试:'-zSTRING' '-n STRING' 'STRING' 'STRING1 = STRING2'

'STRING1 !=STRING2'

整数测试:'ARG1-eq ARG2' 'ARG1 -ne ARG2' 'ARG1 -lt ARG2' 'ARG1 -le ARG2'

'ARG1 -gtARG2' 'ARG1 -ge ARG2'

测试条件连接词:'!EXPR' 'EXPR1 -a EXPR2' 'EXPR1 -o EXPR2'(注意中间的空白符号)

[]test同义可以把test的参数用方括号括起来,以代替在脚本中使用关键字test。括号两边必须有空白符

 

if [ $# -eq 0 ]

then

ehco “Usage: chkarg2 argument...” 1>&2

exit 1

fi

echo “Programrunning.”

exit 0

 

用法信息上面例子显示的信息称为用法信息,使用'1>&2'标记可把这个输出重定位到标准错误输出。在脚本正常运行后一般退出状态为0,如果退出状态非0表示遇到一个错误。

 

if...then...else

if test-command

then

commands

else

commands

fi

iftest-command;then

commands

else

commands

fi

 

下面的shell脚本,参数为文件名,该脚本将文件内容显示在终端上。如果第一个参数为'-v',该脚本将使用less命令分页显示。

if [ $# -eq 0 ]

then

echo “Usage:out [-v] filename...” 1>&2

exit 1

fi

if [ “$1” =“-v” ]

then

shift

less -- “$@”

else

cat -- “$@”

fi

在上面的脚本中,catless命令的'--'参数通知调用这些工具的命令行它后面再没有选项了,即不再把'--'后面以连字符开头的参数作为选项。‘rm-- -example.sh’(删除文件-example.sh).

shiftset对位置参数的操作。

 

if … then …elif

if test-command

then

commands

eliftest-command

then

commands

else

commands

fi

示例:

echo -n “Word1:”

read word1

echo -n “Word2:”

read word2

echo -n “Word3:”

read word3

 

if [ “$word1”= “$word2” -a “$word1” = “$word3” ]

then

echo “Match: words 1,2 & 3”

elif [“$word1” = “$word2” ]

then

echo “Match: word 1 & 2”

elif [“$word2” = “$word3” ]

then

echo “Match: word 2 & 3”

else

echo “NoMatch”

fi

上面的脚本第一个if语句使用布尔操作符AND(-a)作为test的一个参数。注意'=''['']'两边的空白符不可省略。

1#!/bin/bash

2#Identify links to a file

3# Usage:lnks file [directory]

4if [$# -eq 0 -o $# -gt 2 ]

5then

6echo"Usage:lnks file [directory]" 1>&2

7exit 1

8fi

9

10if [-d "$1" ]

11then

12echo"First argument cannot be a directory." 1>&2

13exit 1

14else

15file="$1"

16fi

17if [$# -eq 1 ]

18then

19directory="."

20elif [-d "$2" ]

21then

22directory="$2"

23else

24echo"Optional second argument must be a directory." 1>&2

25echo"Usage:lnks file [directory]." 1>&2

26exit 1

27fi

28

29#checkthat file exists and is a regular file:

30if [! -f "$file" ]

31then

32echo"lnks:$file not found or special file" 1>&2

33exit 1

34fi

35#checklink count on file

36set-- $(ls -l "$file")

37linkcnt=$2

38if ["$linkcnt" -eq 1 ]

39then

40echo"lnks : no other hard links to $file" 1>&2

41exit 0

42fi

43#get theinode of the given file

44set$(ls -i "$file")

45

46inode=$1

47#findand print the files with that incite number

48echo"lnks:using find to search for links..." 1>&2

49find"$directory" -xdev -inum $inode -print

50

黑体黄底命令行使用了命令替换$(command)

确定shell,用法消息,注释,测试参数。

内置命令set使用命令替换机制把位置参数设置为ls-l命令的输出。输出的第二个字段就是链接的数目,因此就将linkcnt就被设置为$2set中的参数'--'防止把'-ls-l'(第一个输出字段是对文件的访问许可,一般以字符‘-’开头)产生的信息作为选项。

比较与文件名关联的inode来确定多个文件名是否链接到同一个文件,这是一种好方法。

find工具用于查找满足其参数所指定条件的文件,查找的位置由第一个参数($directory)指定并搜索其所有子目录。余下的参数指定把值为$inode的文件的文件名送到标准输出。由于不同文件系统中的文件可能拥有相同的inode号,并且没有链接,find必须只查找同一文件系统中的目录。参数-xdev防止find命令搜索其他文件系统。

可以使用-x选项来帮助调试脚本。该选项使得shell执行每条命令前把命令显示出来。

可以在带参数'-x'shell中运行上面的shell脚本,在执行每条命令前先将该命令显示出来。要么为当前shell设置'-x'选项(使用set-x命令),以使所有脚本的命令在执行前都显示出来。要么只在当前执行脚本中通过shell使用'-x'选项。

>bash-x lnks.sh example

脚本中的每条命令前都会加上PS4变量的值,默认是加号'+'.因此可通过该值来区分是脚本输出还是调试输出。

通过在脚本的开始位置加入带有'-x'参数的set,就可以设置运行该脚本的shell'set-x'

若要关闭调试则引应使用命令:'set+x'

'set -x' 'set -xtrace'

控制结构for...in的语法如下:

 

for loop-indexin argument-list

do

commands

done

依照argument-list中的每个参数(将对应的每一个参数赋值给loop-index变量)重复执行dodone语句之间的命令。

例子1

1forfruit in apples oranges pears bananas

2do

3echo "$fruit"

4done

例子2

1for i in *

2do

3if[ -d "$i" ]

4then

5echo"$i"

6fi

7done

'*'匹配当前目录下所有可见文件名,与运行该shell脚本的当前目录有关,而与shell脚本所在目录无关。

 

for控制结构语法如下:

for loop-index

do

commands

done

 

loop-index用命令行参数中的每个参数值取代,重复执行dodone之间的语句。除了参数来源不同,该结构语法同'for...in'结构语法。

例子:

>cat -nfor_test.sh

1for arg

2do

3echo"$arg"

4done

5

>chmod a+rwxfor_test.sh

>./for_test.sh example example2 example4 example6

'forarg'隐含表示'forarg in “$@”',这里将”$@”扩展为一个命令行参数列表,如”$1””$2””$3”等。

>cat -nwhos.sh

1#!/bin/bash

2#adapted from finger.sh by Lee Sailer

3#UNIX/WORLD

4

5if [ $#-eq 0 ]

6then

7echo"Usage: whos id ..." 1>&2

8exit 1

9fi

10

11for id

12do

13gawk-F: ' {print $1,$5} ' /etc/passwd |

14grep-i "$id"

15done

16

>./whos.shroot “terry Zeng”

脚本whos演示了for结构中$@符号所代表的含义。whos脚本后面可以跟一个或几个用户名作为参数,执行whos脚本就可以将这些与用户相关的信息显示出来。

在这个脚本中的for循环中隐含的$@的使用特别有效果,因为它使得for循环可以把带空格的参数视为一个单独的参数(如参数”terry Zeng”)

对于每个命令行参数,whos搜索/etc/passwd文件。在for循环结构中gawk的作用是从文件/etc/passwd的行中提取第一个($1)和第5($5)字段的内容。第1个和第5个字段的内容通过一个管道传递给grepgrep在其输入中搜索内容为$id($id就是输入的命令行参数)的对象。选项'-i'的作用是让grep在搜索过程中忽略其他的事件,grep在其输入中按照每行的格式显示包含$id内容的对象。

行末的| 即便在管道标志后面还有换行,管道也能照常工作。

 

while

while控制结构语法如下:

whiletest-command

do

commands

done

只要测试条件的返回值为真,while结构语句就要执行dodone语句之间的命令。

 

例子:

>cat -ncount.sh

1#!/bin/bash

2number=0

3while ["$number" -lt 10 ]

4do

5echo -n"$number"

6((number+= 1))

7done

8echo

test内置命令:脚本使用了'-lt'来执行数值比较测试。对于数值比较测试由以下几种测试选项:'-ne','-eq','-gt','-ge','-lt','-le'。对于字符串的比较可以用'='或者'!='来进行测试比较。

使用'-n'选项用来防止echo在其输出之后输出换行。最后的echo使脚本在标准输出上输出一个新的字符行。((number+= 1))赋值表达式。

例子:

1#!/bin/bash

2# removecorrect spellings from aspell output

3

4if [ $#-ne 2 ]

5then

6echo"Usage spell_check.sh file1 file2" 1>&2

7echo"file1 list of correct spellings" 1>&2

8echo"file2 file to be checked" 1>&2

9exit 1

10fi

11

12if [! -r "$1" ]

13then

14echo"spell ..check; $1 is not readable" 1>&2

15exit 1

16fi

17

18if [ !-r "$2" ]

19then

20echo"spell ..check; $2 is not readable" 1>&2

21exit 1

22fi

23

24aspell-l < "$2" |

25whileread line

26do

27if !grep "^$line$" "$1" > /dev/null

28then

29echo$line

30fi

31done

32

上面的脚本中'-r'参数判断一个文件是否是可读的,括号里的感叹号是对跟着的操作符进行相反的运算,与'-r'参数联合起来用以判断一个文件是否是不可读的。

上面的脚本把aspell脚本的输出(使用参数-l可以让aspell脚本把所检查出的错误单词的列表送到标准输出上)通过一个管道送到while结构的标准输入上,while结构从他的标准输入上一次读入一行内容(每行只有一个单词)。只要测试条件(也就是readline)能从标准输入上得到一个单词,那么它就返回一个true状态。

while循环中if语句用来检测grep条件测试的返回值,grep是用来判断被读取行是否在用户的正确单词列表中。grep搜索的模式(也就是$line)前后都有特殊字符,这些字符分别用来指明一行的开始和结束(分别是'^''$''^$line$'理解为'^---$line---$'其中'^'代表行的开始,第一个'$'代表对变量'line'的引用,第二个'$'代表行的结束)这些特殊符号的作用是确保grep搜索时只有当变量$line的内容与用户输入的正确单词列表的一整行的内容相同时才形成匹配。grep的标准输出被重定向到文件/dev/null中,因为我们不关心输出结果,只关心返回状态。

' if ! grep "^$line$" "$1" > /dev/null '可以替换为'if ! grep -qw “$line” “$1”'其中'-q'抑制了grep的输出,这样只返回退出状态。'-w'使得grep只匹配整个单词。

 

until

untilwhile语句的语法结构相似。区别仅在于条件语句的测试位置:一个在语句的开始测试,一个在语句的结束测试。

untiltest-commands

do

commands

done

例子:

1#!/bin/bash

2secretname=jenny

3name=noname

4echo"Try to guess the secret name!"

5echo

6until ["$name" = "$secretname" ]

7do

8echo -n"Your guess:"

9readname

10done

11echo"Very good."

12

例子:

1#!/bin/bash

2#UNIX/WORLD

3trap ' '1 2 3 18

4stty-echo

5echo -n"Key: "

6readkey_1

7echo

8echo -n"Again:"

9readkey_2

10echo

11key3=

12if ["$key_1" = "$key_2" ]

13then

14tputclear

15until[ "$key_3" = "$key_2" ]

16do

17readkey_3

18done

19else

20echo"locktty: keys do not match" 1>&2

21fi

22sttyecho

23

如果运行上面的例子而忘记了脚本密码,可以登录另一个虚拟终端并终止该进程。

trap内置命令:利用trap内置命令可以防止用户通过发送中断的方式来终止脚本的运行。通过捕获信号18可以保证用户无法用CONTROL+Z组合键来挂起运行该脚本的进程。stty-echo命令来防止终端把键盘输入的字符显示出来,这样可以保证输入的密码不被显示出来。

breakcontinue:同c语言可以用来中断forwhile until语句的执行

1#!/bin/bash

2forindex in 1 2 3 4 5 6 7 8 9

3do

4if [$index -le 3 ]

5then

6echo"continue"

7continue

8fi

9echo$index

10if [$index -ge 8 ]

11then

12echo"break"

13break

14fi

15done

16

 

case:一种多分支选择机制,同C语言。

casetest-string in

pattern-1)

commands-1

;;

pattern-2)

commands-2

;;

pattern-3)

commands-3

;;

...

esac

case结构中的匹配类型类似于一个模糊文件引用。

字符‘*’:匹配任意字符串,用作默认的case匹配。

字符‘?’:匹配 单个字符。

[…]:定义一个字符类,对处于方括号中的每个字符依次进行单字符匹配;两个字符之间的连字符用来指定字符范围。

| :分离带有选择的选项,这些选项满足case结构的一个特别的分支。

例子:

1echo -n"Enter A,B,or C:"

2readletter

3case"$letter" in

4a|A)

5echo"You Entered A"

6;;

7b|B)

8echo"You Entered B"

9;;

10c|C)

11echo"You Entered C"

12;;

13*)

14echo"You did not enter A,B,or C"

15;;

16esac

例子:

1#!/bin/bash

2

3echo-e "/n COMMAND MENU/n"

4echo"a.Current date and time"

5echo"b.Users currently logged in"

6echo"c.Name of the working directory"

7echo-e "d.Contents of the working directory/n"

8echo-n "Enter a,b,c,or d:"

9readanswer

10echo

11case"$answer" in

12a)

13date

14;;

15b)

16who

17;;

18c)

19pwd

20;;

21d)

22ls

23;;

24*)

25echo"There is no selection:$answer"

26;;

27esac

'echo -e'选项-e使echo把后面的'/n'解释为一个换行符,如果echo后面不加这个参数'-e'echo就会输出两个字符'/n',而不是一个空行。参数'-e'使得echo解释由反斜杠'/'转义的字符。带有反斜杠的字符一定要引起来,否则反斜杠就要由shell来解释而不会传到echoecho解释。

 

例子:

>cat -nsafedit

1#!/bin/bash

2PATH=/bin:/usr/bin

3script=$(basename$0)

4case $#in

50)

6vim

7exit 0

8;;

91)

10if [ !-f "$1" ]

11then

12vim"$1"

13exit0

14fi

15if [ !-r "$1" -o ! -w "$1" ]

16then

17echo"$script:check permissions on $1" 1>&2

18exit1

19else

20editfile="$1"

21fi

22if [ !-w "." ]

23then

24echo"$script:backup cannont be"/

25"createdin the working directory" 1>&2

26exit1

27fi

28;;

29*)

30echo"Usage:$script [file-to-edit]" 1>&2

31exit 1

32;;

33esac

34

35tempfile=/tmp/$$.$script

36cp$editfile $tempfile

37if vim$editfile

38then

39mv$tempfilebak.$(basename $editfile)

40echo"$script:backup file created"

41else

42mv$tempfileediterr

43echo"$script:edit error--copy of" /

44"originalfile is in editerr"1>&2

45fi

46

>chmod u+xsafedit

>./safeditexample.sh

再另一个控制台上进入tmp目录可以查看到一个nxxn.safedit的临时文件,回到原来的控制台退出vim编辑器,查看当前控制台当前工作目录下会生成一个备份文件格式bak.example.sh。再打开新的控制台查看/tmp目录发现最开始生成的临时文件已经被删除。

>ln safeditsafedit.ln

>./safedit.lnexample.sh

注意观察该命令行运行的过程,生成的中间文件,最后结果与上面文字描述的不同。

设置PATH变量:该脚本设置了PATH变量,目的是为了保证脚本中执行的命令是系统目录中的标准命令。避免了用户自己可能设置的PATH包含自己定义的目录,而在该自定义目录下,用户可能编写了一些与脚本中调用命令同名的脚本或程序。

程序名:basename去掉前导的目录部分后打印名称,因此脚本中将script设置为运行脚本的基本名称。通过命令替换实现。$0存储脚本被调用时的命令。好处在于对脚本进行重命名或则创建链接后运行该脚本会得到正确的提示信息。

给临时文件命名:脚本中设置变量tempfile为临时文件名,以shell进程的PID号作为开始并以脚本的名字作为结束。使用PID号是为了确保文件名的唯一性,脚本名字附在临时文件名后面,为了让用户知道其来源。PID号放在前面是由于一些老版本unix上对文件名有长度限制,而PID号可以确保其唯一性,所以放在前面避免由于文件名字长的限制而被切去。

测试条件:脚本中使用了一个测试条件vim$editfile。测试vim编辑器编辑文件完成后返回的exit代码,if控制结构就是利用这个exit代码来决定分支。成功则返回0then被执行;不成功则返回非0else语句被执行。

 

select:首先显示一个菜单,然后根据用户的选择给变量赋予相应的值,最后执行一系列命令。

select varname[in arg ….]

do

command

done

 

例子:

1#!/bin/bash

2PS3='Chooseyour favorite fruit from these from these possibilities:'

3selectFRUITin apple banana blueberry kiwi orange watermelon STOP

4do

5if ["$FRUIT" = "" ];then

6echo-e "Invalid entry./n"

7continue

8elif ["$FRUIT" = STOP ];then

9echo"Thanks for playing."

10break

11fi

12echo"You Chose $FRUIT as your favorite."

13echo -e"That is choice number $REPLY./n"

14done

15

PS3:是select特有的提示符,在select语句输出菜单后,就会显示出PS3的值。slect不断的发出PS3提示并按照用户的输入执行命令,直到有事情使其停止。

selectvarname设置为输入的值(例子中是变量FRUIT),同时将用户的响应存储在键盘变量REPLY中。如果用用户非法输入,shellvarname设置为空($FRUIT)


原创粉丝点击