鸟哥的linux私房菜——第十二章学习(Shell Scripts)
第十二章 Shell Scripts
1.0)、什么是shell scripts?
script 是“脚本、剧本”的意思。整句话是说, shell script 是针对 shell 所写的“剧本!”
shell script 是利用 shell 的功能所写的一个“程序 (program)”,这个程序是使用纯文本文件,将一些 shell 的语法与指令(含外部指令)写在里面, 搭配正则表达式、管线命令与数据流重导向等功能,以达到我们所想要的处理目的。
就像是早期 DOS 年代的批处理文件 (.bat) ,最简单的功能就是将许多指令汇整写在一起,不需要编译即可执行。
1.1)、shell scripts优点
l 自动化管理的重要依据
l 追踪与管理系统的重要工作
l 简单入侵侦测功能
l 连续指令单一化
l 简易的数据处理
l 跨平台支持与学习历程较短
2.0)、第一支script 的撰写与执行
注意事项:
1. 指令的执行是从上而下、从左而右的分析与执行;
2. 指令的下达就如同第四章内提到的: 指令、选项与参数间的多个空白都会被忽略掉;
3. 空白行也将被忽略掉,并且 [tab] 按键所推开的空白同样视为空白键;
4. 如果读取到一个 Enter 符号 (CR) ,就尝试开始执行该行 (或该串) 命令;
5. 至于如果一行的内容太多,则可以使用“ \[Enter] ”来延伸至下一行;
6. “ # ”可做为注解!任何加在 # 后面的数据将全部被视为注解文字而被忽略!
假设shell.sh文件在/home/dmtsai/目录下,那么执行sh文件方式:
l 直接指令下达:shell.sh 文件必须要具备可读与可执行 (rx) 的权限,然后:
- 绝对路径:使用 /home/dmtsai/<filename>.sh 来下达指令;
- 相对路径:假设工作目录在 /home/dmtsai/ ,则使用 ./shell.sh 来执行;
- 变量“PATH”功能:将 shell.sh 放在 PATH 指定的目录内,例如: “ ~/bin/ ”。
l 以bash程序来执行:通过“bash <filename>.sh”或者“sh <filename>.sh”
解析:
因为 /bin/sh 其实就是 /bin/bash (链接文件),使用 sh shell.sh 亦即告诉系统,我想要直接以bash 的功能来执行 shell.sh 这个文件内的相关指令的意思,
【第一支script】
[dmtsai@study ~]$ mkdir bin; cd bin [dmtsai@study bin]$ vim hello.sh #!/bin/bash # Program: # This program shows "Hello World!" in your screen. # History: # 2015/07/16 VBird First release PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin export PATH echo -e "Hello World! \a \n" exit 0
注解:
1、第一行 #!/bin/bash 在宣告这个 script 使用的 shell 名称;
2、整个 script 当中,除了第一行的“ #! ”是用来宣告 shell 的之外,其他的 # 都是“注解”用途;
3、主要环境变量的宣告PATH;
4、主要程序部分,就是echo那一行;
5、执行成果告知:讨论一个指令的执行成功与否,可以使用 $? 这个变量来观察(如接着使用echo $? 即可查询命令是否执行成功),在srcipt中我们也可以利用 exit 这个指令来让程序中断,并且回传一个数值给系统,如exit n(n是一个数字)。
2.1)、简单的数值运算:+,-,*,、,%
运算时,可使用:echo $(( 运算内容 ))
小数点:| bc,是“basic calculator”的缩写。
计算PI值:
[dmtsai@study bin]$ vim cal_pi.sh #!/bin/bash # Program: # User input a scale number to calculate pi number. # History: # 2015/07/16 VBird First release PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin export PATH echo -e "This program will calculate pi value. \n" echo -e "You should input a float number to calculate pi value.\n" read -p "The scale number (10~10000) ? " checking num=${checking:-"10"} # 开始判断有否有输入数值 echo -e "Starting calcuate pi value. Be patient." time echo "scale=${num}; 4*a(1)" | bc -lq
注释:
tan 45° =1; 45°也可以写作 π/4,四分之派。所以arctan 1(echo中的a(1))的值也就是π/4,乘以4当然就是π的值。
2.2)、script的执行方式差异(source, sh script, ./script)
l 利用直接执行的方式来执行 script
当子程序完成后,在子程序内的各项变量或动作将会结束而不会传回到父程序中。
意思就是:当你使用直接执行的方法来处理时,系统会给予一支新的 bash 让我们来执行 showname.sh 里面的指令,因此你的 firstname, lastname 等变量其实是在下图中的子程序 bash 内执行的。 当 showname.sh 执行完毕后,子程序 bash 内的所有数据便被移除,因此上表的练习中,在父程序下面 echo ${firstname} 时, 就看不到任何东西了。类似于局部变量。
l 利用 source 来执行脚本:在父程序中执行
showname.sh 会在父程序中执行的,因此各项动作都会在原本的 bash 内生效。
2.3)、善用判断(test, &&, ||, [])
l test, &&, ||
例子:检查 /dmtsai 是否存在时,使用:
[dmtsai@study ~]$ test -e /dmtsai && echo "exist" || echo "Not exist" Not exist <==结果显示不存在啊!
l []:中括号
作为判断式时,必须要注意中括号的两端需要有空白字符来分隔。
注意:
- 在中括号 [] 内的每个元件都需要有空白键来分隔;
- 在中括号内的变量,最好都以双引号括号起来;
- 在中括号内的常数,最好都以单或双引号括号起来。
如:判断两个字符串是否相等:
[dmtsai@study ~]$ name="VBird Tsai" [dmtsai@study ~]$ [ ${name} == "VBird" ] bash: [: too many arguments
原因就是${name}没有用双引号扩住。
所以上面解析式会变成:[ VBird Tsai == "VBird" ]
但是我们期望的是[ "VBird Tsai" == "VBird" ],所以就需要加上引号了。
另:中括号比较常用在条件判断式 if ..... then ..... fi 的情况中。
【案例:提示输入交互判断】
[dmtsai@study bin]$ vim ans_yn.sh #!/bin/bash # Program: # This program shows the user's choice # History: # 2015/07/16 VBird First release PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin export PATH read -p "Please input (Y/N): " yn [ "${yn}" == "Y" -o "${yn}" == "y" ] && echo "OK, continue" && exit 0 [ "${yn}" == "N" -o "${yn}" == "n" ] && echo "Oh, interrupt!" && exit 0 echo "I don't know what your choice is" && exit 0
注解:
1、由于输入正确 (Yes) 的方法有大小写之分,不论输入大写 Y 或小写 y 都是可以的,此时判断式内就得要有两个判断才行!
2、这里使用 -o (或) 链接两个判断。不能用“||”。
l Shell Script 的默认变量($0, $1...)
read 功能的问题是你得要手动由键盘输入一些判断式。
通过指令后面接参数, 那么一个指令就能够处理完毕而不需要手动再次输入一些变量行为。
/path/to/scriptname opt1 opt2 opt3 opt4 $0 $1 $2 $3 $4
- $# :代表后接的参数“个数”,以上表为例这里显示为“ 4 ”;
- $@ :代表“ "$1" "$2" "$3" "$4" ”之意,每个变量是独立的(用双引号括起来);
- $* :代表“ "$1c$2c$3c$4" ”,其中 c 为分隔字符,默认为空白键, 所以本例中代表“ "$1 $2 $3 $4" ”之意。
2.4)、条件判断式(if...then, case...esac, function)
l if...then
if [ 条件判断式 ]; then 当条件判断式成立时,可以进行的指令工作内容; fi <==将 if 反过来写,就成为 fi 啦!结束 if 之意!
&& 代表 AND ;
|| 代表 or ;
所以:
[ "${yn}" == "Y" -o "${yn}" == "y" ] 上式可替换为 [ "${yn}" == "Y" ] || [ "${yn}" == "y" ]
多重判断:
# 多个条件判断 (if ... elif ... elif ... else) 分多种不同情况执行 if [ 条件判断式一 ]; then <==【if和[之间有空格】 当条件判断式一成立时,可以进行的指令工作内容; elif [ 条件判断式二 ]; then 当条件判断式二成立时,可以进行的指令工作内容; else 当条件判断式一与二均不成立时,可以进行的指令工作内容; fi
l case...esac
[dmtsai@study bin]$ vim hello-3.sh #!/bin/bash # Program: # Show "Hello" from $1.... by using case .... esac # History: # 2015/07/16 VBird First release PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin export PATH case ${1} in "hello") echo "Hello, how are you ?" ;; "") echo "You MUST input parameters, ex> {${0} someword}" ;; *) # 其实就相当于万用字符,0~无穷多个任意字符之意! echo "Usage ${0} {hello}" ;; esac
l function
[dmtsai@study bin]$ vim show123-3.sh #!/bin/bash # Program: # Use function to repeat information. # History: # 2015/07/17 VBird First release PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin export PATH function printit(){ echo "Your choice is ${1}" # 这个 $1 必须要参考下面指令的下达 } echo "This program will print your selection !" case ${1} in "one") printit 1 # 请注意, printit 指令后面还有接参数! ;; "two") printit 2 ;; "three") printit 3 ;; *) echo "Usage ${0} {one|two|three}" ;; esac
输入“ sh show123-3.sh one ”就会出现“ Your choice is 1 ”,“ printit 1 ”中那个 1 就会成为 function 当中的 $1 。
function 也是拥有内置变量的~他的内置变量与 shell script 很类似, 函数名称代表示 $0 ,而后续接的变量也是以 $1, $2... 来取代。
特别注意的是,“ function fname() { 程序段 } ”内的 $0, $1... 等等与 shell script 的 $0 是不同的。
l 常见的port与网络服务关系:
80: WWW
22: ssh
21: ftp
25: mail
111: RPC(远端程序调用)
631: CUPS(打印服务功能)
2.5)、循环(loop)
循环可以不断的执行某个程序段落,直到使用者设置的条件达成为止。
循环又分为不定循环和固定循环。
l 不定循环:while do done, until do done
- while do done
while [ condition ] <==中括号内的状态就是判断式【while和[之间有空格】 do <==do 是循环的开始! 程序段落 done <==done 是循环的结束
例子:
[dmtsai@study bin]$ vim yes_to_stop.sh #!/bin/bash # Program: # Repeat question until user input correct answer. # History: # 2015/07/17 VBird First release PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin export PATH while [ "${yn}" != "yes" -a "${yn}" != "YES" ] do read -p "Please input yes/YES to stop this program: " yn done echo "OK! you input the correct answer."
- until do done
接上述例子:
[dmtsai@study bin]$ vim yes_to_stop-2.sh #!/bin/bash # Program: # Repeat question until user input correct answer. # History: # 2015/07/17 VBird First release PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin export PATH until [ "${yn}" == "yes" -o "${yn}" == "YES" ] do read -p "Please input yes/YES to stop this program: " yn done echo "OK! you input the correct answer."
注:-a 表示且的意思,-o 表示或的意思。
【举例:计算1--100的和】
[dmtsai@study bin]$ vim cal_1_100.sh #!/bin/bash # Program: # Repeat question until user input correct answer. # History: # 2015/07/17 VBird First release PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin export PATH echo "this program is to calculate from 1 to 100." read -p "Now do u want to start(y/n)?" yn sum=0; if [ "${yn}" == "y" -o "${yn}" == "Y" ]; then i=0; while [ "${i}" -le "100" ] # 可换成for i in $(seq 1 100) do echo " i = ${i} " sum=$(( $sum+$i )) i=$(( $i+1 )) done echo "1+2+...+100 = ${sum}" elif [ "${yn}" == "n" -o "${yn}" == "N" ]; then echo "U dont want to calculate,if u want, plz retry and input y." else echo "I dont your choose." fi exit 0
上述中的while [...]这一行还可以换成for i in $(seq 1 100)
seq表示连续的。其缩写为{1..100} 来取代 $(seq 1 100)
l 固定循环for...do...done
for的用法有两种:
- 第一种:
for var in con1 con2 con3 ... do 程序段 done
注:
1. 第一次循环时, $var 的内容为 con1 ;
2. 第二次循环时, $var 的内容为 con2 ;
3. 第三次循环时, $var 的内容为 con3 ;
依次循环。
- 第二种:
for(( 初始值; 限制值; 执行步阶)) <==【for和(之间可以有空格,也可没有】 do 程序段 done
1.初始值:某个变量在循环当中的起始值,直接以类似 i=1 设置好;
2.限制值:当变量的值在这个限制值的范围内,就继续进行循环。例如 i<=100;
3.执行步阶:每作一次循环时,变量的变化量。例如 i=i+1,亦可写成i++。
除了使用大小等于符号之外,还有如下符号表示:
-eq |
等于 |
-ne |
不等于 |
-gt |
大于 |
-lt |
小于 |
-ge |
大于等于 |
-le |
小于等于 |
注意:
语法问题:
if [ 条件判断式一 ]; then <==【if和[之间必须有空格】
while [ condition ] <==中括号内的状态就是判断式【while和[之间必须有空格】
for(( 初始值; 限制值; 执行步阶)) <==【for和(之间可以有空格,也可没有】
3.0)、追踪(debug)
debug
[dmtsai@study ~]$ sh [-nvx] scripts.sh 选项与参数: -n :不要执行 script,仅查询语法的问题; -v :再执行 sccript 前,先将 scripts 的内容输出到屏幕上; -x :将使用到的 script 内容显示到屏幕上,这是很有用的参数! 范例一:测试 dir_perm.sh 有无语法的问题? [dmtsai@study ~]$ sh -n dir_perm.sh # 若语法没有问题,则不会显示任何信息! 范例二:将 show_animal.sh 的执行过程全部列出来~ [dmtsai@study ~]$ sh -x show_animal.sh + PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:/root/bin + export PATH + for animal in dog cat elephant + echo 'There are dogs.... ' There are dogs.... + for animal in dog cat elephant + echo 'There are cats.... ' There are cats.... + for animal in dog cat elephant + echo 'There are elephants.... ' There are elephants....
【重点回顾】
- shell script 是利用 shell 的功能所写的一个“程序 (program)”,这个程序是使用纯文本文件,将一些 shell 的语法与指令(含外部指令)写在里面, 搭配正则表达式、管线命令与数据流重导向等功能,以达到我们所想要的处理目的;
- shell script 用在系统管理上面是很好的一项工具,但是用在处理大量数值运算上, 就不够好了,因为 Shell scripts 的速度较慢,且使用的CPU 资源较多,造成主机资源的分配不良;
- 在 Shell script 的文件中,指令的执行是从上而下、从左而右的分析与执行;
- shell script 的执行,至少需要有 r 的权限,若需要直接指令下达,则需要拥有 r 与 x 的权限;
- 良好的程序撰写习惯中,第一行要宣告 shell (#!/bin/bash) ,第二行以后则宣告程序用途、版本、作者等;
- 对谈式脚本可用 read 指令达成;
- 要创建每次执行脚本都有不同结果的数据,可使用 date 指令利用日期达成;
- script 的执行若以 source 来执行时,代表在父程序的 bash 内执行之意!
- 若需要进行判断式,可使用 test 或中括号 ( [] ) 来处理;
- 在 script 内,$0, $1, $2..., $@ 是有特殊意义的!
- 条件判断式可使用 if...then 来判断,若是固定变量内容的情况下,可使用 case $var in ... esac 来处理;
- 循环主要分为不定循环 (while, until) 以及固定循环 (for) ,配合 do, done 来达成所需任务!
- 我们可使用 sh -x script.sh 来进行程序的 debug。
Over...