Linux操作系统(九):shell脚本

  • 什么是shell脚本与编写
  • shell脚本的逻辑表达式
  • shell脚本的跟踪与调试

 一、关于本文内容的导读

这部分不涉及具体内容的解析,只是作为浏览和查找相关知识点的引导内容,采用【主题 | 命令 | 对应内容小节编号】三个关键信息的组合模式,依照这些信息可以快速查找到相关详细的示例和解析。

关于shell脚本其实这一篇博客是收尾篇,在这之前已经有三篇博客铺垫了大量的内容,这三篇博客分别是:vim编辑器Bash与shell正则表达式与文件格式化处理。前面三篇博客的内容都是shell编程的基础内容,所以在这篇博客里应用到之前的内容这里就不会再解析了,这篇博客主要介绍以下shell脚本的编写、shell的逻辑表达式、以及测试和调试。

启动执行shell脚本的命令 | sh | 2.2
测试命令 | test | 3.1

 二、什么是shell脚本与编写、测试

 2.1什么是shell脚本

关于shell在bash与shell那篇博客中就有详细的介绍了,至于什么是shell脚本简单的来说就是用文本的方式编写的一系列的shell命令形成的程序功能,以达到想要管理处理操作系统的相关工作的脚本文件。

由于shell的通用性降低了使用C等程序语言编写的程序来管理系统的难度,但这只是相对而言,要根据具体的系统复杂的而定,在现在越来越复杂的系统管理中以及自动化管理的需求,也有比较多的又使用python来编写系统管理程序。但是shell在复杂度不高的情况下依然还是最优的选择,毕竟学习一门语言也需要时间,开发更需要人员和资金的投入,所以shell在日常很多时候依然是系统管理维护的主流工具。

下面介绍以下shell脚本可以实现什么:自动化管理、跟踪管理系统的重要工具、简单入侵检测功能、连续命令单一化、简单的数据处理、跨平台支持应用。

2.2编写shell脚本

shell脚本编写的一些基本规则和注意事项:

1.命令从上而下、从左至右分析与执行;
2.命令、选项、参数间的多个空格会被忽略;
3.空白行会被忽略;
4.如果读取到一个【Enter】符号(CR),就会尝试执行该行命令; 5.如果内容太多,可以使用【\Enter】来扩展至下一行; 6.#表示注释,该行#后面的所有数据都视为注释内容被忽略;
7.脚本文件必须具备可读可执行命令,可以使用sh命令启动shell脚本执行,也可以通过bash命令来启动shell脚本执行;

下面就来编一些第一个Holle World的shell脚本:

 vim hello.sh  #使用vim创建编辑shell脚本

 #!/bin/bash
#程序说明:
#    这个程序会在屏幕上打印“Hello World!".
#历史版本:
#2022/7/18    tx    第一版
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
echo -e "Hello World! \a \n"
exit 0

然后使用sh启动执行这个脚本:

 sh hello.sh  #启动执行脚本,终端上输出:Hello World!

在脚本中需要注意的是第一行#!/bin/bash,它是用来告知启动程序当前脚本要使用那个shell的环境,有了这一行代码启动程序就会为脚本去加载指定的相关配置文件。然后就是最后一行的exit 0,这个命令表示退出当前脚本的进程,并返回一个数据,这个数据是可以在脚本进程退出时被执行脚本程序接收的。

编写任何程序都需要有良好的规范和标准,保证程序容易被阅读理解,减少程序bug,方便后期升级更新,从上面这个最简单的示例来看实际起作用的就只有两三行代码,那为什么还要写的这么繁琐呢?

有些东西看示繁琐,当一个程序复杂到一定程度时,或者在实际工作中多人参与的项目时,那些看示无用的代码就可以避免很多问题,提高团队之间的沟通效率。比如在示例中手动设置了PATH这个环境变量,在前面的bash中介绍过在PATH会有默认的参数,但可能在不同的环境中这个参数会有一些差异,为了避免这种差异我们就可以手动的设置它来避免一些问题,手动设置PATH环境变量也相当于规定了在当前脚本下可以使用的资源的范围,并且其他人只要看到这个手动设置就会明白脚本可使用资源的范围。环境变量这些都是功能性部分的一些规范,在这个小小的示例中注释占了很大一部分,下面就来看看注释的一些编写规范:

