shell脚本从入门到精通,防坑必备

it2022-05-05  165

Linux系统.sh脚本文件的语法编辑规范

在Linux系统下,.sh文件是可执行的文件,其用法和Windows系统下的.bat文件类似,这篇文章可以帮助你阅读简单的.sh文件,你也可以动手试验其中的一些命令,编写一个属于自己的脚本文件。在观看时也可以找一个.sh文件进行对照。这样可以加深自己的了解,顺便读懂此文件。

注:如果自己有些Linux命令基础和编程基础再来阅读本文内容最好,但也要多练习,里面有多细节规范都是值得注意的

0、执行此类脚本文件

此类文件有个坑,可能有的朋友双击或者在命令行使用./文件名执行文件不能运行,报错权限不够。首先声明双击文件不能运行脚本!!!

正确姿势是:

①右键属性,查看权限。

②选中Allow executing files as program。

③右键打开终端。

④使用./文件名执行文件。

收工!

接下来就正式开始了解脚本文件的语法规范了,大家上车了!!!

1、开头

.sh文件的开头必须声明(且放在文件的第一行):

#!/bin/sh

#! 是特殊的表示符,后面根的是此解释此脚本的shell的路径,是.sh文件的规范

/bin/sh 表示此脚本使用/bin/sh来解释执行,当然也有其他的解释路径。比如#!/usr/bin/env sh

当然,我也见过有的.sh文件没有这个开头声明(杠精劝退),可以是文件比较老,当时的程序员编写时没有什么规范,如果你是菜鸟,也请不要乱改,写自己脚本的时候注意即可

2、注释

注释,就是在程序执行的时候告诉编译器这一块代码不必执行。一个程序,注释在其中占据了非常重要的作用。既可以让自己以后阅读起来方面,也可以让别人看着方便。下文的代码中,也会经常使用注释对代码进行逐行进行解释,方便介绍。

.sh文件使用#注释,其这一行#后面的内容编译器都不会执行。

3、输入输出

一个程序,输入输出是非常必要的,可以显示出使用者与程序之间的交互,使用者也可以通过程序的输出情况来,分析出程序的运行情况,这也是对程序编辑者非常有帮助的事情。

其中输出(默认为终端)的语法是:

echo "这里是你想输出的内容" #注意echo后面要加空格 # 其后面可以加 ">> 文件路径",意为输出到文件中

其中可以使用 -e 表示默认输出到屏幕上,由于默认情况下就算输出到屏幕上,因此可以不加。

获取输入的语法是:

read input #read表示读入输入,而input表示将输入保存至这个变量之中

其中可以使用 -p 表示获取屏幕上的标准输入,由于默认情况下也是在控制台输入,因此也可以不加。

而在常见的情况下的输入都是有提示语,而使用上面两句的组合就显得比较复杂。因此shell脚本有一个简便的方式来进行提示输入:

read -p "此处时提示语" input #read表示读入输入,而input表示将输入保存至这个变量之中

4、程序强制退出

在程序执行时,有时缺少某些必要条件或者在某些情况下,后面的程序不能正常运行(或者不需要执行),此时可以选择直接推出程序,其命令如下

exit 0

其中0表示告诉表编译器程序是正常退出的。当然,也有非0的情况,此时表示非正常退出。。不知道小伙伴注意到没有,一般我们自己编辑让程序退出的时候,肯定就是要正常退出了,因此exit 0在自己使用的情况比较多。

5、执行文件所附带的参数获取

在执行文件的时候,有时候需要在执行语句后面直接跟上脚本文件所需要调用的参数,以此作为脚本调用命令的格式。当我们在获取这些参数的时候,可以使用$1、$2等等来进行获取值。当然参数多可以有$3、$4,一样的道理。比如下方的例子(请着重注意$1、$2的用法,其他的内容,在下文也会叙述)

n1=$1 #将参数1保存至变量n1中 n2=$2 #将参数1保存至变量n2中 if [ -z $n1 ]; then #若没有输入第一个参数,则提示执行脚本的命令有误,提示出正确的格式 echo "参数有误,正确表达式为:./test1.sh 参数1 参数2" exit 0 #参数没有输入,后面需要使用这个参数值的时候,程序就可以报错,为了防止报错,我们直接退出程序 fi if [ -z $n2 ]; then #若没有输入第二个参数,则提示执行脚本的命令有误,提示出正确的格式 echo "参数有误,正确表达式为:./test1.sh 参数1 参数2" exit 0 fi

