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个参数,2表示传递给程序的第2个参数,由此类推。当要使用第10个参数时,应该使用{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中都是生效的吗?

展开失效的场景

使用单引号包裹字符串时,字符串中的展开都会失效。

在使用双引号包裹字符串时,波浪号展开、通配符展开、花括号展开都会失效。而所有通过$符号来实现的展开都会继续生效。

全部评论

相关推荐

点赞 评论 收藏
转发
2 收藏 评论
分享
牛客网
牛客企业服务