1.脚本功能;
2.脚本版本信息;
3.脚本的作者和联系方式;
4.脚本的历史记录;

前面的PATH环境变量所体现出来的规范有两个:环境变量预先声明和设置、特殊命令使用绝对路径来执行。

2.3编写交互式的脚本

所谓交互式脚本就是需要用户输入一些内容,程序才可以继续往后执行,关于用户输入在BASH与Shell中的3.4中有详细的介绍,这里就直接来写示例:

 vim arithmetic.sh

 #!/bin/bash
 #算术:
 #       这个程序需要接收两个用户输入的数值,然后会将这两个数值分别做一系列计算,并将这些计算公式和结果输出,且生成以时间戳为文件名的文件
 #历史版本:
 #2022/7/19 tx 第一版
 PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
 filedate=$(date +%s)
 filename="arithmetic_${filedate}"
 export PATH
 #接收用户输入的第一个数值
 read -p "请输入第一个数值:" num1
 #接收用户输入的第二个数值
 read -p "请输入第二个数值:" num2
 #输出计算公式及计算结果
 echo "${num1} + ${num2} = $((${num1} + ${num2}))" | tee -a ${filename}
 echo "${num1} - ${num2} = $((${num1} - ${num2}))" | tee -a ${filename}
 echo "${num1} * ${num2} = $((${num1} * ${num2}))" | tee -a ${filename}
 echo "${num1} / ${num2} = $((${num1} / ${num2}))" | tee -a ${filename}
 echo "${num1} % ${num2} = $((${num1} % ${num2}))" | tee -a ${filename}
 echo "生成的文件名是:${filename}"
 exit 0

在上面这个示例中演示了shell的交互、数学运算等操作,关于一些细节内容比如shell中的数学运算建议自己查找参考资料。

2.4脚本的执行差异

执行shell脚本的方式有很多种,前面介绍了sh和bash的方式,其实可以通过以下方式来执行shell脚本:

使用脚本文件的绝对路径和相对路径执行shell脚本;
使用source命令执行shell脚本;
使用chmod a+x 脚本;./脚本的方式执行shell脚本,例如chmod a+x hello.sh;./hello.sh;

但是需要注意的是通过绝对路径和相对路径需要当前用户有文件的可执行权限,然后source命令执行脚本的话,如果脚本内写了exit会导致当前的bash进程关闭,如果当前的bash进程就是终端的进程就会导致整个终端被关闭。

除了source命令以外的方式执行shell脚本都会在当前bash进程的基础上,启动一个新的bash进程来执行脚本,这就是在BASH与Shell中提到的子进程,在使用进程的方式执行shell脚本时需要注意环境变量的问题,这也是在前面的示例中我为什么手动重置脚本的PATH环境变量的值的原因。而使用soure命令执行脚本是在当前进程中进行,这会出现几个问题,第一是shell脚本中的变量设置会作用到当前bash环境上,第二是shell脚本影响当前进程状态,比如前面提到的在脚本中如果包含exit命令就会导致当前进程关闭,关于进程管理在后面相关博客中详细介绍,这里只需要知道脚本执行的差异就行了。

 三、shell脚本的逻辑表达式

3.1使用test命令测试功能

在编写程序的执行过程中由于不同的环境和操作,很多东西都是存在不确定的,如果盲目的使用某些数据或者工具可能会产生很多意想不到的结果甚至错误,这时候测试数据是否符合我们的需求就非常重要,test就是解决这些问题的最好方式,它可以检测文件的类型、检测文件的权限、对比两个文件、判断数据类型、实现多重判断,下面来看一个test判断文件是否存在的示例:

 test -e /hello.sh && echo "exist" || echo "Not exist"  #打印结果:exist

关于test的使用非常简单,但功能也是比较丰富的,总体上也是有规律可循,下面就来看test详细的选项解析:

