鸟哥的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....

【重点回顾】

  1. shell script 是利用 shell 的功能所写的一个“程序 (program)”,这个程序是使用纯文本文件,将一些 shell 的语法与指令(含外部指令)写在里面, 搭配正则表达式、管线命令与数据流重导向等功能,以达到我们所想要的处理目的;
  2. shell script 用在系统管理上面是很好的一项工具,但是用在处理大量数值运算上, 就不够好了,因为 Shell scripts 的速度较慢,且使用的CPU 资源较多,造成主机资源的分配不良;
  3. 在 Shell script 的文件中,指令的执行是从上而下、从左而右的分析与执行;
  4. shell script 的执行,至少需要有 r 的权限,若需要直接指令下达,则需要拥有 r 与 x 的权限;
  5. 良好的程序撰写习惯中,第一行要宣告 shell (#!/bin/bash) ,第二行以后则宣告程序用途、版本、作者等;
  6. 对谈式脚本可用 read 指令达成;
  7. 要创建每次执行脚本都有不同结果的数据,可使用 date 指令利用日期达成;
  8. script 的执行若以 source 来执行时,代表在父程序的 bash 内执行之意!
  9. 若需要进行判断式,可使用 test 或中括号 ( [] ) 来处理;
  10. 在 script 内,$0, $1, $2..., $@ 是有特殊意义的!
  11. 条件判断式可使用 if...then 来判断,若是固定变量内容的情况下,可使用 case $var in ... esac 来处理;
  12. 循环主要分为不定循环 (while, until) 以及固定循环 (for) ,配合 do, done 来达成所需任务!
  13. 我们可使用 sh -x script.sh 来进行程序的 debug。

Over...

上一篇:iOS回顾笔记(09) -- Cell的添加、删除、更新、批量操作


下一篇:包含修改字体,图片上传等功能的文本输入框-Bootstrap