shell编程
序言
shell脚本语言是面向字符串的编程语言。我们需要思考两个问题?shell中的字符串从哪里来,到哪里去?
从哪里来?字符串可以通过shell展开来获得。
到哪里去?所有的字符串最终都被当做命令去执行。
shell中每一行的第一个单词被当做命令,第2个单词被当做参数……
根据以上,我们可以分析shell脚本语言的一些特性。
我们需要分析shell脚本语言的语法与普通编程语言的不同之处,以及它为什么要这么设计?弄清楚这个问题,能方便我们掌握shell脚本语言。
shell脚本语言
shell中的数据类型
shell中变量无需声明,所有变量都被当做字符串类型。
shell中的数组
在shell中用()来表示数组,数组元素之间用空格来分隔,数组元素下标从0开始。
arr1=(item1 item2 item3) arr2=([0]=item1 [1]=item2 [10]=item10)
数组操作
#访问数组中下标为index的元素 ${arr[index]} #获取数组中的所有元素,效果就是一次性取出数组中的所有元素 ${arr[@]} ${arr[*]} #获取数组中的元素个数 ${#arr[@]} ${#arr[*]} #获取数组下标为index的元素的长度 ${#arr[index]} #数组的合并 arr3=(${arr1[@]} ${arr2[@]}) #删除数组中下标为index的元素 unset arr[index] #删除掉整个数组 unset arr
shell中字符串操作
字符串拼接:将两个字符串放在一起即为字符串拼接(和正则表达式的拼接一样)
字符串截取:
${变量名:start:end} | 从start表示的字符截取到end表示的字符,字符串下标从0开始,左闭右开 |
${变量名:start} | 从start开始截取到字符串末尾 |
${变量名:0-start:length} | 从字符串的右侧截取出start个字符,然后再截取出长度为length的字符串 |
${变量名:#*str} | 当在字符串左边第一次出现字符串str时,从str的下一个字符开始截取原字符串后面的所有字符 |
${变量名:##*str} | 当在字符串左边最后一次出现字符串str时,从str的下一个字符开始截取原字符串后面的所有字符 |
${变量名:%str*} | 当在字符串右边第一次出现字符串str时,截取原字符串中str的左边的所有字符 |
${变量名:%%str*} | 当在字符串右边最后一次出现字符串str时,截取原字符串中str的左边的所有字符 |
shell中的运算符
详情可见shell中的算术展开。
shell中的条件判断
由于shell脚本语言中不存在表达式的概念,因此就不存在通过表达式来判断条件的真假。
shell中的字符串都被当做命令,因此我们可以通过命令的退出状态来判断条件的真假。
在其它的编程语言中,条件判断一般分为整数之间的比较和字符串之间的比较。而shell脚本语言中,多了对文件的判断。
test命令
test命令可以测试一个文件的属性,比如是否为普通文件、是否可读等。
也可以测试字符串等于或者不等于,小于、大于、大于等于、小于等于支持吗?
test也可以测试整数之间的小于、大于、小于等于、大于等于、等于、不等于的比较(比如-gt)。
test可以使用<和>来比较整数和字符串,但是需要在<和>前面加上\进行转义。因为<和>都需要通过转义来使用,因此<=和>=也是理所应当的不支持。
注意test命令中出现变量展开时,最好用双引号将变量包裹起来,否则变量中一旦出现空格,那么变量展开后test命令就会将展开之后的字符串识别为多个参数。
如果变量展开之后是以-开头或者展开之后为空字符串,都会出现预期之外的结果,因此我们可以使用以下方式来进行字符串之间的比较。
test "Xwly" == "X$name"
test如果要测试多个条件,最好的办法是&&将多个条件在test命令外连接起来。
[ $a \> 1 -a $a < 10] #不推荐,可移植性不高 [ $a > 1 ] && [ $a < 10 ] #推荐
[]命令
等价于test命令,但是注意[才是真正的命令,而]只是一个参数,]作为一个参数理应和其它的参数之间存在空格。
[[]]命令
这个命令是test命令的进阶版。
大于和小于符号不再需要转义。
对于空格不再具有严格的要求。
(())命令
详情可见算术展开。
该命令仅支持整数之间的比较。
该命令中,变量不再需要使用$符号,表达式的使用方式完全和其它编程语言一致。
该命令可以用于for循环。
for ((i=0;i<10;i++))
shell中的if-else语句
if condition then 命令 elif condition then 命令 else 命令 fi
为什么要加上then?我们注意到condition的后面要跟着then,因为shell脚本语言中不能用花括号将复合语句包裹起来,所以使用then关键字作为起到左花括号的作用。或者说从另外一个角度来说,then表明了它后面的命令是从属于if-else语句的。
那else后面的命令,为什么不需要在命令的前面加上then呢?因为else后面的命令通过else就可以判断该命令是从属于if-else语句的。
我们也可以减少if语句的行数:
if condition ;then 命令;fi
shell中的while循环语句
while condition do statments done
由于shell脚本语言中不能用花括号来将多条命令包裹起来,因此使用关键字来将复合语句包裹起来是合理的做法。
shell中的until语句
until condition do statments done
一直循环,知道满足条件才停止循环。
shell中的for循环语句
# 第1种形式 for i in item1 item2 item3... do staments done #第2种形式 for ((start..end)) do staments done #第3种形式 for ((i=0;i<n;i++)) do staments done
shell中的case语句
shell中的case语句其实就是其它编程语言中的switch语句。
case i in 匹配模式1) 命令 ;; 匹配模式2) 命令 ;; 匹配模式3) 命令 ;; *) 命令 ;; esac
匹配模式实际上就是正则表达式,不过是最简单的正则表达式,仅支持*、[]和|语法。
为什么要使用双分号来作为分隔符呢?单分号只能表示是一条普通命令的结束,这样shell无法区分单分号后面的命令是case语句的条件还是普通命令,因此使用双分号可以使得case结构更加清晰。双分号就相当于break语句,但是不能使用break语句来代替双分号。
shell中的continue和break语句
可以用在循环语句中,但不能用在case语句中,使用效果与其它编程语言中一致。
shell中的函数
[function] func() { 命令; [return $?] #使用return $?的方式来代替函数默认返回值会比较严谨 }
shell中函数的调用就和使用普通命令是一样的,注意函数先定义后使用即可。
$0为父脚本的名字,其它位置参数则是函数本身的参数。
函数可以有返回值,也可以没有返回值。一般来说,如果不设置函数的返回值,则默认函数中最后一条命令的推出状态为函数的返回值,使用return $?的方式来代替函数默认返回值会比较严谨。
shell中内置的变量
# | 目前进程的参数个数 |
@ | 传递给当前进程的命令行参数,置于于双引号内,展开时仍然为多个参数 |
* | 当前进程的命令行参数,置于双引号内,展开时就是一个参数 |
? | 前一命令的退出状态 |
$ | shell进程的进程id |
注意,我们使用变量的时候要在变量前加上?能获取前一命令的退出状态。
$@和@*有什么区别?
¥@和@*不放置在双引号中时,展开时都被识别为多个参数,效果一样。但是置于双引号中时,"$@"展开为"item1" "item2" "item3"……
"$*"展开为"item1 item2 item3"。
shell展开
变量展开
shell中所有的单词都被当做字符串,对变量进行定义时又无需声明变量,那么对于一个单词来说,究竟怎样区分它是字符串还是变量呢?
shell将$符号放在一个字符串的前面,这样就标明了该字符串实际上时一个变量。将变量替换为字符串称之为参数展开。
name=wly #定义一个变量 echo $name #使用变量
注意,由于shell中字符串是被当做命令来执行的,因此定义一个变量时,等号两边不应该有空格。否则的话,name会被当做命令来执行,=和wly将会被当做命令的参数,由此引发错误。
也许参数展开称为变量展开更形象一点。
命令行参数展开
$0表示程序的名字,$1表示传递给程序的第1个参数,{10},由此类推下去。
算术展开
由于shell中一切都是字符串,因此在其它编程语言中的算术运算在shell中实际上也是会被识别为字符串。
echo 1+2 #输出字符串"1+2"
但如果就是想在shell中使用算术运算怎么办呢?那么我们需要将算术运算与字符串区分开来。我们使用$+双圆括号来表示普通的算术运算。
((1+2)) #执行的是1+2这个命令,因此会报错 ((a=1+2)) #在子shell的子shell中执行赋值语句a=1+2,a的值为字符串"1+2" $((a=1+2)) #a的值为3,整个表达式的值也为3
切记,双圆括号前一定要加上$符号才是正常的算术展开,否则就是()中嵌套一个圆括号。而单圆括号表示新开一个子shell来执行括号中的语句。
双圆括号前加上for关键字也属于算术展开。
在双圆括号内,所有的运算与其它编程语言的效果一样,注意不支持浮点数的运算。
我们想要执行算术运算当然也可以使用其它的命令,比如expr命令、let命令、bc命令。
命令展开
将一个命令展开为它执行之后的结果。
echo `ls` #执行ls获取当前目录下的文件,并由echo来输出文件名。 echo $(ls) #命令展开,效果同上。推荐使用这种方式进行命令展开,因为即使嵌套也不会引发混乱
波浪号展开
执行命令的时候,如果命令的参数的第一个字符为~,将会执行波浪号展开,将~替换为用户的家目录。
echo ~ #~展开为当前用户的家目录 echo ~wly`` #~咱开为用户wly的家目录
通配符展开
执行命令的时候,*将会展开为当前目录下的所有文件。
echo * #输出当前目录下的所有文件的文件名 echo wly #输出当前目录下所有以wly开头的文件的文件名
通配符展开也被称为全局展开或路径展开。
花括号展开
echo {aaa,bbb,ccc}.sh #输出aaa.sh、bbb.sh、ccc.sh echo {1..10} #输出数字1-10 echo {a..z} #输出字母a-z
花括号展开在任意shell中都是生效的吗?
展开失效的场景
使用单引号包裹字符串时,字符串中的展开都会失效。
在使用双引号包裹字符串时,波浪号展开、通配符展开、花括号展开都会失效。而所有通过$符号来实现的展开都会继续生效。