当然,也有一些比较其他的用法,大家可以灵活使用,下文也些例子,帮助大家理解。

$0 # 程序本身 $# # 执行程序所附带的参数个数 $@ # 执行程序所附带的所有参数

6、变量

(1)变量的命名规则

和大多数的编程语言一样,.sh脚本文件的命名规则也是:变量名必须是以字母或下划线字符“_”开头,后面跟字母、数字或下划线字符。其也是区分大小写的,即var和VAR是不同的变量。

(2)变量的申明与使用

由于.sh语言变量时弱类型,因此申明变量时必须要给变量赋值。例如上文中的n1、n2。

变量的使用时,可以通过$变量名来进行获取其值。与其他语言不同的是,$变量名不仅可以单独使用,也可以使用在双引号包裹的字符串中,都是可以正常使用的。但是有几种问题需要注意:

①字符串中使用时,变量名紧跟后面的单词。例如

var="apple" # ※※※※请不要在等号两边多加空格,否则会导致程序报错 echo "There are a lot of $vars here."

此时编译器查找变量时,会找vars,无法找到,便会报错,因此可以使用${}来指定变量名,从而使变量正常使用

var="apple" # ※※※※请不要在等号两边多加空格,否则会导致程序报错 echo "There are a lot of ${var}s here."

②当字符串中需要打印$这个字符的时候,但是由于编译器会找到$后面的变量获取值,因此可能无法得出想要结果。因此可以通过转义字符\来将其进行转义。例如

echo "A total price of \$30"

其中对于变量还有比较深入的用法,其中有兴趣可以参考大佬的文章对于变量总结的文章传送门

7、Linux命令

./sh最主要的功能就是帮我我们执行一些Linux命令。因此,这一部分也是最为关键的一部分。Linux命令众多,这里也不做过多的赘述,大家可以自行百度各个命令及其用法。这里简单叙述一下比较常用的命令,可以帮助你阅读一些简单的脚本文件:

cd :跳转当前工作路径 ls :显示当前目录下的文件 cp :复制文件(copy) mv :移动文件(move) rm :删除文件(remove) mkdir :创建文件夹 grep :管道符,查找文件里符合条件的字符串 touch :创建文件,若文件存在,则修改其时间属性 chmod :查看或修改文件权限 git :代码管理工具 ln :创建链接文件(同window的快捷方式) ps :显示所有进程状态 su :切换用户 tar :打包文件(打包而已,并没有进行压缩)

8、流程控制语句

(1)if…else判断

其语法为

if [ 判断条件1 ] ; then 代码块1 elif [ 判断条件2 ] ; then 代码块2 else 代码块3 fi # if判断结束的标志

对于有过编程基础的小伙伴应该对if分支都有所了解,在各个代码里面,if...else判断都是大同小异。对于if...else...if的使用以及其嵌套使用都比较简单,下面就通过简单的例子来展示其用法。

read -p "请输入你的成绩:" score if [ $score \< 60 ] ; then # 小于60为不及格,由于小于号是特殊字符,因此需要进行转义 echo "你的成绩不合格" elif [ $score \< 70 ] ; then # 由于小于60已经进入上面的分支,因此此判断为60到70的区间 echo "你的成绩及格了" elif [ $score \< 90 ] ; then echo "你的成绩良好" elif [ $score \<= 100 ] ; then echo "你太优秀了" else echo "你的分数太高了,是不是错了" fi
(2)case…esac判断

其语法为

case $变量名 in # 若变量的值为下列的某个一个值的时候,执行其对应的代码块 变量可能的值1) 所执行的程序块1 ;; 变量可能的值2) 所执行的程序块2 ;; 变量可能的值3) 所执行的程序块3 ;; *) 其他默认所执行的程序块 ;; esac # case结束的标志

对于有过编程基础的小伙伴一下子能够看懂,case...esac的用法和其他编程语言中的switch...case其用法是相同的。在使用情况也和其类似,当变量的值只有几种情况时,使用其效率比if...else更快,但是对于变量的值处在某个范围内的话,还是需要使用if...else更方便一些,下面就通过简单的例子来展示其用法。

