第十二章 使用结构化命令

根据条件使脚本跳过某些命令,这样的命令称为结构化命令(structured command)。结构化命令允许改变程序执行的顺序。

If-then语句:

If command

Then

Commands

Fi

如果if后的command执行退出码是0(也就是执行成功了),then后面的语句就会被执行。

也可以写成:

If command; then

Commands

Fi

注意了,if后的command结果是会被输出的。(执行脚本后)

If-then-else语句:

If command

Then

Commands

Else

Commands

Fi

If-then-elif-then-else

If command1

Then

Commands

Elif command2

Then

Commands

Else

Commands

Fi

多个elif嵌套:

If command1

Then

Command set 1

Elif command2

Then

Command set 2

Elif command3

Then

Command set 3

Elif command4

Then

Command set 4

Fi

test语句也是用于判断的,语法如下:

Test condition

if一起配合使用:

If test condition

Then

Commands

Fi

If test $var,这表示测试var是否空值,如果空值,表示假,不执行then后面的语句,如果非空值,表示真,执行then后面的语句。

也可以使用if [ condition ]方法代替。

test命令可以判断三类条件:

数值比较、字符串比较、文件比较。

数值比较:

-eq,等于。

-ge,大于或者等于。

-gt,大于。

-le,小于或者等于。

-lt,小于。

-ne,不等于。

注意,数值比较只能是整数。

字符串比较:

=,相同

!=,不相同

<,小于

>,大于

-n strstr长度非0

-z strstr长度为0或者没定义。

注意><使用时,需要转义,不然成了重定向符号。字符串比较中,大写是小于小写的。

文件比较:

-d file,是否存在并且是个目录

-e file,是否存在

-f file,是否存在并是个文件

-r file,是否可读

-s file,存在并且非空

-w file,存在并且可写

-x file,存在并且可执行

-O file,是否存在并且属于当前用户所有

-G file,是否存在并且默认组与当前用户相同

File1 -nt file2file1是否比file2新,针对编写软件安装很有效。比较的是创建日期。比如系统上不愿意安装一个比现在还旧的文件。

File1 -ot file2file1是否比file2旧,针对编写软件安装很有效。比较的是创建日期。

复合条件测试:

[ condition 1 ] && [ condition2 ],两个条件都满足,才执行then

[ condition 1 ] || [ condition2 ],一个条件满足,就执行then。

If-then语句使用高级功能:

用于数学表达式的双括号、用于高级字符串处理功能的双方括号。

双括号允许使用更多的数学符号:

Val++,后增

Val--,后减

++val,先增

--val,先减

!,逻辑求反

~,位求反

**,幂运算

<<,左位移

>>,右位移

&,位布尔和

|,位布尔或

&&,逻辑和

||,逻辑或

双括号可以用于任意的数学赋值或者比较表达式。test一般是用标准的数学运算符。

比如:

#!/bin/bash

Val1=10

If (( $val1 ** 2 > 90 ))

Then

(( var2 = $var1 ** 2 ))

Fi

可以看到,这里的大于号,不需要转义,这是双括号的特性。

双方括号针对了字符串比较的高级特性。语法如下:

` expression `

可以使用匹配模式。比如:

#!/bin/bash

If   [[ $USER = r* ]]

Then

Echo "Hello $USER"

Fi

case语句适合多个elif的这种语句模式,语法如下:

Case var in

Pattern 1 | pattern 2) commands1;;

Pattern3) commands2;;

*) default commands;;

Esac

 

第十三章 更多的结构化命令

for语句允许创建一个遍历一系列值得循环,每次迭代都是用其中一个值来执行已定义好的一组命令。语法格式如下:

For var in list

Do

Commands

Done

list中,你需要提供迭代中要用到的一系列的值。有多种方法。每次迭代会使用list中的不同值。第一次使用第一个值,第二次使用第二个值,以此类推,直到所有值都过一遍。

dodone之间可以是一条或者多条shell命令。

也就是list中的值全部赋值给var变量,然后在commands中调用var变量,每次调用一个,直到结束。

记住,list中的多个值之间的分割是空格。有的时候list还需要转义或者加引号,保证输出的完整性和单一性。

从命令读取值:

For I in `cat $file`

注意$file中指定的文件中的每个数据是按照行分割的,也就是每一行一个数据。如果是以空格分开,也会被一个个读取,但是不推荐这么做,因为有的数据就是有空格的,这样会被误操作。

当然,$file可以使用全路径名,比如:

For I in `cat /tmp/test.txt`

IFSinternal field separator),内部字段分隔符,这个IFS是一个环境变量,定义了bash shell用作字段分隔符的一系列字符。默认情况下,会将下列字符作为分隔符:

空格

制表符

换行符

如果shell中看到了这些字符中的任意一个,它就会假定这表明了列表中一个新函数字段的开始。

如果某个脚本中运用了for循环语句,并且for循环语句调用了某个文件,该文件中的数据是带有空格的,则在脚本中可以忽略数据中的空格,也就是IFS只认换行,不认tab和空格,可以在脚本中临时设置IFS(不要设置全局变量),如:

