摘要
本文介绍shell编程的语法规则。
本文基于CentOS8(x86_64)
文件头
1 2 #!/bin/sh :指定执行脚步的shell路径
如果没有为脚本设置文件头指定执行脚本的shell路径,则默认使用运行脚本的用户的shell
注释语法
系统变量
变量名
描述
$SHELL
默认Shell
$HOME
当前用户家目录
$IFS
内部字段分隔符
$LANG
默认语言
$PATH
默认可执行程序路径
$PWD
当前目录
$UID
当前用户ID
$USER
当前用户
$HISTSIZE
历史命令大小,可通过HISTTIMEFORMAT变量设置命令执行时间
$RANDOM
随机生成一个0至32767的整数
$HOSTNAME
主机名
特殊变量: $0、$?、$*、$@、$#、$$、$!
$0
: 当前脚本的文件名(间接运行时还包括绝对路径)。
$n
: 传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是 $1 。
$#
: 传递给脚本或函数的参数个数。
$*
: 传递给脚本或函数的所有参数。
$@
: 传递给脚本或函数的所有参数。被双引号 "$@"
包含时,与 "$*"
不同,下面将会讲到。
$?
: 上个命令的退出状态(0:success 非0:error),或函数的返回值。这个非常有用,常用语法是:if [ $? -eq 0 ]
。
$$
: 当前 Shell 进程 ID。对于 Shell 脚本,就是这些脚本所在的进程 ID。
$_
: 上一个命令的最后一个参数
$!
: 后台运行的最后一个进程的 ID 号
小贴士
$*
和 $@
都表示传递给函数或脚本的所有参数,不被双引号 ""
包含时,都以独立个体"$1" "$2" … "$n"
的形式输出所有参数。
但是当它们被双引号 ""
包含时,"$*"
会将所有的参数作为一个整体输出,"$@"
依旧会以独立个体"$1" "$2" … "$n"
的形式输出所有参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 for i in $*do echo $i done 1 2 3 4 for i in "$*" do echo $i done 1 2 3 4 for i in $@ do echo $i done 1 2 3 4 for i in "$@ " do echo $i done 1 2 3 4
获取参数数量:
自定义变量
字符串
1 2 3 4 5 6 7 8 9 10 11 12 s1="content" s2=$s1 s3="${s1} _test" s4=`date +%F' ' %T` now=`date +%Y%m%d_%H%M%S`
小贴士
$
与${ }
都是用来引用变量的,${ }
通常用于划定变量名的边界
当执行 echo "$aa"
的时候系统会打印变量$aa
的值,当执行echo "${a}a"
时打印的是${a}
和字母a,如果不需要为变量名划分边界的话,$a
和${a}
是完全相等的。
除此之外,${ }
还有一个重要的功能,就是文本处理:
1 2 str='hello' echo ${#str}
2.字符串切片${a:b:c}
: 将字符串变量a
从第b
个位置开始向后截取c
个字符,b是指下标,下标从0开始,c
可以不指定,表示截取到字符串末尾
1 2 3 4 5 6 7 8 a='hello world!' echo ${a:0:5} echo ${a:6} echo ${a:(-1)} echo ${a:(-6):5}
3.替换字符串${a/b/c}
: 将变量a
中的b
全部替换为c
,开头一个正斜杠为只匹配第一个字符串,两个正斜杠为匹配所有字符。
1 2 3 4 5 6 7 a='hello hello world' echo "${a/hello/hi} " echo "${a//hello/hi} " str=123abc echo ${str//[^0-9]/} echo ${str//[0-9]/}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ${parameter#word} ${parameter##word} ${parameter%word} ${parameter%%word} % 去掉右边,%最短匹配模式,%%最长匹配模式。 URL="http://www.baidu.com/baike/user.html" echo ${URL#*//} echo ${URL##*/} echo ${URL#*/} echo ${URL%%/*} echo ${URL%/*}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 ${VAR:-string} ${VAR:+string} ${VAR:=string} ${VAR:?string} VAR= echo ${VAR:-'hello world!'} echo $VAR VAR="hello" echo ${VAR:+'hello world!'} echo $VAR VAR= echo ${VAR:=hello} echo $VAR VAR= echo ${VAR:?value is null}
数字
整数运算
支持let
、$(( ))
、$[ ]
和expr
四种方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 a1=10 a2=$(($a1 + 5 )) or a2=$((a1 + 5 )) a3=$[$a1 + 5] or a3=$[a1 + 5] a4=`expr $a1 - 5` or a4=$(expr $a1 - 5) let a5=a1-5a2=$((a1 * 5 )) a3=$[a1 * 5] a4=`expr $a1 \* 5` let a5=a1*5a2=$((a1 / 5 )) a3=$[a1 / 5] a4=`expr $a1 / 5` let a5=a1/5
let
、$(( ))
或$[ ]
中的变量都可以不加$
前缀
在进行整数运算时,$(( ))
和$[ ]
的作用是等价的
建议使用let
、$(( ))
或$[ ]
的形式进行运算,其支持正常的运算逻辑,expr稍显笨拙,比如在进行带括号的运算时
1 2 3 4 5 6 7 8 9 a2=$(((a1 - 5 ) * 5 )) a3=$[(a1 - 5) * 5] a4_1=`expr $a1 - 5` a4_2=`expr ${a4_1} \* 5` let a5=(a1-5)*5
小贴士
$()
和 `` 的作用一致,都是用来做命令替换用,一般用于将命令返回的结果传递给变量
浮点数计算
1 2 3 4 5 6 7 8 9 10 11 12 13 x=10 y=3.211 a1=`echo "$x * 2 / $y " | bc` a2=`echo "scale=2; $x * 2 / $y " | bc` a2=`echo "$x $y " | awk '{printf "%.2f\n",$1*2/$2}' ` a3=`echo "scale=2; 2 / 3" | bc` a3=`echo 2 3 | awk '{printf "%.2f\n",$1/$2}' `
bc
是一个用于执行任意精度的数学计算的命令行计算器,支持基本的数学运算、逻辑运算、变量、函数等。
awk
的计算方式比 bc
更好一些,支持精度更为准确
bc
和awk
的计算方式同样支持整数
2进制与10进制或16进制之间进行相互转换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 echo "obase=2; 255" | bc echo "obase=16; 255" | bc echo "ibase=2; 11111111" | bc echo $((2#11111111 )) echo "ibase=16; FF" | bc echo $((16 #FF)) echo "obase=16; ibase=2; 11111111" | bc echo "obase=2; ibase=16; FF" | bc
数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 array=(Zero One Two Three) array=("One" "Two Three" "Four" ) array[0]="One" array[1]="Two Three" array[0]="Four" i=0 while [ $i -lt ${#array[@]} ];do echo ${array[$i]} let i=i+1 done for ((i=0 ;i<${#array[@]} ;i++))do echo ${array[i]} done
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 declare -A arrayarray=(["apple" ]="red" ["banana" ]="yellow" ["grape" ]="purple" ) array["apple" ]="red" array["banana" ]="yellow" array["grape" ]="purple" for key in "${!array[@]} " ; do echo "Fruit: $key , Color: ${array[$key]} " done
1 2 3 4 5 6 7 8 echo ${array[@]} echo ${#array[@]} echo ${!array[@]}
从键盘设置变量:read
1 2 3 4 5 6 read [选项] [变量名] 选项: -p “提示信息”:在等待read 输入时,输出提示信息 -t “秒数”: read 命令会一致等待用户输入,使用此选项可以指定等待时间 -n “字符数”: read 命令只接受指定的字符数,就会执行 -s: 隐藏输入的数据,适用于机密信息的输入
1 2 3 4 5 6 7 8 read a echo $a read -t 30 -p "Please input your username:" usernameread -s -t 30 -p "Please input your passsword:" passwordread a b c echo "${a} _${b} _${c} "
test
命令
测试文件或者文件夹是否存在
1 2 3 4 5 6 7 8 -d dir :是否为目录,是目录为真 -f file :是否为常规文件,是文件为真 -x file/dir :是否可执行,可执行为真 -r file/dir :是否可读,可读为真 -w file/dir :是否可写,可写为真 -a file/dir :文件或目录是否存在,存在为真 -e file/dir :文件或目录是否存在,存在为真 -s file :文件大小是否非0,非0为真
1 2 3 4 5 $ test -f test.sh $ echo $? $ test -d dir $ echo $?
字符串比较
1 2 3 4 5 6 参数 说明 -z 当str为空时返回真 -n 当str为非空时返回真 = 两个字符串相等时返回真 == 两个字符串相等时返回真,同= != 两个字符串不相等时返回真
1 2 3 4 5 6 7 8 9 10 11 $ test -z '' $ echo $? $ test -n 'hello' $ echo $? $ test 'hello' == 'world' $ echo $? $ test 'hello' != 'world' $ echo $?
数值比较
1 2 3 4 5 6 7 参数 说明 -eq 等于时返回真 == -ne 不等于时返回真 != -lt 小于时返回真 < -le 小于等于时返回真 <= -gt 大于时返回真 > -ge 大于等于时返回真 >=
1 2 $ test 1 -lt 2 $ echo $?
逻辑运算
1 2 3 4 参数 说明 -a 逻辑与,二者都为真则为真 -o 逻辑或,二者任意一个为真则为真 ! 逻辑非
1 2 3 4 5 $ test ! -e test.sh $ echo $? $ test -f test.sh -a -d dir $ echo $?
shell中的用法
1 2 3 4 5 6 i=$1 if test $i -lt 5;then echo "$i < 5" else echo "$i >= 5" fi
条件判断
在使用if
或while
等语句时,需要进行条件判断,上面我们已经见到一个while
的例子,其使用[ ]
来定义条件判断
实际上除了[ ]
以外,我们还可以使用test
、(( ))
和[[ ]]
来进行条件判断,那么他们之间有什么区别吗?
[ ]
是test
命令的另一种形式,例如 test a == b
等同于 [ a == b ]
,注意 [
后和 ]
前都需要有空格,并且==
两边也都要有空格
1 2 3 test 'hello' != 'world' ==> [ 'hello' != 'world' ]test ! -e test.sh ==> [ ! -e test.sh ]test $i -lt 5 ==> [ $i -lt 5 ]
[[ ]]
是[ ]
的增强版,其在如下几个方面进行了增强:
1.在[[ ]]
中使用>
、<
进行数值比较时不需要转义,但是不支持>=
、<=
2.支持&&
和||
1 2 3 4 [[ $a > 3 && $a != 10 ]] [ $a > 3 -a $a != 10 ] [ $a > 3 ] && [ $a != 10 ]
3.[[ ]]
在比较字符串时支持正则匹配和通配符匹配
1 2 3 4 5 6 7 8 9 a="linux" [[ $a == l?nu? ]] [[ $a != li* ]] a="linux" [[ $a =~ ^li ]] [[ $a =~ ^li[abn]ux ]]
(( ))
用于条件判断时只能进行数值比较,运算符不需要转义,而且不支持-lt
、-gt
等等
小贴士
(( ))
除了用于条件判断外,还有三种用法:
1.与$
结合使用进行数学运算 : $(( ))
,如:a=((5 + 3)),a= ((2#1010)) 二进制转10进制,等等
2.在for
循环命令中控制循环 : for((i=1;i<10;i++))
3.改变变量的值,且变量前不需要$
: ((i++))
,没有任何输出,只是改变i的值
推荐在进行条件判断时使用[[ ]]
,运算符不需要转义,而且支持正则
流程控制语句
if
语句
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 if [[ -d $path ]];then echo "dir" fi if [[ -d $path ]]then echo "dir" fi if [[ -d $path ]];then echo "dir" else echo "not dir" fi if [[ -d $path ]];then echo "dir" elif [[ -f $path ]];then echo "file" else echo "not match" fi
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 paramSize=${#@} if [[ $paramSize == 0 || $paramSize > 1 ]];then echo "ERROR:需要一个参数!!!" exit 1 fi if [ "$1 " -gt 0 ] 2>/dev/null ;then echo "$1 id number" else echo "ERROR: $1 is not number!" exit 1 fi expr $1 "+" 0 &> /dev/nullif [[ $? == 0 || $1 == 0 ]];then echo "$1 is number" else echo "$1 not number" fi if [[ -a $bookId .tar.gz ]];then rm -rf $bookId .tar.gz fi if [[ -n $str ]];then echo "$str is not null" else echo "$str is null" fi
case
语句
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 case "$1 " in start) echo "start" ;; reload) echo "reload" ;; stop) echo "stop" ;; status) echo "status" ;; *) echo "$0 : Usage: {start|status|stop|reload}" exit 1 ;; esac
select
语句
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 select var in "Linux" "UNIX" "Windows" "Other" do echo "You have selected $var " break done $ sh select.sh 1) Linux 2) UNIX 3) Windows 4) Other You have selected UNIX
while
循环语句
1 2 3 4 5 6 a=0 while [[ $a < 5 ]]do echo $a ((a++)) done
until
循环语句
1 2 3 4 5 6 7 a=5 until [[ $a == 0 ]]do echo $a ((a--)) done
for
循环语句
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 for ((i=0 ;i<=10 ;i++))do echo $i done for i in {1..9} do echo "this is $i " done for i in {1..9..2} do echo "this is $i " done for i in `seq 1 9` do echo "this is $i " done for i in `seq 1 2 9` do echo "this is $i " done for day in Sun Mon Tue Wed Thu Fri Satdo echo "The day is : $day " done for line in `cat file`do echo $line done for p in $@ do echo $p done
其它语句
1 2 3 4 5 6 7 8 9 10 11 12 for ((i=0 ;i<=10 ;i++))do if [[ $i == 0 ]];then continue fi echo $i if [[ $i == 8 ]];then break fi done
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 if (( $# <= 0 ));then echo "Not enough parameters" exit 0 fi sum =0while [[ $# >= 0 ]]do sum =$((sum + $1 )) shift done echo $sum $ sh shift.sh 1 2 3 4 5
shell函数的定义与使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 function name () { statements [return value] } function : Shell 中的关键字,专门用来定义函数name : 函数名 statements : 函数要执行的代码,也就是一组语句; return value : 函数的返回值,其中 return 是 Shell 关键字,专门用在函数中返回一个值,这一部分可以写也可以不写。由{ }包围的部分称为函数体,调用一个函数,实际上就是执行函数体中的代码。 name () { statements [return value] } function name { statements [return value] }
1 2 3 4 5 name name param1 param2 param3
和其它编程语言不同的是,Shell 函数在定义时不能指明参数,但是在调用时却可以传递参数,在方法体内引用时通过$1,$2,……来使用传递过来的参数
1 2 3 4 5 6 function start (){ echo "start" } start
1 2 3 4 5 6 7 8 function sum (){ return $(( $1 + $2 )) } sum 1 2echo $?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function getsum (){ local sum =0 for n in $@ do ((sum+=n)) done return $sum } getsum 10 20 55 15 echo $?
运行shell
sh test.sh
: 运行shell脚本
sh -x test.sh
: 执行脚本,并显示全部过程
sh -n test.sh
: 不执行脚本,只检查语法错误
也可以为shell脚本授予执行权限,然后通过.
关键字执行,比如. ~/test.sh
,或者直接通过脚本路径运行~/test.sh
(注意要设置文件头指定shell)
小贴士
如果在windows环境下编写的脚步,上传到linux后需要先执行dos2unix
进行编码转换,否则不能正确执行
如果dos2unix
命令不存在,可以通过yum
进行安装