read -p "请输入今天第一个来的同学名字:" name case $name in # 若变量的值为下列的某个一个值的时候,执行其对应的代码块 "xiaoming") echo "今天又是小明第一个来,大家鼓励" ;; "xiaoli") echo "今天时小丽第一个来,大家加油" ;; *) echo "今天${name}先来" ;; esac
(3)while…do…done循环

其语法为

while [ 判断条件 ] # 循环执行的条件,请注意格式,中括号左右都要有空格 do # 循环体执行的开始 循环体代码块 # 循环执行的循环体 done # 循环体执行的结束

对于有过编程基础的小伙伴一下子能够看懂,while...do...done的用法和其他编程语言中的while循环的用法是相同的。都是先进行一次判断,再执行循环体的代码块,其比较适用于循环次数不固定,循环次数时根据循环体的不同而改变。下面就通过简单的例子来展示其用法。

# 当输入n的时候退出程序,输入其他则接着循环 while [ "$input" != "n" -a "$input" != "N" ] # 中括号和判断符两侧都要有空格 do read -p "你确定要继续吗?(y/n)" input done
(4)until…do…done循环

其语法为

until [ 判断条件 ] # 循环执行的条件,请注意格式,中括号左右都要有空格 do # 循环体执行的开始 循环体代码块 # 循环执行的循环体 done # 循环体执行的结束

乍一看,until...do...done循环和while...do...done循环差不多一样,其实却是是这样的。两个唯一之处就是until...do...done循环是直到某个条件满足时才退出,当满足判断条件时就退出;而while...do...done循环是当条件成立时就执行循环体,当不满足判断条件了就退出了。因此两者正是相反的。下面将while...do...done循环的例子替换为util...do...done循环,大家来进行比较分析。

# 当输入n的时候退出程序,输入其他则接着循环 until [ "$input" == "n" -o "$input" == "N" ] # 中括号和判断符两侧都要有空格 do read -p "你确定要继续吗?(y/n)" input done

