鸟哥的Linux私房菜学习笔记(三)学习 Shell 与 Shell scripts——学习 shell scripts
1.什么是 Shell Script
在 shell script 的撰写中还需要用到底下的注意事项:
命令的运行是从上而下、从左而右的分析与运行;命令的下达就如同第五章内提到的: 命令、选项与参数间的多个空白都会被忽略掉;
空白行也将被忽略掉,并且 [tab] 按键所推开的空白同样视为空白键;
如果读取到一个 Enter 符号 (CR) ,就尝试开始运行该行 (或该串) 命令;
至於如果一行的内容太多,则可以使用『 \[Enter] 』来延伸至下一行;
『 # 』可做为注解!任何加在 # 后面的数据将全部被视为注解文字而被忽略!
那如何运行这个文件?很简单,可以有底下几个方法:
直接命令下达: shell.sh 文件必须要具备可读与可运行 (rx) 的权限,然后:
绝对路径:使用 /home/dmtsai/shell.sh 来下达命令;相对路径:假设工作目录在 /home/dmtsai/ ,则使用 ./shell.sh 来运行
变量『PATH』功能:将 shell.sh 放在 PATH 指定的目录内,例如: ~/bin/
以 bash 程序来运行:透过『 bash shell.sh 』或『 sh shell.sh 』来运行
撰写第一支 script
大致是这样:
1.第一行 #!/bin/bash 在宣告这个 script 使用的 shell 名称:因为我们使用的是 bash ,所以,必须要以『 #!/bin/bash 』来宣告这个文件内的语法使用 bash 的语法!那么当这个程序被运行时,他就能够加载 bash 的相关环境配置档 (一般来说就是 non-login shell 的 ~/.bashrc), 并且运行 bash 来使我们底下的命令能够运行!
2.程序内容的说明:除了第一行的『 #! 』是用来宣告 shell 的之外,其他的 # 都是『注解』用途!第二行以下就是用来说明整个程序的基本数据。一般来说, 建议你一定要养成说明该 script 的:1. 内容与功能; 2. 版本资讯; 3. 作者与联络方式; 4. 建档日期;5. 历史纪录 等等。这将有助於未来程序的改写与 debug 呢!
3.主要环境变量的宣告:建议务必要将一些重要的环境变量配置好,鸟哥个人认为, PATH 与 LANG (如果有使用到输出相关的资讯时) 是当中最重要的! 如此一来,则可让我们这支程序在进行时,可以直接下达一些外部命令,而不必写绝对路径呢!比较好啦!
4.主要程序部分就将主要的程序写好即可!在这个例子当中,就是 echo 那一行啦!
5.运行成果告知 (定义回传值)是否记得我们在第十一章里面要讨论一个命令的运行成功与否,可以使用 $? 这个变量来观察~ 那么我们也可以利用 exit 这个命令来让程序中断,并且回传一个数值给系统。 在我们这个例子当中,鸟哥使用 exit 0 ,这代表离开 script 并且回传一个 0 给系统, 所以我运行完这个 script 后,若接著下达 echo $? 则可得到 0 的值喔! 更聪明的读者应该也知道了,呵呵!利用这个 exit n (n 是数字) 的功能,我们还可以自订错误信息, 让这支程序变得更加的 smart 呢!
2.简单的 shell script 练习
1)简单范例: 对谈式脚本, 随日期变化, 数值运算
对谈式脚本:变量内容由使用者决定
很多时候我们需要使用者输入一些内容,好让程序可以顺利运行。如,可使用 read 命令。
随日期变化:利用 date 进行文件的创建
假设我的服务器内有数据库,数据库每天的数据都不太一样,因此当我备份时, 希望将每天的数据都备份成不同的档名,这样才能够让旧的数据也能够保存下来不被覆盖。 哇!不同档名呢!
数值运算:简单的加减乘除
可以使用 declare 来定义变量的类型吧? 当变量定义成为整数后才能够进行加减运算啊!此外,我们也可以利用『 $((计算式)) 』来进行数值运算的。 可惜的是, bash shell 里头默认仅支持到整数的数据而已。
在数值的运算上,有:『 +, -, *, /, % 』等等。我们可以使用『 declare -i total=$firstnu*$secnu 』 也可以使用上面的方式来进行!基本上,鸟哥比较建议使用这样的方式来进行运算:
var=$((运算内容))
2)script 的运行方式差异 (source, sh script, ./script)
利用直接运行的方式来运行 script
直接命令下达 (不论是绝对路径/相对路径还是 $PATH 内),或者是利用 bash (或 sh) 来下达脚本时, 该 script 都会使用一个新的 bash 环境来运行脚本内的命令!也就是说,使用者种运行方式时, 其实 script 是在子程序的 bash 内运行的!
利用 source 来运行脚本:在父程序中运行
如果你使用 source 来运行命令那就不一样了! sh02.sh 会在父程序中运行的,因此各项动作都会在原本的 bash 内生效!
3.善用判断式
1)利用 test 命令的测试功能
例如:
[[email protected] ~]# test -e /dmtsai && echo "exist" || echo "Not exist"
测试的标志及代表意义:
1. 关於某个档名的『文件类型』判断,如 test -e filename 表示存在否-e 该『档名』是否存在?(常用)
-f 该『档名』是否存在且为文件(file)?(常用)
-d 该『档名』是否存在且为目录(directory)?(常用)
-b 该『档名』是否存在且为一个 block device 装置?
-c 该『档名』是否存在且为一个 character device 装置?
-S 该『档名』是否存在且为一个 Socket 文件?
-p 该『档名』是否存在且为一个 FIFO (pipe) 文件?
-L 该『档名』是否存在且为一个连结档?
2. 关於文件的权限侦测,如 test -r filename 表示可读否 (但 root 权限常有例外)
-r 侦测该档名是否存在且具有『可读』的权限?
-w 侦测该档名是否存在且具有『可写』的权限?
-x 侦测该档名是否存在且具有『可运行』的权限?
-u 侦测该档名是否存在且具有『SUID』的属性?
-g 侦测该档名是否存在且具有『SGID』的属性?
-k 侦测该档名是否存在且具有『Sticky bit』的属性?
-s 侦测该档名是否存在且为『非空白文件』?
3. 两个文件之间的比较,如: test file1 -nt file2
-nt (newer than)判断 file1 是否比 file2 新
-ot (older than)判断 file1 是否比 file2 旧
-ef 判断 file1 与 file2 是否为同一文件,可用在判断 hard link 的判定上。 主要意义在判定,两个文件是否均指向同一个 inode 哩!
4. 关於两个整数之间的判定,例如 test n1 -eq n2
-eq 两数值相等 (equal)
-ne 两数值不等 (not equal)
-gt n1 大於 n2 (greater than)
-lt n1 小於 n2 (less than)
-ge n1 大於等於 n2 (greater than or equal)
-le n1 小於等於 n2 (less than or equal)
5. 判定字串的数据
test -z string 判定字串是否为 0 ?若 string 为空字串,则为 true
test -n string 判定字串是否非为 0 ?若 string 为空字串,则为 false。
注: -n 亦可省略
test str1 = str2 判定 str1 是否等於 str2 ,若相等,则回传 true
test str1 != str2 判定 str1 是否不等於 str2 ,若相等,则回传 false
6. 多重条件判定,例如: test -r filename -a -x filename
-a (and)两状况同时成立!例如 test -r file -a -x file,则 file 同时具有 r 与 x 权限时,才回传 true。
-o (or)两状况任何一个成立!例如 test -r file -o -x file,则 file 具有 r 或 x 权限时,就可回传 true。
! 反相状态,如 test ! -x file ,当 file 不具有 x 时,回传 true
2)利用判断符号 [ ]
除了我们很喜欢使用的 test 之外,其实,我们还可以利用判断符号『 [ ] 』(就是中括号啦) 来进行数据的判断呢! 举例来说,如果我想要知道 $HOME 这个变量是否为空的,可以这样做:
[[email protected] ~]# [ -z "$HOME" ] ; echo $?
如果要在 bash 的语法当中使用中括号作为 shell 的判断式时,必须要注意中括号的两端需要有空白字节来分隔喔!
所以说,你最好要注意:
在中括号 [] 内的每个组件都需要有空白键来分隔;在中括号内的变量,最好都以双引号括号起来;
在中括号内的常数,最好都以单或双引号括号起来。
3)Shell script 的默认变量($0, $1...): shift
命令可以带有选项与参数,例如 ls -la 可以察看包含隐藏档的所有属性与权限。那么 shell script 能不能在脚本档名后面带有参数呢?举例来说,如果你想要重新启动系统登录档的功能,可以这样做:
[[email protected] ~]# file /etc/init.d/syslog/etc/init.d/syslog: Bourne-Again shell script text executable
# 使用 file 来查询后,系统告知这个文件是个 bash 的可运行 script 喔!
[[email protected] ~]# /etc/init.d/syslog restart
那么如果你在 /etc/init.d/syslog 后面加上 stop 呢?没错!就可以直接关闭该服务了!
其实 script 针对参数已经有配置好一些变量名称了!对应如下:
/path/to/scriptname opt1 opt2 opt3 opt4
$0 $1 $2 $3 $4
除了这些数字的变量之外, 我们还有一些较为特殊的变量可以在 script 内使用来呼叫这些参数喔!
$# :代表后接的参数『个数』,以上表为例这里显示为『 4 』;
[email protected] :代表『 "$1" "$2" "$3" "$4" 』之意,每个变量是独立的(用双引号括起来);
$* :代表『 "$1c$2c$3c$4" 』,其中 c 为分隔字节,默认为空白键, 所以本例中代表『 "$1 $2 $3 $4" 』之意。
1)shift:造成参数变量号码偏移
除此之外,脚本后面所接的变量是否能够进行偏移 (shift) 呢?hift 会移动变量,而且 shift 后面可以接数字,代表拿掉最前面的几个参数的意思。
4.条件判断式
1)利用 if .... then: 单层简单条件, 多重复杂条件, 检验$1内容, 网络状态, 退伍
单层、简单条件判断式
语法如下:
if [ 条件判断式 ]; then
当条件判断式成立时,可以进行的命令工作内容;
fi <==将 if 反过来写,就成为 fi 啦!结束 if 之意!
例如:
read -p "Please input (Y/N): " yn
if [ "$yn" == "Y" ] || [ "$yn" == "y" ]; then
echo "OK, continue"
exit 0
fi
if [ "$yn" == "N" ] || [ "$yn" == "n" ]; then
echo "Oh, interrupt!"
exit 0
fi
多重、复杂条件判断式
语法如下:
# 一个条件判断,分成功进行与失败进行 (else)
if [ 条件判断式 ]; then
当条件判断式成立时,可以进行的命令工作内容;
else
当条件判断式不成立时,可以进行的命令工作内容;
fi
# 多个条件判断 (if ... elif ... elif ... else) 分多种不同情况运行
if [ 条件判断式一 ]; then
当条件判断式一成立时,可以进行的命令工作内容;
elif [ 条件判断式二 ]; then
当条件判断式二成立时,可以进行的命令工作内容;
else
当条件判断式一与二均不成立时,可以进行的命令工作内容;
fi
一般来说,如果你不希望使用者由键盘输入额外的数据时, 可以使用上一节提到的参数功能 ($1)!让使用者在下达命令时就将参数带进去!
例子:
if [ "$1" == "hello" ]; then
echo "Hello, how are you ?"
elif [ "$1" == "" ]; then
echo "You MUST input parameters, ex> {$0 someword}"
else
echo "The only parameter is 'hello', ex> {$0 hello}"
fi
2)利用 case ..... esac 判断
语法如下:
case $变量名称 in <==关键字为 case ,还有变量前有钱字号"第一个变量内容") <==每个变量内容建议用双引号括起来,关键字则为小括号 )
程序段
;; <==每个类别结尾使用两个连续的分号来处理!
"第二个变量内容")
程序段
;;
*) <==最后一个变量内容都会用 * 来代表所有其他值
不包含第一个变量内容与第二个变量内容的其他程序运行段
exit 1
;;
esac <==最终的 case 结尾!『反过来写』思考一下!
例子:
case $1 in
"hello")echo "Hello, how are you ?"
;;
"")
echo "You MUST input parameters, ex> {$0 someword}"
;;
*) # 其实就相当於万用字节,0~无穷多个任意字节之意!
echo "Usage $0 {hello}"
;;
esac
系统的很多服务的启动 scripts 都是使用这种写法的。
一般来说,使用『 case $变量 in 』这个语法中,当中的那个『 $变量 』大致有两种取得的方式:
直接下达式:例如上面提到的,利用『 script.sh variable 』 的方式来直接给予 $1 这个变量的内容,这也是在 /etc/init.d 目录下大多数程序的设计方式。互动式:透过 read 这个命令来让使用者输入变量的内容。
3)利用 function 功能
语法如下:
function fname() {
程序段
}
因为 shell script 的运行方式是由上而下,由左而右, 因此在 shell script 当中的 function 的配置一定要在程序的最前面, 这样才能够在运行时被找到可用的程序段喔!
另外, function 也是拥有内建变量的~他的内建变量与 shell script 很类似, 函数名称代表示 $0 ,而后续接的变量也是以 $1, $2... 来取代的~
5.回圈 (loop)
1)while...do...done, until...do...done (不定回圈)
语法如下:
while [ condition ] <==中括号内的状态就是判断式
do <==do 是回圈的开始!
程序段落
done <==done 是回圈的结束
while 的中文是『当....时』,所以,这种方式说的是『当 condition 条件成立时,就进行回圈,直到 condition 的条件不成立才停止』的意思。
语法如下:
until [ condition ]do
程序段落
done
这种方式恰恰与 while 相反,它说的是『当 condition 条件成立时,就终止回圈, 否则就持续进行回圈的程序段。』2)for...do...done (固定回圈): 帐号检查, 网络状态 $(seq )
语法如下:
for var in con1 con2 con3 ...do
程序段
done
例子:
for animal in dog cat elephant
users=$(cut -d ':' -f1 /etc/passwd) # 撷取帐号名称
for username in $users
for sitenu in $(seq 1 100)
3)for...do...done 的数值处理
语法如下:
for (( 初始值; 限制值; 运行步阶 ))do
程序段
done
例子:
for (( i=1; i<=$nu; i=i+1 ))
6.shell script 的追踪与 debug
scripts 在运行之前,最怕的就是出现语法错误的问题了!那么我们如何 debug 呢?有没有办法不需要透过直接运行该 scripts 就可以来判断是否有问题呢?呵呵!当然是有的!我们就直接以 bash 的相关参数来进行判断吧!
在输出的信息中,在加号后面的数据其实都是命令串,由於 sh -x 的方式来将命令运行过程也显示出来, 如此使用者可以判断程序码运行到哪一段时会出现相关的资讯!
参考自:http://cn.linux.vbird.org/linux_basic/linux_basic.php