IFS=$'\n'

注意了,上面的 写法,必须这么写。

这样在脚本中的分隔符只有是\nfor调用的文件中的数据空格,可以忽略了。

注意,在代码量较大的时候,可能需要在一个地方需要修改IFS,然后忽略这次修改,在脚本的其它地方需要继续沿用IFS默认值,那么可以临时保存一下原先的IFS值,然后再恢复它。

IFS.OLD=$IFS

IFS=$'\n'

IFS=${IFS.OLD}

比如要遍历/etc/passwd中的值,可以设定:

IFS=:

如果IFS要设置多个值,可以这么做:

IFS=$'\n':;"

If [ -d "$file" ]

这里的file是多个值(for file in /home/rich/*),所以要双引号引起来,作为一个完整性的变量。

C语言风格的for命令最大特点就是使用计数器,每次迭代使计数器增1或者减1。比如:

格式:

For (( var ; condition ; interation process ))

比如:

For (( a = 1; a < 10; a++ ))

注意,有些部分并没有遵循shell标准的for循环:

变量赋值可以有空格

条件中的变量不以$开头

迭代过程没用expr计算

另外,C语言的for语句可以使用多个变量,比如:

For (( a=1, b=10; a <= 10; a++, b-- ))

while命令的基本语法:

While test command

Do

Other commands

Done

这里的test commandif-then语句一模一样。可以使用任何普通的shell命令,或者用test命令进行条件测试,比如测试变量值。

比如:

Var1=10

While [ $var1 -gt 0 ]

Do

Echo $var1

Var1=$[ $var1 -1 ]

Done

注意while中的command也会作为输出,输出到屏幕上。

until命令和while命令工作方式相反,until命令要求指定规格通常返回非0的状态退出码作为测试。一旦状态退出码为0,就退出程序了。语法:

Until test commands

Do

Other commands

Done

比如:

Var1=100

Until [ $var1 - eq 0 ]

Do

Echo $var1

Var1=$[ $var1 - 25 ]

Done

$var1等于0时,程序退出。如果是while,则是$var10继续跑,如果不为0,就退出,正好相反。

注意,untiltest commands也会作为输出,输出到屏幕上。

嵌套循环也就是for中套用for或者while等语句,多条使用。

比如处理输出/etc/passwd中的每个列的值,可以先IFS设定为$'\n',输出每一行,然后再设定IFS=:,输出每个列。套用2次循环。

控制循环的语句主要是:

Breakcontinue。

break是跳出当前正在执行的循环。这个循环被停止了,不会再继续。当然它只是跳出当前这一个的循环,上次的循环还是会继续,不要搞错。

Break n,表示跳出n层循环。比如break 2,就是当层循环跳出,上一层循环再跳出。

continue命令就是提前终止某次循环中的命令,但不会完全终止整个循环 。也就是本次循环退出,然后执行下一次的循环。

Continue n,就是n层的剩余代码不执行,但是循环次数不变。比如:

For (( a =1; a <=5; a++ ))

Do

Echo "$a"

For (( b=1;  b<3; b++ ))

Do

If [ $a -gt  2 ]  && [ $a  -lt 4 ]

Then

Continue 2

Fi

Var3=$[ $a * $b ]

Echo " The result of $a * $b is $var3"

Done

Done

这里的当a=3时,var3就没数据了。然后a=4时,继续输出。

如果想把处理的数据输出到某个文件,而不是屏幕,可以这么做:

For var in list

Do

Commands

Done > output.txt

如果相对输出进行排序,可以这么做:

For var in list

Do

Commands

Done  | sort

案例,查找可执行文件:

#!/bin/bash

IFS=:

For folder in $PATH

Do

Echo "$folder:"

For file in $folder/*

Do

If [ -x $file ]

Then

Echo " $file"

Fi

Done

Done

案例,自动创建多个账户:

#!/bin/bash

Input="users.csv"

While IFS=',' read  userid name <- 读取这第一列作为userid,读取第二列作为name,它们之间使用逗号分开的。

Do

Echo "adding $userid"

Useradd -c "$name" -m "$userid"

Done < "$input"

 

第十四章 处理用户输入

位置参数(positional parameter)变量是标准的数字:$0是程序名,$1是第一个参数,直到第九个参数$9,如果是第十个参数是${10},以此类推。

程序名可能是./test.sh,也有可能是/home/alex/test.sh,这是因为用户的启动方式决定的。如果想得到仅仅是这个脚本的名字,可以这么做:

Basename $0,也就是剥离路径,只留下脚本名字。

使用位置参数之前,请确定一定需要检查一下该位置参数是否存在,以免出现异常:

If [ -n "$1" ]

这样别人运行该脚本时,如果没有输入第一个位置参数,则可以传达一些echo信息。

$#这个变量代表着参数的个数。如果你想对参数个数进行判断,可以这么做:

If [ $# -ne 2 ]

这就是检查如果参数个数不是2,于是做点什么,用于判断参数的输入个数,很有帮助。

${!#}代表着命令行最后一个参数变量。如果没有$1,就输出$0的值 ,也就是程序名。

$*$@变量可以用来访问所有参数。$*会把所有参数作为一个单词保存,视为一个整体。$@会把这些参数视为多个答案度的个体进行保存。

shift命令能够用来操作命令行的参数。shift命令会根据它们的相对位置来移动命令行参数。默认情况下,它会将每个参数的变量向左移动一个位置。所以,变量$3会移动到$2$2会移动到$1$1则会被删除。但是$0是不会变的。这是遍历命令行参数的一个好办法,尤其是你不知道到底有多少个参数时。可以只操作第一个参数,然后移动参数,然后继续操作第一个参数。例如:

#!/bin/bash

Count=1

While [ -n "$1" ]

Do

Echo "#$count = $1"

Count=$[ $count + 1 ]

Shift

Done

每次先判断第一个参数是否为空,然后打印出来,加1,左移动1个参数,继续判断第一个参数是否为空,不空,继续打印,继续左移动,直到第一个参数为空结束

如果一次性要移动多个,则shift n

处理选项的方法如下:

case方法:

#!/bin/bash

While [ -n "$1" ]

Do

Case "$1" in

-a) echo a;;

-b) echo b;;

-c) echo c;;

*) echo "no";;

Esac

Shift

Done

检查完了就shift,继续检查,直到$1为空。

双破折线(--)表明选项列表结束,在双破折线之后就是参数而不是选项了,比如:

#!/bin/bash

While [ -n "$1" ]

Do

Case "$1" in

-a) echo 1;;

-b) echo 2;;

-c) echo 3;;

--) shift

Break;;

*) echo "$1 is not an option";;

Esac

Shift

Done

运行时:

./test.sh -a -b -c -- test1 test2 test3

输出时,-a -b -c会被正确认出,--表示之后的都是参数,test1test2test3就会被认为是参数而不是选项了。

比如要在某个选项后跟上参数的话,可以这么做:

#!/bin/bash

While [ -n "$1" ]

Do

Case "$1" in

-a) echo 1;;

-b) param="$2"

Echo "$param"

Shift;;

-c) echo 3;;

--) shift

Break;;

*) echo "no";;

Esac

Shift

Done

getopts命令内建于shell。每次调用时,它一次只处理命令行上检测到的一个参数 。处理完所有的参数后,它会退出并返回一个大于0的退出状态码。这让它非常适合解析命令行所有参数的循环中。

Getopts optstring variable

如果选项字母有参数值,就加个冒号。要去掉错误消息的话,可以在opstring前面加个冒号,getopts将当前参数保存在命令行定义的variable中。如果选项需要加一个参数值,OPTAGR环境变量会用到这个值,OPTIND环境变量保存了参数列表中getopts正在处理的参数位置。比如:

#!/bin/bash

While getopts :ab:c opt

Do

Case "$opt"  in

a) echo "a";;

  1. Echo "b, with value $OPTAGR";;

  2. Echo "c";;

*) echo "no";;

Esac

Done

执行时:

./test.sh -ab test1 -c

-a -b -c都保存在了opt中,因此case是引用opt的变量进行操作。$OPTARG表示 -b后有参数,因此需要 加一个$OPTARG

getopts会移除开头的但破折号线,所有case时不需要加。getopts在实际操作时,参数值可以包含空格(需要引号),比如:

./test.sh -b "test1 test2" -a

还有就是选项和参数之间可以不加空格,比如:

./test.sh -abtest1

这样写也是可以的。

如果选项没找到,也可以顺利解析。比如:

./test.sh -d

getopts处理每个选项时,OPTIND环境变量会加1,在getopts处理完毕时,可以使用shiftOPTIND值来移动参数。

Shift $[ $OPTIND -1 ]

OPTIND默认为1

注意OPTIND记录的是 getopts处理的参数位置,如果一个getopts中处理的参数后有跟OPTARG,这被视为一个参数。比如:

./test.sh -a -b test1 -d test2 test3 test4

OPTIND认为是-a-b test1-d为三个参数。

常用Linux选项命令:

-a,显示所有对象。

-c,生成一个计数。

-d,指定一个目录。

-e,扩展一个对象

-f,指定读入数据的文件。

-h,显示命令帮助信息。

-i,忽略大小写。

-l,产生长格式。

-n,使用非交互式模式。(批处理)

-o,将所有输出重定向到指定输出文件。

-q,安静模式运行。

-r,递归。

-s,安静模式运行。

-v,生成详细信息。

-x,排除某个对象。

-y,对所有问题回答yes

read命令可以获取用户输入。

Read -p "Enter your name: " first last

这里如果输入了两个值,第一个值就赋给first,第二个值赋给last。也就是输入的每个值分配给变量列表中的下一个变量。如果变量数量不够,那么剩余的输入都赋给最后一个变量。(假如变量只有4个,输入了6次,则第4,5,6次输入都给第4个变量)。

如果read命令行中不指定变量,会默认放到REPLY变量中,这个变量会收到任何输入。

Read -t 5,设定输入超时5秒。

Read -n1,也就是获取一个字符输入后,就开始执行,不用回车。也就是read接收到1个字符后,就执行下面的命令。

Read -s,可以隐藏输入,屏幕上不会输出。

把文件中的数据传给read命令,最常见的办法是使用cat,将结果通过管道传给含有read命令的while命令,比如:

Cat test.txt | while read line

这就是把test.txt所有文件内容传给readread每读一行就传给line,然后line会在while中循环,进行处理,处理完毕,read接着读下一行,再传给line,然后再处理,以此类推。直到read命令以非0状态码退出,while结束循环。

 

第十五章 呈现数据

Linux系统将每个对象当做文件处理。这包括输入和输出进程。Linux用文件描述符(file descriptor)来标识每个文件对象。文件描述符是一个非负整数,可以唯一标识会话中打开的文件。每个进程一次最多可处理9个文件描述符。bash保留了前三个,0,1,2。其实0是标准输入,1是标准输出,2是标准错误。

针对终端来说,标准输入就是键盘。在使用重定向符号(>)时,Linux会用重定向指定的文件来替换标准输入文件描述符。它会读取文件并提取数据,就如同它是键盘上键入的。比如:

Cat < testfile

这就是一种典型的STDIN接受数据的技术。

默认情况下,错误消息也会输出到显示器输出中。

&>表示1>2>一起同时定向。如果是这样写的话,错误的STDERR优先级更高 ,也就是先输出文件描述符2的错误日志,再输出1的标准输出日志。

Echo "This is an error message" >&2

表示这句话被定向到了STDERR文件描述符,如果输出时这么做:

./test.sh

这样是看不出区别的,也会正常输出,但是:

./test.sh 2> test9

这样的话,This is an error message就不会被输出了。因为这句话被定位到了>&2中。

如果脚本中有大量数据需要重定向,那重定向每个echo语句会很繁琐,取而代之,可以使用exec命令告诉Shell在脚本执行期间重定向某个特定文件描述符。exec会启动一个新shell并将STDOUT文件描述符重定向到文件,脚本中发给STDOUT的所有输出会被重定向到文件。

#!/bin/bash

Exec 2> testerror

Echo "1"

Echo "2"

Exec 1> testout

Echo "3"

Echo "4" > &2

这样执行这个脚本后输出:

1

2

testerror文件中是4testout中是3

看一个脚本:

#!/bin/bash

Exec 0< testfile

Count=1

While read line

Do

Echo "Line #$count: $line"

Count=$[ $count +1 ]

Done

这里的exec 0< testfile表示将STDIN从键盘重定向到其它位置。exec命令让你将STDIN重定向到Linux系统文件中。当read去读数据,就不是从键盘读,而是从testfile中读取。

也可以使用自己创建的文件描述 符来做事:

#!/bin/bash

Exec 3>test3out

Echo "1"

Echo "2" >&3

这样在test3out文件中就有2了。

当然如果是追加写法,就是exec 3>>test3out

如果需要做文件描述的重定向恢复,需要这么做:

Exec 3>&1

Exec 1>test1out

Exec 1>&3

这里就是先把文件描述符3定位到STDOUT,然后1描述符输出到test1out文件,最后把1描述符再变回STDOUT。也就是恢复了输出。

类似的,也有这么做的:

Exec  6<&0

Exec 0< testfile

Exec 0<&6

先把STDIN保存到文件描述符6里面,然后STDIN变成testfile而不是键盘,最后把STDIN恢复给0

如果创建了新的输入或者输出的文件描述符,shell会在脚本退出时自动关闭它们,有些情况下,需要自己关闭,关闭方法:

Exec 3>&-

这就是关闭文件描述符3

如果再次开启3文件描述符,会打开一个新文件替换已有文件,这时输出数据会覆盖已有文件

lsof命令可以列出整个Linux系统打开的所有文件描述符。可以这么用:

Lsof -p $$ -a -d 0,1,2

列出0,1,2的文件描述符情况,-p $$表示当前PID-a表示and

普通用户使用/usr/sbin/lsof

lsof默认输出:

COMMAND,正在运行的命令名的前9个字符

PID,进程的PID

USER,进程属主的登录名 

FD,文家描述符和访问类型(r读,w写,u读写)

TYPE,文件类型(CHR字符型,BLK块型,DIR目录,REG常规文件

DEVICE,设备的设备号(主+从)

SIZE,如果有的话,文件大小

NODE,本地文件的节点号

NAME,文件名

> /dev/null不输出。

mktemp创建临时文件,有读写权限,创建者是属主,只有创建者和root可以访问。

一般这么用:

Mktemp test.XXXXXX

6X会变成6个随机字符码,保证唯一文件名字。

Mktemp -t test.XXXXXX

-t表示创建在/tmp目录。

Mktemp -d dir.XXXXXX

-d表示创建临时目录。

Date | tee testfile

date的输出存到testfile中。tee每次存的时候都会覆盖内容。如果想追加则-a

一个重定向实例:

#!/bin/bash

Outfile='members.sql'

IFS=','

While read lname fname address city state zip

Do

Cat >> $outfile << EOF

Insert  into members (lname,fname,address,city,state,zip) VALUES ('$lname', '$fname', '$address', '$city', '$state', '$zip');

EOF

Done < ${1}

Cat members.csv

Blum,Richard,123  Main  St.,Chicago,IL,60601

./test23 < members.csv

 

第十六章 控制脚本

Linux信号:

1SIGHUP,挂起进程

2SIGINT,终止进程

3SIGQUIT,停止进程

9SIGKILL,无条件终止进程

15SIGTERM,尽可能终止进程

17SIGSTOP,无条件停止进程,但不是终止进程

18SIGTSTP,停止或者暂停进程,但不终止进程

19SIGCONT,继续运行停止的进程

CTRL+C发送SIGINT信号,也就是终止进程。

CTRL+Z会生成SIGTSTP信号,也就是停止或者暂停进程。此时的进程状态为T。如果想退出停止状态的进程,可以exit

捕获信号:

不忽略信号,在信号出现时捕获它们并且执行其它命令。

Trap command signals

比如:

Trap "echo 'Sorry, I have trapped ctrl-c'" SIGINT

当按下ctrl+c时,会打印这句话。

捕获脚本退出:

Trap "echo goodbye…" EXIT

如果需要再次定义捕获信号,可以多次定义:

Trap "echo 1" SIGINT

Trap "echo 2" SIGINT

此时,重新定义后的捕获显示信息不一样了。

删除设置好的捕获:

Trap -- SIGINT

恢复默认行为:

Trap - SIGINT

后台运行:

./test4.sh &

[1] 3231

3231PID[1]job 1

[1] Done ./test4.sh

表示运行完毕。

nohup命令运行了另外一个命令来阻断所有发送给该进程的SIGHUP信号,这会在退出终端会话时阻止该进程退出。

Nohup ./test.sh &

产生一个nohup.out记录STDERRSTDOUT

变量$$用来显示进程PID

jobs命令可以查看分配给shell的作业。

jobs命令参数:

-l,列出进程的PID以及作业号

-n,只列出上次shell发出的通知后改变了状态的作业

-p,只列出作业PID

-r,只列出运行中的作业

-s,只列出停止的作业

jobs输出的带加号的作业会被当成默认作业。带减号的是下一个默认作业。

要以后台模式重启一个作业,可用bg加上作业号。

Jobs

Bg 2

要以前台模式重启一个作业,可以fg加上作业号。

调度优先级(scheduling priority)是内核分配给进程的CPU时间更多。从-20(最高)到+19(最低),默认0。

设定优先级使用nice -n

Nice -n 10 ./test.sh > test.out &

使用renice调整优先级(已运行命令)

Renice -n 10 -p 5055

-p 5055是进程5055

renice只能对自己的进程调整,renice只能降低nice值。

at使用atd守护进程,位于/var/spool/at,每60秒检查一次。格式如下“

At [-f filename] time

默认是将输入放入at

atq列出等待作业。

Atrm 18

删除18at作业。

cron来安排定期执行作业:

Min hour datofmonth month dayofweek command

每个月最后一天执行:

00 12 * * * if [`date +%d -d tomorrow` = 01 ]; then ; command

列出cron时间表:

Cron -l

浏览cron目录:

Ls /etc/cron.*ly

复制到daily目录,每天就跑一次。

如果计算机关机,那cron不能正确运行,比如对于一些日志需要整理,那么可能就会有问题,这时候引入anacron来进行调整,如果anacron知道某个作业错过了执行时间,它会尽快运行该作业,这意味着如果Linux系统关机了几天,当它再次开机时,原定在关机期间运行的作业会自动运行。anacron目录在/var/spoo/anacron,使用自己的时间/etc/anacrontab(配置文件)。比如某文件/var/spool/anacron/cron.monthly里面的值是20150626,它使用时间戳来决定作业是否在正确的计划间隔内运行了。

anacron时间表的格式如下:

Period delay identifier command

period定义了多久运行一次,以天为单位。也就是多久检查一下时间戳。delay条目是系统启动后anacron等待多少分钟再运行错过的脚本。command就是命令。identifier是一种非空字符串,如cron-weekly,用于唯一标识日志消息和错误邮件中的作业。

比如:

1 5 cron.daily nice run-parts /etc/cron.daily

Run-parts负责运行传给它的任何脚本。

 

第十七章 创建函数

函数用于代替那些重复要写的代码。函数是一个脚本代码块,你可以为其命名并在代码中任何位置重用。要在脚本中使用该代码块时,只要使用所起的函数名就行了(这个过程称为调用函数)。

创建函数的方式有两种:

1function name { commands }

2name() { commands }

调用函数:

#!/bin/bash

Function func1 {

Echo "1"

}

Func1

记住,必须先定义才能引用。函数名必须唯一。

如果函数定义多次,则使用最后一次定义。

$?来确定函数的退出状态码。成功执行就是0,否则是1~255。

Shell使用return命令来退出函数并返回特定的退出状态码。return命令允许指定一个整数值来定义函数的退出状态码。比如:

#!/bin/bash

Function db1 {

Read -p "Enter a value: " value

Echo "Doubling the value."

Return $[ $value * 2 ]

}

函数一结束就取返回值,退出状态码是0~255。

函数的输出值也能保存在变量中:

#!/bin/bash

Function db1 {

Read -p "Enter a value: " value

Echo $[ $value * 2 ]

}

Result=${db1}

result变量得到函数db1的返回值而不是退出状态码。

特殊的位置参数环境变量如果和函数一起使用,那只能在脚本中定义使用,离开脚本在外部调用,会失败错误。比如:

Function badfunc1 {

Echo $[ $1 * $2 ]

}

If [ $# -eq 2 ]

Then

Value=$(badfunc1)

Echo "The result is $value"

Fi

如果是这样调用,是不行的。但是可以这么做:

Function fun7 {

Echo $[ $1 * $2 ]

}

If [ $# -eq 2 ]

Then

Value=$(func7 $1 $2)

Echo "The result is $value"

Fi

这样手动传过去,是可以的。

作用域是变量可见的区域。函数中定义的变量与普通变量的作用域是不一样的。也就是说,函数变量的作用域对于脚本的其它部分是隐藏的。函数使用两种变量类型:全局变量和局部变量。全局变量是在Shell脚本中任何地方都有效的变量。如果在脚本的主体部分定义了一个全局变量,那么可以在函数内读取它的值。类似的,如果在函数的内部定义了一个全局变量,可以在脚本的主体部分读取它的值。但是在实际使用中,全局变量被主体和函数分别调用后可能产生混淆,这里可以使用局部变量解决。局部变量的使用方法是:local varvar就是变量。这样保证了var变量只是在该函数中有作用。如果在函数外还有一个var变量,则认为是这两个变量的值是分开的。

如果把整个数组变量值传给函数,函数会认为是一个值(数组变量第一个元素值),要解决这个问题需要将数组变量的值分解成单个的值。然后将这些值作为函数参数使用。在函数内部,可以将所有的参数重新组合成一个新的变量。

比如:

Function testit {

Local newarray

Newarray=(`echo "$@"`)

Echo "the new array value is: ${newarray[*]}"

}

Myarray=(1 2 3 4 5)

Echo "The original array is ${myarray[*]}"

Testit ${myarray[*]}

这里的newarray就是一个数组,它的输入方式是取myarray每一次一个值,即1,2,3,4,5,全部取完再结合成数组(1,2,3,4,5),存入newarray。从函数里面向shell脚本传回函数也用类似方法。函数用echo语句来按正确顺序输出单个数组值,然后脚本再将它们重新放进一个新的数组变量中。

局部函数变量的一个特性是自成体系,自成体系的函数不需要使用任何外部资源,这个特性使得函数可以递归的调用,也就是说,函数可以调用自己来得到结果。通常递归函数都有一个最终可以迭代到的基准值。比如做一个阶乘函数:

Function factorial {

If [ $1 -eq 1 ]

Then

Echo 1

Else

Local temp=$[ $1 -1 ]

Local result='factorial $temp'

Echo $[ $result * $1 ]

Fi

}

这个就是每一次自己乘以自己的函数算法。也就是一个阶乘算法。

Shell允许创建函数库文件,然后在多个脚本中引用该库文件。假如有个文件叫做myfuncs文件,里面写了一段的函数,你需要在别的脚本中调用myfuncs文件中的某些函数,需要这么做:

. ./myfuncs

这里的myfuncs被调用了,注意这里用了.操作符,也可以使用source,因为它们不会开启新的shell,如果开启了新的shellmyfuncs文件中的函数不能被使用。

命令行中也可以定义函数,并且直接使用。

如果想把定义函数存起来,那就放在.bashrc文件中。

shtool库提供了一些简单的shell脚本函数,可以用来完成日常的shell功能。

使用方法:

Shtool [options] [function [options] [args]]

比如:

Ls -alfR / | Shtool prop -p "waiting…"

waiting…

会显示一个旋转的进度条。使用\|/-字符创建一个旋转的进度条。

[root@centos7-python ~]# shtool rotate anaconda-ks.cfg

[root@centos7-python ~]# ll

total 8

-rw-r--r--. 1 root   root      0 Nov 13 07:23 anaconda-ks.cfg

-rw-r--r--. 1 root   root      0 Nov 13 07:23 anaconda-ks.cfg.0

-rw-------. 1 houjun houjun 1066 Aug 25 19:25 anaconda-ks.cfg.1

-rw-r--r--. 1 root   root    825 Nov 13 07:17 anaconda-ks.cfg.tar

drwxr-xr-x. 3 root   root     37 Sep  2 11:06 ex-python

Shtool mkshadow,快速创建软链接:

Shtool mkshadow -a -v . /tmp/shadow

/tmp/shadow下创建软链接对应到当前目录下的文件,目录结构一一对应。

 

第十八章 图形化桌面环境中的脚本编程

本章是一个实战篇。

脚本一,一个菜单:

[root@centos7-python ~]# cat ex18.sh

#!/bin/bash

function menu {

clear

echo

echo -e "\t\t\tSys Admin Menu\n"

echo -e "\t1. Display disk space."

echo -e "\t2. Display logged on users"

echo -e "\t3. Display memory usage"

echo -e "\t0. Exit menu\n\n"

echo -en "\t\tEnter option: "

read -n 1 option

}

function diskspace {

clear

df -k

}

function whoseon {

clear

who

}

function memusage {

clear

cat /proc/meminfo

}

while [ 1 ]

do

menu

case $option in

0)

break;;

1)

diskspace;;

2)

whoseon;;

3)

memusage;;

*)

clear

echo "Sorry, wrong selection";;

esac

echo -en "\n\n\t\tHit any key to continue"

read -n 1 line

done

clear

输出是一个菜单。

其中echo -e是表示可以使用\t,\n等符号,-n表示不换行。

脚本二:

[root@centos7-python ~]# cat ex19.sh

#!/bin/bash

PS3="Enter option: "

select option in "Display 1" "Display 2" "Display 3" "Display 4"

do

case $option in

"Display 1") echo 1;;

"Display 2") echo 2;;

"Display 3") echo 3;;

"Display 4") echo 4;;

*) clear; echo "No";;

esac

done

clear

select语句可以自动输出一个带编号的选项,选项显示一个由PS3环境变量定义的特殊提示符。

[root@centos7-python ~]# ./ex19.sh

1) Display 1

2) Display 2

3) Display 3

4) Display 4

Enter option: 1

1

Enter option: 2

2

Enter option: 3

3

Enter option: 4

4

dialog包可以显示窗口菜单。

第十九章 初识sed和gawk

sed编辑器称作流编辑器(stream editor),流编辑器则会在编辑器处理数据之前预先提供的一组规则来编辑数据流。sed编辑器可以根据命令来处理数据流中的数据,这些命令要么从命令行中输入,要么存储在一个命令文本文件中。

sed编辑器的执行流程:

1、一次从输入中读取一行数据。

2、根据所提供的编辑器命令匹配数据。

3、按照命令修改流中的数据。

4、将新的数据输出到STDOUT。

在流编辑器将所有命令与一行数据匹配完毕后,它会读取下一行数据并重复这个过程。在流编辑器处理完流中的所有数据行,它就会终止。

sed语法如下:

sed options script file

-e,允许多重编辑

-f file,在处理输入时,将file中指定的命令添加到已有的命令中。也就是用file中的sed命令

-n,静默输出

例子:

echo "This is a test" | sed 's/test/big test/'

This is a big test

sed的修改只是输出到STDOUT,如果sed对文件进行流编辑,只是显示输出结果,但是文件实际上没有被修改。

sed -e 's/brown/green/; s/dog/cat/' data1.txt

这里使用了-e,进行多个数据的编辑。

或者:

sed -e '

s/brown/green/

s/fox/elephant/

s/dog/cat' data1.txt

如果使用-f file方式,如下:

cat script1.sed

s/brown/green/

s/fox/elephant/

s/dog/cat

sed -f script1.sed data1.txt

gawk程序是Unix中的原始awk程序的GNU版本。在gawk中,可以做到下面的事情:

1、定义变量来保存数据。

2、是用算术和字符串操作来处理数据。

3、使用结构化编程概念(比如if-then语句和循环)来为数据处理增加处理逻辑。

4、通过提取数据文件中的数据元素,将其重新安排或格式化,生成格式化报告。

gawk程序的报告生成能力通常用来从大文件中提取数据元素,并将它们格式化成可读的报告。其中最完美的例子是格式化日志文件。

gawk语法如下:

gawk options program file

-F fs,指定分隔符

-f file,从指定文件中读取程序

-v variable,定义gawk程序中的一个变量及其默认值

-mf N,指定要处理的数据文件中的最大字段数

-mr N,指定数据文件中的最大数据行数

-W keyword,指定gawk兼容模式或警告等级

在gawk中:

$0表示整个文本行

$1表示文本中第一个数据字段

$2表示文本中第二个数据字段

$n表示文本中第n个数据字段

数据字段分割是根据字段分隔符分割的,默认是空白字符。(例如空格或者制表符)

gawk -F: '{print $1}' /etc/passwd

对/etc/passwd输出第一列字段,分割符号为:。

echo "My name is Rich" | gawk '{$4="Alex"; print $0}'

输出:

My name is Alex

这里$4是一个赋值过程,把第四个字段赋值成为Alex,然后输出整行。

gawk -f file,从文件中读取命令:

cat script2.gawk

{print $1 "'s home directory is " $6}

gawk -F: -f script2.gawk /etc/passwd

在处理数据前运行脚本:

cat data3.txt

gawk 'BEGIN {print "The data3 File Contents:" {print $0}' datat3.txt

这里在运行gawk前,先运行BEGIN中的命令,然后再运行print $0。

在处理数据后运行脚本:

gawk 'BEGIN {print "The data3 File Contents:" {print $0} END {print "End of File"}' data3.txt

END后面的语句是print $0后运行的。

这种方法添加页脚很好。

sed替换:

sed 's/test/trail/' data4.txt

语法:

s/pattern/replacement/flags

替换标记:

数字,表示新文本将替换第几处模式匹配的地方

g,所有匹配

p,原先行的内容要打印出来

w file,将替换的结果写到文件

sed 's/test/trial/2' data4.txt

只替换每行第二次出现的匹配模式。

sed 's/test/trial/g' data4.txt

替换所有匹配

sed -n 's/test/trial/p' data4.txt

-n选项禁止sed编辑器输出,p替换标记会输出修改过的行。两者和用就是只输出被替换名修改过的行。

sed 's/test/trial/w test.txt' data5.txt

把输出保存到test.txt中。 

 如果遇到带有/字符的替换,可以使用别的替换字符:

sed 's#/bin/bash#/bin/csh#' /etc/passwd

默认情况下,sed会作用域文本数据所有行,如果只想把命令作用与某些行,必须用行寻址(line addressing)。

行寻址两种形式:

1、以数字形式表示行区间

2、用文本模式来过滤出行

格式都一样:

[address]command

或者:

address {

command1

command2

command3

}

sed '2s/dog/cat/' data.txt

只修改指定第二行文本。

sed '2,3s/dog/cat/' data.txt

只修改2-3行。

sed '2,$/s/dog/cat/' data.txt

从第2行到最后,$表示最后(正则)。

sed '/alex/s/bash/csh/' /etc/passwd

先对/etc/passwd进行alex匹配(类似grep alex /etc/passwd),匹配到了以后再用csh替换bash。

d选项用于删除行:

sed '3d' data6.txt

删除第三行。

sed '2,3d' data6.txt

删除2-3行。

sed '3,$d' data.txt

删除第三行到最后。

sed '/number 1/d' data.txt

删除包含number 1的行。

sed '/1/,/3/d' data.txt

找到匹配1,开始删除,找到匹配3,关闭删除,1和3都包括在内。这会引起一个问题,如果先开始删除了,最后没有找到匹配,那就会把剩余的全部删除了。

i,指定行前插入一行

a,指定行后插入一行

echo "Test Line 2" | sed 'i\Test Line 1'

输出:

Test Line 1

Test Line 2

echo "Test Line 2" | sed 'a\Test Line 1'

输出:

Test Line 2

Test Line 1

sed '3i\Insert line.' data6.txt

在第三行前插入Insert line。

sed '3a\Append line.' data6.txt

在第三行后插入Append line。

sed '$a\Last line.' data.txt

最后一行插入Last line。

如果要插入多行,对新行加入\。

sed '1i\1\2' data.txt

c,修改行。

sed '3c\This is' data.txt

对第三行修改。

sed '/number 3/c\Changed' data.txt

匹配number 3,进行修改成Changed。

sed '2,3c\Changed' data.txt

这表示把2-3行改成Changed一行,而不是两行都改成Changed。

sed 'y/123/789/' data.txt

这是一种映射处理,1->7,2->8,3->9,关系一一对应,是全局处理模式,针对data.txt所有数据修改。

=在sed中可以打印行号:

sed -n '/number 4/{=p}' data.txt

这里配合p,打印出匹配number 4的行,并且打印出行号。

sed -n '1,2w test.txt' data.txt

把data.txt的1-2行输出到test.txt。

sed '3r data12.txt' data.txt

把data12.txt内容读出来,然后插入到data.txt第三行下面。

sed '/number 2/r data12.txt' data6.txt

把data12.txt内容读出来插入到匹配number 2的行下面。

sed '$r data12.txt' data6.txt

把data12.txt读出来插入到data6.txt尾部。

r方式只能读一个行号或者文本地址模式。

sed '/LIST/{r data1.txt;d}' data.txt

读取data1.txt内容插入到匹配LIST的行,并且删除匹配LIST的行。也就是这个占位符。

第二十章 正则表达式

在Linux中,有两种正则表达式引擎:

POSIX基础正则表达式(basic regular expression,BRE)引擎

POSIX扩展正则表达式(extended regular expression,ERE)引擎

sed使用BRE,gawk使用ERE。

gawk要是使用正则表达式间隔,需要使用--re-interval选项。

比如:

echo "bt" | gawk --re-interval '/be{1}t/{print $0}'