使用test测试文件类型的选项:
-e:检测文件是否存在;
-f:检测文件是否存在且为文件;
-d:检测文件是否存在且为目录;
-b:检测文件是否存在且为block device设备;
-c:检测文件是否存在且为character device设备;
-S:检测文件是否存在且为socket文件;
-p:检测文件是否存在且为FIFO(pipe)文件;
-L:检测文件是否存在且为一个链接文件;
使用test检测文件的权限的选项:
-r:检测文件是否存在且具有可读权限;
-w:检测文件是否存在且具有可写权限;
-x:检测文件是否存在且具有可执行权限;
-u:检测文件是否存在且具有SUID的属性;
-g:检测文件是否存在且具有SGID的属性;
-k:检测文件是否存在且具有Sticky bit的属性;
-s:检测文件是否存在且为非空文件;
使用test比较两个文件的选项:
-nt:判断file1是否比file2新;
-ot:判断file1是否比file2旧;
-ef:判断file1与file2是否为同一个文件,实际上是判断两个文件是否指向同一个inode;
使用test判断数据类型的选项:
-eq:判断两个数值是否相等;
-ne:判断两个数值是否不相等;
-gt:判断n1是否大于n2;
-lt:判断n1是否小于n2;
-ge:判断n1是否大于或等于n2;
-le:判断n1是否小于等于n2;
-z:判断字符串是否为0?即是否为空字符串;
-n:判断字符串是否为非0?即是否为非空字符串;
test str1==str2:判断两个字符串是否相等;
test str1!=str2:判断两个字符串是否不相等;
使用test实现多重判断:
-a:测试多个条件是否同时成立,例如test -r file -a -x file表示判断feile是否可读且可执行;
-o:测试多个条件是否有任意一个成立,例如test -r file -o -x file表示判断file是否可读或可执行;
!:取判断结果的反值,比如test ! -x file,如果file可执行则会返回false。

在shell中除了test命令的判断方式还有另一种更方便的判断符号,这个符号就是[],通常在逻辑语句中能[]一般尽可能的使用[],这样可以使程序更易读,这个是个人建议,看具体的需求而定吧。

3.2判断符号:[]

先来看一个基于[]实现的示例:

 [ -z "${HOME}" ]; echo $?  #输出结果:1,这个判断表达式是用来判断这个变量是否为空,HOMT变量不为空,输出1

 [ -z "${a}" ]; echo $?  #输出结果:0,变量a不存在,输出0

在使用[]作为判断符号的时候需要注意在括号内的两端需要有空格符,括号内的变量需要使用双引号包裹,常量使用单引号包裹。同样也可以使用多重判断,和数值比较判断,使用的关键字依然和test一样,可以简单的理解[]是test的另一种表达方式。

 [ "10" -gt "20" -o "10" -gt "5" ] && echo 1 || echo 0  #输出结果:1

3.3shell脚本的默认变量和参数

通过前面一系列的Linux相关内容介绍了很多命令,很多命令都需要传参,有编程经验都知道参数在实际程序中就是一个特殊的变量而已,既然是变量就又变了命名,并且可以通过这个变量名来获取这个变量的值,参数的值就是在启动变量的时候传入的,那参数在shell脚本中的名称又是什么,我们怎么样才可以通过参数的变量名来使用这些参数呢?

其实在shell脚本中,参数的名称很简单,基本的语法表达形式就是:

 shellname opt1 opt2 opt3

       $0         $1    $2    $3

通过上面的语法可以看到,其实一个命令实际上会被bash拆分成诺干个片段的变量值,然后$0表示程序名称(命令名称)、$1表示第一个参数、$2表示第二个参数,以此类推,也就是说我们在shell脚本中可以通过$n的方式来获取这些数据,供整个脚本使用,除了写常规的参数变量以外,还有三个特殊的参数变量:

$#:表示后面接的参数的个数,但需要注意不包括$0;
$@:表示的是【"$1" "$2" "$3"】,获取全部变量并每个变量都是单独双引号包裹;
$*:表示的是【"$1c$2c$3"】,获取所有变量并所有变量被一个双引号包裹,每个变量之间使用c分隔,c默认为空格,所以这个变量值的另一种表达是【"$1 $2 $3"】;

下面就来写一个shell脚本,这个脚本执行可以将所有参数输出:

 vim shell_paras.sh

 #!/bin/bash
 #程序说明:
 #    这个程序会将接收到的参数打印出来。
 #历史版本:
 #2022/7/20    tx    第一版
 PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
 export PATH
 echo "脚本名称:${0}"
 echo "参数个数:$#"
 echo "全部脚本参数:'$@'"
 echo "第一个参数:${1}"
 echo "第二个参数:${2}"
 exit 0

