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 "执行文件时带了参数,正确"
当某个条件成立时,需要执行某些语句 。。。这种情况的时候,使用这个用法就比较简单了。注意:执行的语句为多条时,使用" && "来进行连接,且必须为一行。