《Linux命令、编辑器与Shell编程》读书笔记12-函数和脚本
来源:互联网 发布:淘宝收件人名字大全 编辑:程序博客网 时间:2024/06/06 16:34
控制Shell脚本执行顺序
条件测试和信号捕获
条件测试主要包括用户输入、条件命令执行完后的返回状态、变量值、文件状态及系统发送的信号等。
退出状态
Linux系统中的命令、脚本或者程序,在执行完退出后,都存在退出状态(0:执行成功,1:执行失败),这个值保存在预定义变量$?中。编写脚本时也可以自定义退出状态,比如在每个可能的退出点增加一条设置退出状态的语句,就可以根据自定义的退出状态值得知脚本是运行到什么地方出的错。
【举例】
#检查向脚本传递的参数个数是否为0,如果为0则给出错误提示,不为0则输出参数
#!/bin/bash
function usage()
{
echo "Error:Must have a parameter."
echo "$0 parameter..."
exit 1
}
if [ $# = 0 ]
then
usage
fi
echo $1
echo $2
exit 2
*上文中,将异常退出的状态值置为1,也就是赋给了变量$#,将正常退出的状态值置为2.
* function usage()表示定义了一个名为usage的函数。Shell中的函数也没有数据类型,且只在调用它的脚本范围内生效。
测试状态
文件状态测试
使用方括号将要测试的目标括起来,系统会按照要求判断这个文件,并将结果(是:0,否:1)返回给预定义变量$?,之后使用echo $?就可以得知结果了;命令格式如下
[ -command target_name ]
使用”man [“查看command可用的选项如下(仅翻译常用的):
-b FILE
FILE exists and 是否是一个块设备文件
-c FILE
FILE exists and is 字符设备文件
-d FILE
是否是一个目录
-e FILE
文件是否存在
-f FILE
是否是一个常规文件
-g FILE
FILE exists and is set-group-ID
-G FILE
FILE exists and is owned by the effective group ID
-h FILE
是否是一个超链接(same as -L)
-k FILE
FILE exists and has itsstickybit set(待学习)
-L FILE
是否是一个超链接 (same as -h)
-O FILE
FILE exists and is owned by the effective user ID
-p FILE
FILE exists and is a named pipe
-r FILE
是否具备读权限
-s FILE
FILE exists and has a size greater than zero
-S FILE
FILE exists and is a socket
-t FD file descriptor FD is opened on a terminal
-u FILE
是否具有SUID权限
-w FILE
是否具备写权限
-x FILE
是否具备可执行权限
FILE1 -ef FILE2
FILE1 and FILE2 have the same device and inode numbers
FILE1 -nt FILE2
FILE1 is newer (modification date) than FILE2
FILE1 -ot FILE2
FILE1 is older than FILE2
*注:这个命令如果要放到if里用的话,就不用再加额外的方括号了
变量状态测试
测试即将使用的变量是否被定义过:
[ -z $PARMETER_NAME ]
返回0表示该变量未被使用,1表示已经使用。
字符串和数值状态测试
可以用来判断用户的输入是否符合要求的格式或值,比如:
判断两个字符串是否相同:
[ “abc” = “ABC” ]
判断变量的值是否为空:
[ -n “$PARA” ]
其它数值比较格式:
STRING1 = STRING2
the strings are equal
STRING1 != STRING2
the strings are not equal
INTEGER1 -eq INTEGER2
INTEGER1 is equal to INTEGER2
INTEGER1 -ge INTEGER2
INTEGER1 is greater than or equal to INTEGER2
INTEGER1 -gt INTEGER2
INTEGER1 is greater than INTEGER2
INTEGER1 -le INTEGER2
INTEGER1 is less than or equal to INTEGER2
INTEGER1 -lt INTEGER2
INTEGER1 is less than INTEGER2
INTEGER1 -ne INTEGER2
INTEGER1 is not equal to INTEGER2
系统进行数值测试时,会自动把两边转换为数值后在进行运算。
逻辑操作符
将上面任意两个表达式用下列连接选项组合起来,就组成了逻辑表达式:
-a:与,两边都为0时总结果为0
-o:或,任意一边为0,总结果为0
!:非,总结过时反得。
例如,测试/etc/passwd文件是否可读写:
[ -r /etc/passwd-a -w /etc/passwd ]
echo $?
0
捕获系统信号
这个是相对于用户发出的操作信号而言的。有时为了避免正在运行的脚本因用户误操作而异常退出,编写脚本时可以通过捕捉系统信号来决定脚本合适停止执行。
系统信号说明
1) SIGHUP
本信号在用户终端连接(正常或非正常)结束时发出,通常是在终端的控制进程结束时,通知同一session内的各个作业,这时它们与控制终端不再关联。
登录Linux时,系统会分配给登录用户一个终端(Session)。在这个终端运行的所有程序,包括前台进程组和后台进程组,一般都属于这个 Session。当用户退出Linux登录时,前台进程组和后台有对终端输出的进程将会收到SIGHUP信号。这个信号的默认操作为终止进程,因此前台进程组和后台有终端输出的进程就会中止。不过可以捕获这个信号,比如wget能捕获SIGHUP信号,并忽略它,这样就算退出了Linux登录,wget也能继续下载。
此外,对于与终端脱离关系的守护进程,这个信号用于通知它重新读取配置文件。
2) SIGINT
程序终止(interrupt)信号,在用户键入INTR字符(通常是Ctrl-C)时发出,用于通知前台进程组终止进程。
3) SIGQUIT
和SIGINT类似,但由QUIT字符(通常是Ctrl-/)来控制.进程在因收到SIGQUIT退出时会产生core文件,在这个意义上类似于一个程序错误信号。
4) SIGILL
执行了非法指令. 通常是因为可执行文件本身出现错误, 或者试图执行数据段.堆栈溢出时也有可能产生这个信号。
5) SIGTRAP
由断点指令或其它trap指令产生.由debugger使用。
6) SIGABRT
调用abort函数生成的信号。
7) SIGBUS
非法地址, 包括内存地址对齐(alignment)出错。比如访问一个四个字长的整数,但其地址不是4的倍数。它与SIGSEGV的区别在于后者是由于对合法存储地址的非法访问触发的(如访问不属于自己存储空间或只读存储空间)。
8) SIGFPE
在发生致命的算术运算错误时发出.不仅包括浮点运算错误,还包括溢出及除数为0等其它所有的算术的错误。
9) SIGKILL
用来立即结束程序的运行. 本信号不能被阻塞、处理和忽略。如果管理员发现某个进程终止不了,可尝试发送这个信号。
10) SIGUSR1
留给用户使用
11) SIGSEGV
试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据.
12) SIGUSR2
留给用户使用
13) SIGPIPE
管道破裂。这个信号通常在进程间通信产生,比如采用FIFO(管道)通信的两个进程,读管道没打开或者意外终止就往管道写,写进程会收到SIGPIPE信号。此外用Socket通信的两个进程,写进程在写Socket的时候,读进程已经终止。
14) SIGALRM
时钟定时信号, 计算的是实际的时间或时钟时间. alarm函数使用该信号.
15) SIGTERM
程序结束(terminate)信号,与SIGKILL不同的是该信号可以被阻塞和处理。通常用来要求程序自己正常退出,shell命令kill缺省产生这个信号。如果进程终止不了,我们才会尝试SIGKILL。
17) SIGCHLD
子进程结束时, 父进程会收到这个信号。
如果父进程没有处理这个信号,也没有等待(wait)子进程,子进程虽然终止,但是还会在内核进程表中占有表项,这时的子进程称为僵尸进程。这种情况我们应该避免(父进程或者忽略SIGCHILD信号,或者捕捉它,或者wait它派生的子进程,或者父进程先终止,这时子进程的终止自动由init进程来接管)。
18) SIGCONT
让一个停止(stopped)的进程继续执行.本信号不能被阻塞.可以用一个handler来让程序在由stopped状态变为继续执行时完成特定的工作.例如,重新显示提示符
19) SIGSTOP
停止(stopped)进程的执行.注意它和terminate以及interrupt的区别:该进程还未结束,只是暂停执行.本信号不能被阻塞,处理或忽略.
20) SIGTSTP
停止进程的运行, 但该信号可以被处理和忽略. 用户键入SUSP字符时(通常是Ctrl-Z)发出这个信号
21) SIGTTIN
当后台作业要从用户终端读数据时,该作业中的所有进程会收到SIGTTIN信号.缺省时这些进程会停止执行.
22) SIGTTOU
类似于SIGTTIN, 但在写终端(或修改终端模式)时收到.
23) SIGURG
有”紧急”数据或out-of-band数据到达socket时产生.
24) SIGXCPU
超过CPU时间资源限制.这个限制可以由getrlimit/setrlimit来读取/改变。
25) SIGXFSZ
当进程企图扩大文件以至于超过文件大小资源限制。
26) SIGVTALRM
虚拟时钟信号. 类似于SIGALRM, 但是计算的是该进程占用的CPU时间.
27) SIGPROF
类似于SIGALRM/SIGVTALRM,但包括该进程用的CPU时间以及系统调用的时间.
28) SIGWINCH
窗口大小改变时发出.
29) SIGIO
文件描述符准备就绪, 可以开始进行输入/输出操作.
30) SIGPWR
Power failure
31) SIGSYS
非法的系统调用。
在以上列出的信号中,程序不可捕获、阻塞或忽略的信号有:SIGKILL,SIGSTOP
不能恢复至默认动作的信号有:SIGILL,SIGTRAP
默认会导致进程流产的信号:
SIGABRT,SIGBUS,SIGFPE,SIGILL,SIGIOT,SIGQUIT,SIGSEGV,SIGTRAP,SIGXCPU,SIGXFSZ
默认会导致进程退出的信号:
SIGALRM,SIGHUP,SIGINT,SIGKILL,SIGPIPE,SIGPOLL,SIGPROF,SIGSYS,SIGTERM,SIGUSR1,SIGUSR2,SIGVTALRM
默认会导致进程停止的信号:
SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU
默认进程忽略的信号:
SIGCHLD,SIGPWR,SIGURG,SIGWINCH
此外,SIGIO在SVR4是退出,在4.3BSD中是忽略;SIGCONT在进程挂起时是继续,否则是忽略,不能被阻塞。
举例
捕捉系统信号用trap命令,下面这个脚本开始执行后会sleep60秒,当用户按下ctrl+c后,脚本使用trap命令调用自定义的trap2函数输出提示:
#!/bin/bash
trap"trap2" 2
function trap2()
{
echo "You press CTRL+C."
echo "Exiting process, pleasewait..."
exit 1
}
sleep 60
条件判断语句if
格式一:
if 条件表达式
then
语句1
语句2
…
fi
这里的条件表达式就是上面提到的条件测试表达式。
格式二:
if 条件表达式
then
语句块1
…
else
语句块2
…
fi
Shell里的语句块不用像其他语言、必须用{}括起来。
格式三:
if 条件测试表达式1
then
语句块1
elif 条件测试表达式2
then
语句块2
…
elif 条件测试表达式4
then
语句块4
else
语句块5
fi
这种格式的执行流程如下:
例一:判断文件状态及类型
判断某个文件的状态及类型,通过脚本参数将文件路径传入脚本:
#!/bin/bashfunction test_file(){ FILE_NAME=$1 if [ ! -e $FILE_NAME ] then echo "file not exist." return 1 fi if [ -d $FILE_NAME ] then echo $FILE_NAME": Directory." return 0 elif [ -c $FILE_NAME ] then echo $FILE_NAME":Character device file." return 0 elif [ -L $FILE_NAME ] then echo $FILE_NAME":Link file." return 0 elif [ -b $FILE_NAME ] then echo $FILE_NAME":Block device file." return 0 elif [ -f $FILE_NAME ] then echo $FILE_NAME":Regular file." return 0 else echo $FILE_NAME":Unkown type." return 1 fi}function usage(){ echo "Error:Must have a file path following the script name." echo "Usage: "$0" file_name" exit 1}if [ $# = 0 ] then usage exit 1fiif MESSAGE=`test_file $1` then echo $MESSAGE exit 0else echo $MESSAGE exit 1fi
例二:多if嵌套
判断用户输入的脚本参数是一个目录还是文件,如果是则提示用户是否删除,读取用户输入后、做相应的操作。
#!/bin/bashfunction usage(){ echo "Error: Must have a file path following the script name." echo "Usage: "$0" file name" exit 1}if [ $# = 0 ] then usage exit 1fiif [ -d $1 ] then echo -n "Are you sure to delete this directory?[y|n]" read ANS_del if [ $ANS_del = "y" ] then rm -rf $1 &>/dev/null else exit 1 fielse echo -n "Are you sure to delete this file?[y|n]" read ANS_del if [ $ANS_del = "y" ] then rm -rf $1 &>/dev/null else exit 1 fifi
多条件判断语句case
基本格式如下:
case 变量名 in
模式1)
语句块1
;;
模式2)
语句块1
;;
……
;;
esac
它的执行流程和if-elif相同,但是缺陷是只能对具体的某几个数值进行判断,而无法进行变量范围判断。
例一:判断用户的输入
#!/bin/bash
while true
do
echo -n "Please enter yesor no.[yes|no]"
read ANS
case "$ANS" in
y|Y|yes|Yes)
echo "Youenter yes."
#下面可以添加想要执行的命令语句
… …
break
;;
n|N|no|No)
echo "Youenter no."
#下面可以添加想要执行的命令语句
… …
break
;;
*)
echo"Please enter yes or no."
continue
;;
esac
done
#下面可以添加想要执行的收尾语句
… …
*注:case判断中,每一个判断模式下面一定要记住加break语句,否则有可能是代码陷入死循环。
循环语句for
基本格式:
for 步进变量名 in 变量列表
do
语句块
done
这里的变量列表既可以是一个集合,也可以是一个数组。
for语句会自动从将变量列表中的值依次传递给步进变量,直到所有变量都使用完为止。
例:输出循环的次数和步进变量值
#!/bin/bash
I=1
J="1 2 3 45"
for LOOP in $J
do
echo "Loop:" $I
echo "LOOP="$LOOP
I=`expr $I + 1`
done
exit 0
循环语句while
基本格式:
while 条件判断语句(为真)
do
语句块
done
例子可以参考下面until的例子。
循环语句until
基本格式:
until 条件判断表达式(为假)
do
语句块
done
该语句的执行流程按照书本上说如下:
但在我的机器(SLSE11_SP3,内核版本:3.0.76-0.11)上还是会先判断条件、然后才执行语句块。建议能用for或while代替的循环、还是不要用这个语句。容易产生歧义。
可以看出,它是在判断条件为假时才执行语句块,当条件为真、则跳出循环,执行done后面的语句。
例:使用until语句监控某个分区的使用率,当使用率大于90%则向root用户告警并退出脚本,否则就一直循环执行监控语句。每次监控启动间隔600秒。
#!/bin/bashfunction usage(){echo"Error:Must have a parameter." echo "$0 parameter..." exit 1}if [ $# = 0 ] then usagefiUSED=`df -k | grep $1 | awk '{print $5}'|awk -F% '{print $1}'`until ["$USED" -gt "90" ] do echo "disk:" $1 echo "Used:" $USED if [ $USED -gt "90" ] then echo "disk:" $1 >/tmp/disk.tmp echo "Used:" $USED>> /tmp/disk.tmp mail -s "diskwarning!" root </tmp/disk.tmp rm -rf /tmp/disk.tmp echo "Has notifiedroot." fi sleep 600done#如果使用率大于90%,until里面的语句将不会被执行,所以外面还要再写下echo"disk:" $1echo"Used:" $USEDecho"disk:" $1 > /tmp/disk.tmpecho"Used:" $USED >> /tmp/disk.tmpmail -s "diskwarning!" root </tmp/disk.tmprm -rf/tmp/disk.tmpecho "Hasnotified root."unset USED
*注:使用非root用户执行该脚本时,我的机器报了这个错:
df: `/root/.gvfs':Permission denied
按照网上的方法,使用root用户进入/root目录卸载掉这个文件后问题解决了:umount .gvfs
break和continue控制循环
break:当循环满足某一条件时,使用该语句跳出循环;
如果使用了循环嵌套,则可以在break后面加上数字指定要跳出的循环层数。
continue:结束当前循环、继续进行下一次循环(如果还满足循环条件的话)
函数和脚本参数
其实上面的例子中,已经用到了shell函数和脚本参数。
Shell函数
定义函数
function function_name()
{
语句块
}
这里注意函数名function_name的命名,不要使用系统函数,尽量能体现该函数的功能。推荐驼峰命名和下划线的组合。
向函数传递参数
函数能够接收参数的前提是函数中引用了位置变量$1~$9;向函数传参时、直接在函数名后面加上要传递的参数、用空格分隔即可。比如:
命名一个脚本名为call_function.sh
#!/bin/bashfunction Add_A(){ B=` expr $1 + $2 `}Add_A $1 $2echo$1"+"$2"="$B#执行./call_function.sh 3234 53213234+5321=8555
返回函数执行状态
系统会把函数最后一句执行的状态作为该函数的执行状态自动返回给位置变量#?,但也可以手动通过return语句设置函数的返回值。
系统执行函数遇到return时,无论后面有没有待执行的语句,都会立即设置函数返回值并退出当前函数。
return:用函数最后一句的执行状态作为函数的返回值、并立即退出函数。
return 0:返回函数执行成功、并立即退出函数。
return 1:返回函数执行失败、并立即退出函数。
调用函数文件
顾名思义,可以将某个函数写进一个固定的脚本文件,从而让其他脚本随时调用该函数(前提是执行用户对该函数文件有读和执行的权限)
我们可以把检查脚本执行时是否带参数的函数写到下列文件usage.sh
#!/bin/bashfunction usage(){ if [ $# = 0 ] then echo "Error: Must have a file pathfollowing the script name." echo "Usage: "$0" number1number2" exit 1 fi}#然后在上面的"call_function.sh"脚本中调用这一函数:#!/bin/bashfunction Add_A(){ B=` expr $1 + $2 `}#调用函数所在文件../usage.sh#调用文件中的函数usageAdd_A $1 $2echo$1"+"$2"="$B#不带参数执行call_function.sh./call_function.shError: Must have afile path following the script name.Usage:./call_function.sh number1 number2
*调用函数所在文件时要注意,上面的作法只适用于被调文件和执行文件在同一目录下的情况,否则要用相对路径或者决定路径表示
向脚本传递参数
当向脚本传递的参数大于9个时,需要使用shift命令。
(个人感觉很少有脚本会用到9个参数,这里简单说下shift实现的原理,就不举例了)
假设向脚本传递了10个参数,分别为:
1 2 3 4 5 6 7 8 9 0
$1 $2 $3 $4 $5 $6 $7 $8 $9 (暂无变量)
这里直接用$1~$9引用对应的值1~9没有问题,但如果想用第十个变量值0,就需要执行一次shift命令,执行后,值与变量名的对应关系就会发生变化,如下:
2 3 4 5 6 7 8 9 0
$1 $2 $3 $4 $5 $6 $7 $8 $9
这时$9对应的就是0,而1这个值就没有办法再被引用了。同一脚本中,没使用一次shift,对应的值就会“往前”移动一位,直到所有位置变量对应的值都变为空。
- 《Linux命令、编辑器与Shell编程》读书笔记12-函数和脚本
- 《Linux命令、编辑器与Shell编程》读书笔记13-系统脚本和登录环境
- 《Linux命令、编辑器与Shell编程》读书笔记10-vim编辑器
- 《Linux命令、编辑器与Shell编程》读书笔记3-linux命令中的特殊字符和正则表达式
- <Linux命令,编辑器和shell编程>之命令部分读书笔记
- 《Linux命令、编辑器与Shell编程》读书笔记2-linux常用命令
- 《Linux命令、编辑器与Shell编程》读书笔记7-Linux系统管理
- 《Linux命令、编辑器与Shell编程》读书笔记4.1-查找和筛选工具(grep,find,sed)
- 《Linux命令、编辑器与Shell编程》读书笔记6-磁盘和文件系统管理
- 《Linux命令、编辑器与Shell编程》读书笔记11-Shell编程基础
- 《Linux命令、编辑器与Shell编程》读书笔记1-linux系统入门命令
- 《Linux命令、编辑器与Shell编程》读书笔记14-Shell实例及调试
- 《Linux命令、编辑器与Shell编程》读书笔记8-Linux数据备份与应用管理
- 《Linux命令、编辑器与Shell编程》读书笔记9-Linux网络管理
- Linux命令、编辑器与Shell编程
- 实用程序--linux命令、编辑器与shell编程
- 《Linux命令、编辑器与Shell编程》读书笔记4.2-格式化文本工具(awk)
- 《Linux命令、编辑器与Shell编程》读书笔记5-用户和文件权限管理
- 串匹配算法之Sunday算法
- Mac 批量删除 .svn 文件
- 【虚幻4】学习总结6-第三人称角色01
- 编辑距离问题
- Algorithm Intro - Bubble Sort
- 《Linux命令、编辑器与Shell编程》读书笔记12-函数和脚本
- spring学习(转载)
- 初学jQuery ajax
- 数据库之简单的数据查询
- CDATA区段的作用
- adb devices连接提示 Android offline或unauthorized的解决办法
- Algorithm Intro - Merge Sort
- Java反射
- 菜鸟学python(7) 简单文件访问