当执行上面的例子的时候,可能有的小伙伴会报错 [: unexpected operator ,这是正常情况(我一开始也这样报错了),这是由于 Linux默认sh链接到dash的,和bash不兼容(两个都是相似的脚本。具体两者的区别可以自行百度)

对于个问题,只需要在终端输入

sudo dpkg-reconfigure dash

在弹出框中选择No,就可以了。再次运行程序就正常了

(5)for…do…done循环

对于for...do...done循环,有两种格式,和其他大多数语言类似,其也有 简单的for循环 格式和 for...each循环 格式(只是比较类似,大家可以这么去记),以下就以这两个名字来做简单的叙述:

① for...each循环 格式的语法为

for 变量名 in 变量值1 变量值2 变量值3 # 循环的变量名依次为"in"后面的变量值时,执行下文的循环体 do # 循环体执行的开始 循环体代码块 # 循环执行的循环体,有几个变量值,就循环几次 done # 循环体执行的结束

写过其他语言的for...each循环的,可能一下子能够想要in后面加数组的效果会更好。因此我们可以配合前文的 $@ 来进行使用,从而依次读取出每调用函数的每一个参数。下面来举个例子来展示其用法。

index=1 for var in $@ do echo "运行程序所带的第$index个参数为$var" let index=($index+1) # let表示此运算是整数运算,不写的话,第一次之后index="1+1" # ((index=$index+1)) # 也可以使用双括号来做整数运算,其效果同上 done

上面这个程序调用之后的运行结果为: ②简单的for循环格式的语法为

for ((初始值;限制条件;步长)) # 循环的变量名依次为"in"后面的变量值时,执行下文的循环体 do # 循环体执行的开始 循环体代码块 # 循环执行的循环体,有几个变量值,就循环几次 done # 循环体执行的结束

其用法也和程序中的for循环类似了,只不过需要注意的是:for后面需要两个括号,这也是写这个循环时最容易范的错。下面可以通过这个例子来做一个简单的了解,也可以通过这个来查看for循环中括号内三个条件的运行规则,做进一步的加深了解。

for ((index=1;index<10;index++)) do echo "当前index的值为$index" done

上面这个程序调用之后的运行结果为: 以上就对5种条件判断叙述完了,对于有其他编程语言基础的,可以通过其他的语言中对应的语法来进行记忆。

9、条件判断

对于条件判断,上面流程控制语句中大家也见过了,其大部分的使用位置便是在这些流程控制中。而在上文中我大都只是使用了比较简单的大于小于来进行判断,现在就正式进入条件判断,梳理那些比较复杂的条件判断,以及比较常用的用法。

(1)简单比较判断
大于:" > ""-gt" #greater than 大于等于:" >= "" -ge " #greater equal 小于:" \< ""-lt" #less than 由于小于号是特殊字符,因此需要进行转义 大于等于:" \<= "" -le " #less equal 等于:" == "" = ""-eq" #equal 不等于:" != ""-ne" #no equal 与:" && "" -a " # 左右两边都为真才为真 或:" || "" -o " # 左右两边都为假才为假 非:" ! " # 非真即假、非假即真

这几个是条件判断中最常见的判断符了,上文中也有简单的使用,对于有编程的小伙伴应也非常熟了,这里就不过多介绍了。

(2)字符串的判断
-z $变量名 # (zero)判断字符串是否为空,字符串为空时返回true -n $变量名 # (与上面相反)判断字符串是否不为空(全为空格时也不为空),字符串不为空时返回true
(3)文件的类型判断

在.sh脚本文件里面,更多的是对文件的操作。因此接下来主要针对文件进行判断

-e 文件路径字符串 # 判断文件是否存在 -d 文件路径 # 判断文件是否存在,且文件类型是目录(dir) -f 文件路径 # 判断文件是否存在,且文件类型是文件(file) -b 文件路径 # 判断文件是否存在,且文件类型是块设备文件(block) -c 文件路径 # 判断文件是否存在,且文件类型是字符设备文件(character) -S 文件路径 # 判断文件是否存在,且文件类型是socket文件(注意是大写的S) -p 文件路径 # 判断文件是否存在,且文件类型是FIFO文件 -L 文件路径 # 判断文件是否存在,且文件类型是连接文件(注意是大写的L)(link)
(4)文件权限的判断
-s 文件路径 # 判断文件是否存在,且是一个非空白文件(注意是小写的S) -r 文件路径 # 判断文件是否存在,且具有读权限 -w 文件路径 # 判断文件是否存在,且具有写权限 -x 文件路径 # 判断文件是否存在,且具有可执行权限 -u 文件路径 # 判断文件是否存在,且具有"SUID"属性 -g 文件路径 # 判断文件是否存在,且具有"SGID"属性 -k 文件路径 # 判断文件是否存在,且具有"sticky bit"属性
(5)两个文件的比较
文件1 -nt 文件2 # 文件1是否比文件2新(newer than) 文件1 -ot 文件2 # 文件1是否比文件2旧(older than) 文件1 -ef 文件2 # 文件1和文件2是否是同一个文件(equal file)

10、函数

其函数编写的语法为

# 函数的声明 function 函数名(){ 函数体 # 可以使用"$1","$2"来获取调用函数的参数 return; # 最后的"return"可有可无,需要的使用即可 } # 调用 函数名 参数1 参数2 # 函数名后面加参数即可

先说两点:

①在函数中的参数使用也和程序的参数使用是一样的,,也可以使用$0、$#等,其也是意思也就成为了自己函数的参数数量,其实可以将程序本身理解为一个大的函数。

②$1、$2、$0、$#等的这些用法的使用是独立的。在函数A和函数B中的两者使用是相互独立的,函数中和程序里的使用也是相互独立的,都是互不影响的。

下面举一个简单的例子来说明:

function function_add(){ echo "$1+$2=$(($1+$2))" # 双括号表示计算,再加 $ 可以将计算的值显示出来。 } function_add 3 4

11、其他

(1)使用两个"`"包裹Linux命令可以将命令的返回值赋值给变量,例如
var=`ls` # 键盘tab键上方的键,在英文状态下即可输入 ` for v in $var # 遍历变量的值 do echo "当前文件夹下的文件有:$v" # 即可以打印每一个文件的名字 done
(2)单个if时候的简写

当程序只有一个if判断分支的时候,可以使用简便的方式进行书写,例如

[ "$#" -lt 1 ] && echo "执行文件时请添加参数" && exit 0 # 必须写为一行 echo "执行文件时带了参数,正确"

当某个条件成立时,需要执行某些语句 。。。这种情况的时候,使用这个用法就比较简单了。注意:执行的语句为多条时,使用" && "来进行连接,且必须为一行。


最新回复(0)