测试shell_paras.sh脚本:

 sh shell_paras.sh one 2 abc 踏雪  #下面是打印结果
 脚本名称:shell_paras.sh
 参数个数:4
 全部脚本参数:'one 2 abc 踏雪'
 第一个参数:one
 第二个参数:2

然后需要注意的是与参数有关的一个命令:shift,这个命令表示偏移量,实际上就是将获取命令的参数往后移动,前面的参数自动忽略,$n从偏移到的位置开始匹配,即$1对应的变量是shift偏移到的位置的值,并且shift可以多次执行实现偏移量累加,下面直接来看示例:

 vim shift_paras.sh

 #!/bin/bash
#程序说明:
#    这个程序用来测试参数偏移。
#历史版本:
#2022/7/20    tx    第一版
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
echo "没有偏移的全部参数:$*,当前第一个参数是:${1}"
shift    #默认偏移一个变量
echo "第一次偏移后的全部参数:$*,当前第一个参数是:${1}"
shift 3    #再累加偏移三个变量
echo "第二次偏移后的全部参数:$*,当前第一个参数是:${1}"
exit 0

测试shift_paras.sh脚本:

sh shift_paras.sh one two three four five six  #下面是打印结果
没有偏移的全部参数:one two three four five six,当前第一个参数是:one
第一次偏移后的全部参数:two three four five six,当前第一个参数是:two
第二次偏移后的全部参数:five six,当前第一个参数是:five

3.4条件判断式

既然shell脚本也是程序,那条件语句就是必不可少了,这部分内容对于有编程经验的真的太简单了,只需要了解shell脚本中的条件语法与其他编程语言中的条件语法的一些差异就可以了,它们的内在逻辑都是一摸一样的。

shell脚本中的if...then:

 if [ 条件判断式 ]; then   #表示if的起始与条件

   当条件成立时,执行这里的命令

 fi  #表示该if条件语句结束

条件判断式就是前面3.2介绍的内容,这里就不解释了。然后可以使用&&和||来表示and和or这在其他编程语言中都是一样的,在shell脚本中同样可以实现多重条件和复杂条件,直接来看相关的语法:

shell脚本中的if...else:

 if [ 条件判断式 ]; then

   当条件成立时,执行这里的命令

 else

   当条件不成立时,执行这里的命令

 fi

shell脚本中的if...elif...else

 if [ 条件判断式1 ]; then

   当条件判断式1成立时,执行这里的命令,后面其他结构中的内容不再执行

 elif [ 条件判断式2 ]; then

   当条件判断式1不成立,但条件判断式2成立时,执行这里的命令,后面的其他结构中的内容不再执行

 else

   当条件判断式1和条件判断式2都不成立时,执行力这里的命令

 fi

这里写一个最简单的if条件判断式的示例,如果要了解更详细的相关示例建议直接去阅读一些实际应用的shell脚本工具。

 vim yn.sh

 #!/bin/bash
 #程序说明:
 #    这个程序是用来测试shell脚本中的if条件语句的示例。        
 #历史版本:
 #2022/7/20    tx    第一版
 PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
 export PATH
 read -p "请输入(Y/N):" yn
 if [ "${yn}" == "Y" ] || [ "${yn}" == "y" ]; then
     echo "OK,继续往下执行"
 elif [ "${yn}" == "N" ] || [ "${yn}" == "n" ]; then
     echo "Oh,马上退出执行"
 else
     echo "不能理解您的操作"
 fi
 exit 0

关于if相关的示例测试就不展示了,实在是太简单了,下面来看shell脚本中的case...esac条件语句,实际上case就相当于其他语言中的switch,用于条件的既定的多个变量内容的判断逻辑,下面来看语法:

shell脚本中的case...esac:

 case $变量名称in

  "第一个变量内容")

    当变量值等于当前变量内容时执行的程序

    ;;

  "第二个变量内容")

    当变量值等于当前变量内容时执行的程序

    ;;

  *)  #*实际上就是通配符的意思

    当变量值为其他变量内容时执行的程序

    ;;

 esac

