第一章 深入讨论 内容: 深入讨论awk ; 深入讨论 标准输入 << ; 第二章 shell 工具 内容: 日志文件; 信号; trap捕捉信号; eval ; logger 第三章 运行级别脚本介绍 内容: 运行级别; inittab 启动应用程序 第四章 几个脚本例子 内容: kill_processes.sh cpdir.sh menu.sh 1.1 深入讨论 awk ① 记录和域, 模式和动作, 正则表达式和元字符 ② 条件操作运算符 ③ awk 内置变量 ④ NF, NR 和 FILENAME ⑤ awk操作符 ⑥ 内置的字符串函数 (7) 字符串屏蔽序列 (8) awk输出函数printf (9) awk数组 1.1 深入讨论 awk ① 条件操作符 (awkif.sh) < >= <= == != ~ 匹配正则表达式 !~ 不匹配正则表达式 ② 逻辑操作符 && and || or ! not 示例: #!/bin/bash #awkif echo "210-219网段的访问量是: `awk '{if ($1~/^21[0-9]/) print $0}' www.log | wc -l`" echo "非210-219网段的访问量是: `awk '{if ($1!~/^21[0-9]/) print $0}' www.log | wc -l`" echo "2004年07月07日的访问量是: `awk '{if ($4~/^\[07\/Jul\/2004/]) print $0}' www.log | \ awk '{if ($7=="/htm/free_call.php") print $0}' | wc -l `" //注: ~ 表示匹配 ; print $0 表示打印出整条记录 \[ 左中括号是元字符, 因此需以反斜杠进行转意; \/ 斜杠是元字符, 因此也需用反斜杠进行转意; 1.1 深入讨论 awk ① awk内置变量 (awkvar.sh) ARGC 命令行参数个数 ARGV 命令行参数排列 ENVIRON 支持队列中系统环境变量的使用 FILENAME awk浏览的文件名 FNR 浏览文件的记录数 FS 设置输入域分隔符, 等价于命令行-F选项 NF 浏览记录的域个数 NR 已读的记录数 OFS 输出域分隔符 ORS 输出记录分隔符 RS 控制记录分隔符 示例: awk -F '#' '{print NF,NR,$0}' grade.txt awk -F '#' '{print NF,NR,ENVIRON["USER"],$0,FILENAME,ARGC,ARGV[0]}' grade.txt //环境变量是以数组的方式存放的 1.1 深入讨论 awk ① 字符串函数 (内置函数) gsub(r,s) 在整个$0中用s替代r gsub(r,s,t) 在整个t中用s替代r (t可以是一个域/一条记录/一个字符串) index(t,s) 返回s在字符串t中的第一个位置 length(s) 返回s长度 match(s,r) 测试s是否包含匹配r的字符串 split(s,a,fs) 用fs上将s分成序列a (fs为域分隔符,序列a 即数组a) sprint(fmt,exp) 返回经fmt格式化后的exp sub(r,s) 用$0中最左边最长的子串代替s substr(s,p) 返回字符串s中从p开始的之后部分 substr(s,p,n) 返回字符串s中从p开始长度为n的之后部分 示例: awk -F '#' '{if (gsub("#","||")) print $0}' grade.txt awk -F '#' '{if (gsub("s","S",$2)) print $2}' grade.txt awk -F '#' '{print (index($2,"s"))}' grade.txt 1.1 深入讨论 awk ① 转义字符 \b 退格键 \t tab键 \f 走纸换页 \ddd 八进制 \n 新行 (换行) \c 任意其它特殊字符,例如\\为反斜线符号 \r 回车键 示例: awk -F '#' '{print (index($2,"s")),"\t",$2}' grade.txt 1.1 深入讨论 awk ① printf修饰符 %c ASCII字符 %d 整数 %f 浮点数,例如(123.44) %e 浮点数,科学计数法 %f 新行 (换行) %g awk决定使用哪种浮点数转换e或者f %o 八进制 %s 字符串 %x 十六进制数 示例: awk -F '#' '{printf "%c\n",$1}' grade.txt awk -F '#' '{printf "%c\t%d\n",$1,$1}' grade.txt 1.1 深入讨论 awk ① awk 数组 ② awk 'BEGIN {print split("as#qw#1234",array2,"#")}' //3 表示数组有3个元素 ③ 举例说明 (awk_array.sh) 示例: awk 'BEGIN {split("as#qw#1234",array2,"#"); print array2[1]}' //awk中的下标是从1开始的 awk 'BEGIN {split("as#qw#1234",array2,"#"); print array2[1],"\t",array2[2],"\t",array2[3]}' 举例说明 (awk_array.sh) #!/bin/awk -f #awk_array.sh BEGIN{ FS="#" //一个内置变量, 它表示分隔符 score["0-60"]=0 score["60-70"]=0 score["70-80"]=0 score["80-90"]=0 score["90-100"]=0 student["junior"]=0 student["senior"]=0 } { {if ($1<60) score["0-60"]++ } {if ($1<70 && $1>=60) score["60-70"]++ } {if ($1<80 && $1>=70) score["70-80"]++ } {if ($1<90 && $1>=80) score["80-90"]++ } {if ($1<=100 && $1>=90) score["90-100"]++ } } { for (senior_junior in student) //依次读出student数组中的元素 赋值给变量senior_junior {if ($2==senior_junior) student[senior_junior]++ } } END{ {for (number in score) print "The score", number, "has", score[number], "students"} {for (senior_junior in student) print "The class has", student[senior_junior], senior_junior, "students"} } 注: ./awk_array.sh grade.txt man awk 查看一下 1.2 深入讨论 << 示例: #!/bin/bash loop_var=2 #main menu main_menu( ) { echo //echo两个空行 echo dis_mainmenu="CREAT MINISITE IN CHINAITLAB.COM" curdate=`date "+%Y-%m-%d %T"` cat <<mayday //进入标准输入部分 DATE : $curdate ================================================ $dis_mainmenu ================================================ ** 1)ADD MINISITE ACCOUNT ** ** 2)ADD DOMAIN IN CHINAITLAB.COM ** ** 3)ADD DATABASE IN MYSQL ** ** 4)ADD VIRTUAL HOST IN APACHE ** ** 5)BACKUP MINISITE ** ** 6)DELETE MINISITE ** ** 7)EXIT ** ================================================ mayday } while [ $loop_var -gt 0 ] do main_menu echo -n " Please choose [1-7]:" //-n 意思是不用回车换行 read main_choice case $main_choice in 7) exit ;; *) clear continue ;; esac done ---------------------------------------------------------- 第二章 shell 工具 内容: 日志文件; 信号; trap捕捉信号; eval ; logger 2.1 日志文件 ① 创建日志文件的重要性; ② 以时间为标识的日志文件; ③ 以进程号为标识的临时文件 示例: #!/bin/bash #datelog.sh #当前的日期 current_date=`date "+%Y%m%d"` //当前日期的格式,%Y 表示4位的年份 %m 2位的月份 %d当前日期 #今天的日志文件名 todaylog="log/${current_date}.log" #如果日志文件不存在, 创建一个 if [ ! -f $todaylog ] then touch $todaylog fi #输出日志到日志文件 log_time_format=`date "+%Y-%m-%d %T"` // %T 表示 小时:分:秒都打印出来, 以冒号分割 echo "${log_time_format} 命令开始" >>$todaylog # # command blocks sleep 4 # #输出日志到日志文件 log_time_format=`date "+%Y-%m-%d %T"` echo "${log_time_format} 命令结束" >>$todaylog 注: man date 查看一下 示例2: #!/bin/bash #kill_process.sh #取得当前进程号 current_PID=$$ //$$ 一个特殊的变量, 表示当前进程号 #获得特定进程的进程号并重定向到一个临时文件中 ps -aux | grep "/usr/sbin/httpd" | grep -v "grep" | awk '{print $2}' > /tmp/${current_PID}.txt //grep -v "grep" 表示过滤掉grep #命令块开始 for pid in `cat /tmp/${current_PID}.txt` do { echo "kill -9 $pid" kill -9 $pid } done #命令块结束 #删除临时文件 echo "rm -f /tmp/${current_PID}.txt" rm -f /tmp/${current_PID}.txt 2.2 信号 ① 信号就是系统向脚本或命令发出的消息, 告知它们某个事件的发生; ② kill -l //列出所有的信号 ③ kill 发送信号给进程. 2.2 信号 (续) 1 SIGHUP 挂起或父进程被杀死 (使子进程挂起后杀死子进程, 或杀下父进程是子进程号的子进程) 2 SIGINT 来自键盘的中断信号, 通常是CTRL+C 3 SIGQUIT 从键盘退出 9 SIGKILL 无条件终止 11 SIGSEGV 段(内存)冲突 15 SIGTERM 软件终止(缺省杀进程) 注: 信号0为"退出shell"信号. 为了发出信号0, 只要从命令行键入exit, 或在一个进程或命令行中使用<CTRL+D>即可. 示例: kill -s SIGKILL 7686 kill -9 7686 2.3 trap 捕捉信号 ① 信号可以被应用程序或脚本捕捉, 并依据该信号(1,2,3和15)采取相应的行动. 一些信号不能被捕捉. 例如, 如果一个命令收到了信号9, 就无法再捕捉其它信号. (9 是由系统来直接进行处理的一个信号) ② 捕捉到一个信号后, 它可能会采取下面三种操作之一: 1) 不采取任何行动, 由系统来进行处理; 2) 捕获该信号, 但忽略它; 3) 捕获该信号, 并采取相应的行动. 2.3 trap 捕捉信号 ① trap 可以使你在脚本中捕捉信号. 命令形式为: trap name signal(s) 其中, name 是捕捉到信号以后所采取的一系列操作. 实际中, name 一般是一个专门用来处理所捕捉信号的函数. name 需要用双引号(" ")引起来. signal 就是待捕捉的信号. (信号可以是多个信号, 由空格来分割) 最常见的行动包括: 1) 清除临时文件; 2) 忽略该信号; (trap "" 2 3) 3) 询问用户是否终止该脚本的运行. 举例 (trap1.sh, trap2.sh) #!/bin/bash #trap1.sh trap 'exitprocess' 2 LOOP=0 function exitprocess( ) { echo "You just hit <CTRL+C>, at number $LOOP" echo "I will now exit" exit 1 } while : //这个循环总是为真 do LOOP=$[$LOOP+1] echo $LOOP sleep 1 done 示例2: #!/bin/bash #trap2.sh LOOP=0 trap 'exitprocess' 2 HOLD1=/tmp/hold1.$$ HOLD2=/tmp/hold2.$$ function exitprocess( ) { echo -e "\nRecived Interrupt ..." echo -n "Do you really wish to exit?(Y/N)" read ANS case $ANS in Y|y) rm_tmp_file ;; N|n) ;; *) exitprocess ;; esac } function rm_tmp_file( ) { echo "<CTRL-C> detected ... Now cleaning up ...wait" rm /tmp/*.$$ 2>/dev/null exit 1 } while : do LOOP=$[$LOOP+1] echo $LOOP df>>$HOLD1 ps -xa >>$HOLD2 sleep 1 done 2.4 eval 命令 ① eval 命令将会首先扫描命令行进行所有的置换, 然后再执行该命令. 该命令适用于那些一次扫描无法实现其功能的变量. ② MYFILE="cat myfile"; `eval $MYFILE` eval `cat myfile` 2.4 logger ① logger 命令向 /var/log/message文件发送消息; (写消息进去) ② logger 命令的一般形式为: logger -p -i message -p : 为优先级, 这里只涉及到提示用户注意的优先级, 这也是缺省值. -i : 在每个消息中记录发送消息的进程号 示例: cat /var/log/messages 记录一些非常重要的日志; 系统进程 syslogd 负责把系统信息输出到响应的文件中; logger -p 1 -i "chinaitlab shenzhen" cat /var/log/messages --------------------------------------------------------------------------- 第三章 运行级别脚本介绍 内容: ① 运行级别 ② inittab ③ 启动应用程序 3.1 运行级别 ① 运行级别目录 (/etc/rcN.d) ② 当前运行级别(runlevel) ③ 运行级别目录文件格式 (进入目录/etc/rcN.d 后, 可以看到) (SXXscript, KXXscript) SXXscript : S开头表示在此优先级下面, 这个脚本的服务是在运行着的; KXXscript: K开头表示在此优先级下面, 这个脚本的服务是被停止的. 注: 都是一些超链接文件. S00--S99 : 按照阿拉伯数字的大小, 从小到大一次启动. 服务启动的相互依赖性, 被依赖的服务(数字小的服务)必须先启动. 3.2 inittab ① 运行级别控制文件 (/etc/inittab) cat 查看一下. ② 修改inittab文件 示例: # Thing to run in every runlevel ud::once:/sbin/update checkdisk:3:once:/sbin/checkdisk.sh > /dev/console 2> &1 3.2 启动应用程序 ① 启动脚本分析 (start | stop | restart) cat /etc/init.d/crond ② 启动脚本 service server start | stop | restart | ... //server 是脚本的名字; start | stop | restart | 是参数,1,2,3 script_name start | stop | restart 例: vi /etc/rc3.d/S90crond 示例: service crond stop service crond restart service crond status 注: 运行脚本通常放在 /etc/init.d 目录下面 --------------------------------------------------------------------------- 第四章 几个脚本的例子 ① kill_processes.sh ② cpdir.sh ③ menu.sh 示例1: #!/bin/bash #kill_process.sh #取得当前进程号 current_PID=$$ //$$ 一个特殊的变量, 表示当前进程号 #获得特定进程的进程号并重定向到一个临时文件中 ps -aux | grep "/usr/sbin/httpd" | grep -v "grep" | awk '{print $2}' > /tmp/${current_PID}.txt //grep -v "grep" 表示过滤掉grep #命令块开始 for pid in `cat /tmp/${current_PID}.txt` do { echo "kill -9 $pid" kill -9 $pid } done #命令块结束 #删除临时文件 echo "rm -f /tmp/${current_PID}.txt" rm -f /tmp/${current_PID}.txt 示例2: #!/bin/bash #cpdir.sh #此脚本用于将源目录下的子目录全部复制到目的目录中,不复制源目录中的文件, #确保目的目录中的子目录是空目录. #脚本用法函数 usage( ) { echo "cpdir.sh 源目录 目的目录" } #判断是否为两个参数, 否则提示脚本 用法 if [ $# -ne 2 ] then { usage exit 0 } fi srcdir=$1 desdir=$2 #判断源目录${srcdir}是否为目录, 否则提示错误信息和用法 if [ ! -d $srcdir ] then { usage echo "错误: 源目录${srcdir}不是目录" exit } fi #判断目的目录${desdir}是否为目录, 否则提示错误信息和用法 if [ ! -d $desdir ] then { usage echo "错误: 目的目录${desdir}不是目录" exit } fi processid=$$; #查找源目录下所有的子目录, 输出并保存到 /tmp/srcdir_进程号.txt文件中 echo "源目录下${srcdir}所有的子目录" echo "-------------------------------------" find $srcdir/* -type d | /usr/bin/tee /tmp/srcdir_tmp_${processid}.txt #替换 sed "s/^${srcdir}/${desdir}/g" /tmp/srcdir_tmp_${processid}.txt > /tmp/srcdir_${processid}.txt #在目的目录下建立空子目录 rm -rf ${desdir}/* for subdir in `cat /tmp/srcdir_${processid}.txt` do { mkdir ${subdir} } done echo "" echo "目标目录下${desdir}所有的子目录" echo "-------------------------------------" find $desdir/* -type d | /usr/bin/tee /tmp/desdir_${processid}.txt #比较在目的目录下建立空子目录后的差异 echo "" echo "比较目标目录和源目录的差异" echo "-----------------------------------" diff /tmp/desdir_${processid}.txt /tmp/srcdir_${processid}.txt rm -f /tmp/srcdir_${processid}.txt rm -f /tmp/desdir_${processid}.txt rm -f /tmp/srcdir_tmp_${processid}.txt |
|
来自: 看风景D人 > 《shell脚本编程》