Linux笔记:4-Shell Script

发布时间 2023-05-29 00:00:51作者: CD、小月

@

学习 Shell Scripts

关于Shell Scripts

shell script 号称是程序 (program) ,但实际上, shell script 处理数据的速度上是不太够的。
 
因为 shell script 用的是外部的指令与 bash shell 的一些默认工具,所以,他常常会去呼叫外部的函式库,因此,指令周期上面当然比不上传统的程序语言。 所以啰, shell script 用在系统管理上面是很好的一项工具,但是用在处理大量数值运算上, 就不够好了,因为 Shell scripts 的速度较慢,且使用的 CPU 资源较多,造成主机资源的分配不良。

shell script书写注意事项:

  1. 指令的执行是从上而下、从左而右的分析与执行;
  2. 指令、选项与参数间的多个空白都会被忽略掉;
  3. 空白行也将被忽略掉,并且 [tab] 按键所推开的空白同样视为空格键;
  4. 如果读取到一个 Enter 符号 (CR) ,就尝试开始执行该行 (或该串) 命令;
  5. 至于如果一行的内容太多,则可以使用『 [Enter] 』来延伸至下一行;
  6. 『 # 』可做为批注!任何加在 # 后面的资料将全部被视为批注文字而被忽略!

shell script运行:
以 /home/dmtsai/shell.sh 为例:
 

  • 直接指令下达: shell.sh 文件必须要具备可读与可执行 (rx) 的权限,然后:
    o 绝对路径:使用 /home/dmtsai/shell.sh 来下达指令;
    o 相对路径:假设工作目录在 /home/dmtsai/ ,则使用 ./shell.sh 来执行
    o 变量『PATH』功能:将 shell.sh 放在 PATH 指定的目录内,例如: ~/bin/
  • 以 bash 程序来执行:透过『 bash shell.sh 』或『 sh shell.sh 』来执行
  • 利用 source 或小数点 (.) 来执行

注意:
 
为何可以使用『./shell.sh 』执行执行:
由于CentOS 默认用户家目录下的 ~/bin 目录会被设定到 ${PATH} 内,所以你也可以将 shell.sh 建立在/home/dmtsai/bin/ 底下 ( ~/bin 目录需要自行设定) 。
此时,若 shell.sh 在 ~/bin 内且具有 rx 的权限,那就直接输入 shell.sh 即可执行该脚本程序
 
为何『 sh shell.sh 』也可以执行:
因为 /bin/sh 其实就是 /bin/bash (连结档),使用 sh shell.sh亦即告诉系统,我想要直接以 bash 的功能来执行 shell.sh 这个文件内的相关指令的意思,所以此时你的 shell.sh 只要有 r 的权限即可被执行。 我们也可以利用 sh 的参数,如 -n 及 -x 来检查与追踪 shell.sh 的语法是否正确。

shell 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 名称:
    因为我们使用的是 bash ,所以,必须要以『 #!/bin/bash 』来宣告这个文件内的语法使用 bash 的语法。那么当这个程序被执行时,他就能够加载 bash 的相关环境配置文件 (一般来说就是 non-login shell 的~/.bashrc), 并且执行 bash 来使我们底下的指令能够执行!这很重要的!(在很多状况中,如果没有设定好这一行, 那么该程序很可能会无法执行,因为系统可能无法判断该程序需要使用什么 shell 来执行。)
  2. 程序内容的说明:
    整个 script 当中,除了第一行的『 #! 』是用来宣告 shell 的之外,其他的 # 都是『批注』用途。
    所以上面的程序当中,第二行以下就是用来说明整个程序的基本数据。一般来说, 建议你一定要养成说明该script 的:1. 内容与功能; 2. 版本信息; 3. 作者与联络方式; 4. 建檔日期;5. 历史纪录 等等。这将有助于未来程序的改写与 debug
  3. 主要环境变量的宣告:
    建议务必要将一些重要的环境变量设定好,如此一来,则可让我们这支程序在进行时,可以直接下达一些外部指令,而不必写绝对路径。
  4. 主要程序部分:
    就将主要的程序写好即可!在这个例子当中,就是 echo 那一行
  5. 执行成果告知 (定义回传值):
    利用 exit 这个指令来让程序中断,并且回传一个数值给系统,利用这个 exit n (n 是数字) 的功能,我们还可以自定义错误讯息, 让这支程序变得更加的智能。

script 的执行方式差异 (source, sh script, ./script)详解

脚本的执行方式除了前面小节谈到的方式之外,还可以利用 source 或小数点 (.) 来执行。

利用直接执行的方式来执行 script

直接指令下达 (不论是绝对路径/相对路径还是 ${PATH} 内),或者是利用bash (或 sh) 来下达脚本时, 该 script 都会使用一个新的 bash 环境来执行脚本内的指令。
也就是说,使用这种执行方式时, 其实 script 是在子程序的 bash 内执行
需要注意的是:重点在于:『当子程序完成后,在子程序内的各项变量或动作将会结束而不会传回到父程序中』

以showname.sh 脚本执行为例,执行示意图:

在这里插入图片描述

利用 source 来执行脚本:在父程序中执行

source执行的脚本是在父程序的的环境中执行。

示意图如下:

在这里插入图片描述

script 中的普通条件判断表达式

普通条件判断表达式:即为根据判断条件,后面接单一指令。

利用 test 指令的测试功能

要检测系统上面某些文件或者是相关的属性时,可以利用 test 这个指令

[dmtsai@study ~]$ test -e /dmtsai
#检查 /dmtsai是否存在