基于case...esac的条件语句的示例:

 #!/bin/bash
 #程序说明:
 #    这个程序是用来测试shell脚本中的case条件语句的示例。        
 #历史版本:
 #2022/7/20    tx    第一版
 PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
 export PATH
 case ${1} in
  "one")
     echo "你选择的是第一种程序模式"
     ;;
  "two")
     echo "你选择的是第二中程序模式"
     ;;
  *)
     echo "你选择的程序模式找不到,请确认你输入的参数"
     ;;
 esac
 exit 0

3.5shell脚本中的function

函数也不需要太多的介绍,它主要就是用来实现一段程序代码的复用,shell脚本一样是有的,而且相对其他变成语言的函数要简练的多,这里直接上语法,然后展示一个示例。

 function fname(){

   程序片段

 }

在shell中的函数一样也可以使用参数,跟命令使用参数的形式一样,在使用函数名调用函数时后面接收参数:fname par1 par2 ...,然后再函数内容使用$n来使用参数,下面的示例我就直接使用一个带参的函数来演示:

 #!/bin/bash
 #程序说明:
 #    这个程序是用来测试shell脚本中的case条件语句的示例。        
 #历史版本:
 #2022/7/20    tx    第一版
 PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
 export PATH
 function str(){
     echo "输入的参数是:'${1}' "
 }
 case ${1} in
  "one")
     str "$1,正在启动第一种程序模式"
     ;;
  "two")
     str "$1,正在启动第二种程序模式"
     ;;
  *)
     echo "输入的参数有误,没有找到对应的程序模式"
     ;;
 esac
 exit 0

函数示例的测试: sh fun.sh one 、 sh fun.sh two 、 sh fun.sh abc 。 

3.6shell脚本中的循环语句

在shell中循环有语句有三种,分别是:while、until、for,下面逐个来了解它们的语法:

shell脚本中的不定循环while do done语法:

 while [ 条件判断式 ]

 do

   当条件成立时进入循环

 done

shell脚本中的不定循环until do done语法:

 until [ 条件判断式 ]

 do

  当条件不成立时进入循环

 done

这两个不定循环只是判断逻辑上相反,基本实现思路都是一样的,这里就写一个while的示例:

 #!/bin/bash
 #程序说明:
 #    这个程序是用来测试shell脚本中的while循环语句的示例。        
 #历史版本:
 #2022/7/20    tx    第一版
 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 "请入yes/YES停止此程序:" yn
 done
 echo "ok!程序正常关闭"
 exit 0

执行测试就不写了,需要说明的是不定循环的条件判断式是在执行循环体内部程序一次以后再判断的,不像if那一类的条件语句是先判断在执行内部程序。

shell脚本的固定循环for...do...done之固定参数循环语法:

 for var in con1 con2 con3 ...

do

  程序段  #$var会逐个取值conN作为程序执行的数据,循环次数就是conN的次数

done

shell脚本的固定循环for...do..done之数值计算判断式语法:

 for (( 初始值; 限制值; 赋值运算 ))

 do

  程序段

 done

关于for循环就不写完整的脚本,实在觉得没有必要哈,这里就写一下循环的部分吧:

 #固定参数

 for animal in dog cat elephant  #in后面的数据也可以是用空格符间隔的数据,比如使用cut -d ':' -f1 /etc/passwd获取的数据

 do

  echo "There are ${animal}"

 done

 #计算判断式

 nu=10

 s=0

 for (( i=1; i<${nu}; i=i+1 ))

 do

   s=(( ${s}+${i} ))

 done

 四、shell脚本的跟踪与调试

关于shell脚本的调试也是非常的简单,在3.*的示例中都是直接执行脚本来测试,这种做法在实际开发中显然不是很合适,sh命令给出了非常人性化的选项功能提供调试使用,下面就来看看sh的语法和选项:

 sh [-nvx] scripts.sh

sh命令的选项解析:

-n:不执行脚本,仅检查语法问题;
-v:执行脚本前,将脚本的内容输出到屏幕上;
-x:将用到的脚本内容显示到屏幕上;

关于shell的脚本调试也非常简单,这里就不演示了,后期的博客中会经常用到shell脚本,慢慢熟悉了就OK了,如果想提高shell编程能力,可以自行参考其他资料。

 

posted @ 2022-07-20 22:08  他乡踏雪  阅读(628)  评论(0编辑  收藏  举报