参数 e 为test的测试标志(测试什么东西)参数;
test 可以测试的标志参数如下:

测试的标志 代表意义
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. 判定字符串的数据
-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)
6. 多重条件判定,例如: test -r filename -a -x filename
-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)

利用判断符号 [ ]

举例来说,如果我想要知道 ${HOME} 这个变量是否为空的,可以这样做:

[dmtsai@study ~]$ [ -z "${HOME}" ] ; echo $?

使用中括号必须要特别注意,因为中括号用在很多地方,包括通配符与正规表示法等等,所以如果要在 bash 的语法当中使用中括号作为 shell 的判断式时,必须要注意中括号的两端需要有空格符来分隔

使用注意:

  • 在中括号 [] 内的每个组件都需要有空格键来分隔;
  • 在中括号内的变数,最好都以双引号括号起来;
  • 在中括号内的常数,最好都以单或双引号括号起来。

Shell script 的默认参数($0, $1...)

script 针对 Shell script 传入的参数有设定好一些变量名称;

/path/to/scriptname opt1 opt2 opt3 opt4
	$0 				 $1   $2   $3   $4

除了这些数字的变量之外,还有一些较为特殊的变量可以在 script 中使用:

  • $# :代表后接的参数『个数』,以上表为例这里显示为『 4 』;
  • $@ :代表『 "$1" "$2" "$3" "$4" 』之意,每个变量是独立的(用双引号括起来);
  • $* :代表『 "$1c$2c$3c$4" 』,其中 c 为分隔字符,默认为空格键, 所以本例中代表『 "$1 $2 $3 $4" 』之意

shift:造成参数变量号码偏移

shift 后面可以接数字,代表拿掉最前面的几个参数的意思;不带数字,默认为1。

script 中的条件判断表达式

条件判断表达式(如『 if then 』):可根据不同的判断条件,处理多条指明

if .... then

if ... then 的判断可以是多层判断:

单层、简单条件判断式

if [ 条件判断式 ]; then
	当条件判断式成立时,可以进行的指令工作内容;
fi <==将 if 反过来写,就成为 fi 啦!结束 if 之意!

多重、复杂条件判断式

# 一个条件判断,分成功进行与失败进行 (else)
if [ 条件判断式 ]; then
	当条件判断式成立时,可以进行的指令工作内容;
else
	当条件判断式不成立时,可以进行的指令工作内容;
fi

#更复杂的情况,则可以使用这个语法:

# 多个条件判断 (if ... elif ... elif ... else) 分多种不同情况执行
if [ 条件判断式一 ]; then
	当条件判断式一成立时,可以进行的指令工作内容;
elif [ 条件判断式二 ]; then
	当条件判断式二成立时,可以进行的指令工作内容;
else
	当条件判断式一与二均不成立时,可以进行的指令工作内容;
fi

利用 case ..... esac 判断

case $变量名称 in <==关键词为 case ,还有变数前有钱字号
    "第一个变量内容") <==每个变量内容建议用双引号括起来,关键词则为小括号 )
        程序段
        ;; <==每个类别结尾使用两个连续的分号来处理!
    "第二个变量内容")
        程序段
        ;;
	*) <==最后一个变量内容都会用 * 来代表所有其他值
    不包含第一个变量内容与第二个变量内容的其他程序执行段
    exit 1
    ;;
esac <==最终的 case 结尾!『反过来写』思考一下!

一般来说,使用『 case $变量 in 』这个语法中,当中的那个『 $变量 』大致有两种取得的方式:

  • 直接下达式:例如上面提到的,利用『 script.sh variable 』 的方式来直接给予 $1 这个变量的内容,这也是在 /etc/init.d 目录下大多数程序的设计方式。
  • 交互式:透过 read 这个指令来让用户输入变量的内容。

利用 function 功能

function fname() {
	程序段
}

要注意的是,因为 shell script 的执行方式是由上而下,由左而右
因此在 shell script 当中的 function 的设定一定要在程序的最前面, 这样才能够在执行时被找到可用的程序段。

function 也是拥有参数变量的~他的内建变量与 shell script 很类似, 函数名称代表示 $0 ,而后续接的参数也是以 $1, $2... 来取代的 。

script 中的循环(loop)表达式

循环可以不断的执行某个程序段落,直到用户设定的条件达成为止。 所以,重点是那个『条件的达成』是什么。
循环分为不确定循环固定循环

while do done, until do done (不定循环)

不定循环的两种方式:
#方式一:
while [ condition ] <==中括号内的状态就是判断式 当 condition 条件成立时,就进行循环,直到condition 的条件不成立才停止
	do <==do 是循环的开始!
		程序段落
done <==done 是循环的结束

#方式二:
until [ condition ] <==当 condition 条件成立时,就终止循环, 否则就持续进行循环的程序段。
	do
		程序段落
done

for...do...done (固定循环)

for var in con1 con2 con3 ...  <== 变量var 在循环中依次等于 con1 con2 con3 ...
	do
		程序段
done

for...do...done 的数值处理

除了上述的方法之外,for 循环还有另外一种写法

for (( 初始值; 限制值; 执行步阶 ))
    do
    	程序段
done

for 后面的括号内的三串内容意义为:

  • 初始值:某个变量在循环当中的起始值,直接以类似 i=1 设定好;
  • 限制值:当变量的值在这个限制值的范围内,就继续进行循环。例如 i<=100;
  • 执行步阶:每作一次循环时,变量的变化量。例如 i=i+1

shell script 的追踪与 debug

可以通过 bash 中的参数,不运行 script,而进行 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....