Shell的编程
Bash是用得最广泛的shell,是大多数Linux 系统的缺省 shell。Bourne Again shell (bash), 正如它的名字所暗示的,是Bourne shell 的扩展。bash 与Bourne shell 完全向后兼容,并且在 Bourne shell 的基础上增加和增强了很多特性。bash 也包含了很多 C 和 Korn shell 里的优点。bash 有很灵活和强大的编程接口,同时又有很友好的用户界面。 下面具体来介绍bash。
Bash语法基本介绍
脚本的开头,必须以下面的行开始(必须方在文件的第一行):
#!/bin/bash
符号#!用来告诉系统它后面的参数是用来执行该文件的程序。当编辑好脚本时,如果要执行该脚本,有2种方式执行:
1.给脚本执行权限:
chmod +x filename 这样才能用./filename 来运行
2.直接用bash调用执行
bash filename
注释
在进行shell编程时,以#开头的句子表示注释,直到这一行的结束。
变量
在shell编程中,所有的变量都由字符串组成,并且您不需要对变量进行声明
根据类型又分为环境变量、本地变量、局部变量、位置变量、特殊变量(内置)
本地变量:
varname=value:作用域为整个bash进程可以使用;
变量命名规范:
1. 只能含字母、数字和下划线,并且以字母和下划线开头
2. 最好不要跟系统已有的环境变量重名
3. 见名知意
局部变量:
local varname=value:作用域为当前代码段;
环境变量:作用域为当前shell进程及其子进程,不能影响到其父进程;
export varname=value “导出”,如果变量已经定义可以只是用变量名 export varname,即
1. export varname=value
2. varname=value
export varname
脚本在执行命令时会启动一个子shell环境变量:
位置变量:
$1,$2,$3,……
cat first.sh test.txt hello
$1: first.sh
$2: test.txt
$3: hello
shift:踢掉参数
shift n:踢掉n个参数,默认踢掉一个参数
特殊变量:
$?:上一个命令执行状态的返回值:
程序执行可能有两种返回值:
1. 程序执行结果
2.程序状态返回吗(0-255)0则为执行正确,1-255 则执行出错(1,2,127系统预留);还可以使用exit n 自定义返回值
$#:获取当前shell命令行中参数的总个数
$*:获取当前shell的所有参数 “$1 $2 $3 …,受IFS控制
$@:这个程序的所有参数 “$1″ “$2″ “$3″ “…”,不受IFS控制
$0 获取当前执行的shell脚本的文件名
$n 获取当前执行的shell脚本的第n个参数值,n=1..9
$$ 获取当前shell的进程号(PID)
$! 执行上一个指令的PID
n 这些特殊变量在编程中是非常重要的,例如执行一个脚本testsh a1 a2 ,可以通过$1和$2获得a1和a2的值,$#可以用于判断是否有带参数,和参数个数。
n 还有一个shift [n]:轮替参数,可以把当前输入参数从左至右踢掉一个,可以用来方便的读取各个输入的参数
引用变量:
${varname},括号可以省略,但在某些时候必须使用,用来区分字符还是变量
例:${a}bc
撤销变量:
unset varname
查看当前shell中的变量:
set 包括环境变量和本地变量
查看当前shell中的环境变量:
1. printenv 2. env 3. export
Bash还支持各种条件测试(数值测试、字符串测试、文件测试)
根据运行的命令的状态结果;
有布尔型: “真”:成功, “假”:失败
例:
1 #!/bin/bash
2 if $(cat /etc/fstab &>/dev/null);then
3 echo $?"ok"
4 else
5 echo $?"false"
6 fi
如果cat /etc/fstab执行成功,条件则为真,输出0OK,执行失败输出1false
测试表达式
test EXPRESSION
[ EXPRESSION ]
[[ EXPRESSION ]]
例:
整数测试:隐含着做数值大小比较,所以不要给变量引用加引用;
$A -gt $B:是否大于;是则为“真”,否则为“假”;
$A -ge $B: 是否大于等于;
$A -lt $B:是否小于;
$A -le $B: 是否小于等于;
$A -eq $B: 是否等于;
$A -ne $B:是否不等于;
例:
字符串测试:ASCII数值越大,字符比较时其值越大;
"$A" > "$B":是否大于;
"$A" < "$B":是否小于;
"$A" == "$B":是否等于;
"$A" != "$B":是否不等于;
-z "$A":是否为空;空则为“真”,否则为“假”
-n "$A":是否不空;不空则“真”,空则为“假”
注意:应该使用[[ EXPRESSION ]]
例: 1 #!/bin/bash
2 a="a"
3 b="b"
4
5 if [[ $a < $b ]];then
6 echo $?"ok"
7 else
8 echo $?"false"
9 fi
文件测试:测试文件的存在性以及属性;
-e $file: 是否存在;存在则为“真”,否则为“假”;
-a $file: 同上;
-f $file:文件是否存在且为普通文件;
-d $file:文件是否存在且为目录;
-h $file:是否存在且为符号链接文件;
-L $file: 同上
-b $file:是否存在且为块设备文件;
-c $file:是否存在且为字符设备文件;
-S $file:是否存在且为套接字文件;
-p $file: 是否存在且为管道文件;
-r $file: 当前用户对文件是否拥有读权限;
-w $file:当前用户对文件是否拥有写权限;
-x $file:当前用户对文件是否拥有执行权限;
-u $file:文件是否拥有SUID权限;
-g $file:文件是否拥有SGID权限;
-k $file:文件是否拥有sticky权限;
-O $file: 当前用户是否为指定文件的属主;
-G $file: 当前用户是否为指定文件的属组;
例:
双目操作符:
$file1 -nt $file2: file1是否新于file2, file1的最近一次的修改时间戳是否晚于file2的;
$file1 -ot $file2: file1是否旧于file2, file1的最近一次的修改时间戳是否早于file2的;
$file1 -ef $file2:file1与file2是否指向了同一个inode;测试二者是否为同一个文件的硬链接;
例:
特殊设备:
u /dev/null: 空,bit buckets,吞下所有数据,并直接丢弃;我们可以把一些不需要的信息丢弃,例如脚本中语句执行状态判断的时候
1 #!/bin/bash
2 if $(cat /etc/fstab &>/dev/null);then
3 echo $?"ok"
4 else
5 echo $?"false"
6 fi
cat /etc/fstab的输出全部丢弃了
u /dev/zero:吐出一堆0;
提供输入,read命令:
语法: read [options] VAR...
-p "PROMPT"
-t timeout
例:read �Ct 30 �Cp “请在30秒内输入你的名字:” names
停留30秒让用户输入名字,名字保存到变量names
命令引用:把执行的结果拿来使用
`COMMAND`, $(COMMAND)
可以使用`号或$(),建议使用$()更直观
例:times=$(echo “19:00”)
Bash也包含了for、if、case、等流程控制语句
判断语句:
if CONDITION; then
if-true-分支
fi
条件为真就执行里面的语句
例:if [ $a == “a”];then echo “ok”;fi
值得说明的是如果你将 if 和 then 简洁的写在一行里面,就必须在 then 前面加上分号,如:if [ expression ]; then ...。
if CONDITION; then
if-true-分支1
else
if-false-分支2
fi
条件为真执行分支1,否则执行分支2
例:if [ $a == “a”];then
echo “ok”
else
echo “false”
fi
case语句
简洁版多分支if语句;
使用场景:判断某变量的值是否为多种情形中的一种时使用;
语法:
case $VARIABLE in
PATTERN1)
分支1
;;
PATTERN2)
分支2
;;
PATTERN3)
分支3
;;
...
*)
分支n
;;
esac
PATTERN可使用glob模式的通配符:
*: 任意长度的任意字符;
?: 任意单个字符;
[]: 指定范围内的任意单个字符;
a|b: 多选1;多条件判断
例:
#!/bin/bash
read -p "please input a char: " char
case $char in
[a-z])
echo "A character."
;;
[0-9])
echo "A digit."
;;
*)
echo "A special character."
;;
esac
注意每个分支语句后面跟;;
循环语句:
for VARIABLE in LIST; do
循环体
done
n LIST:是一个或多个空格或换行符分隔开的字符串组成;
把列表的每个字符串逐个赋值给VARIABLE表示的变量;
n 进入条件:列表非空;
n 退出条件:列表遍历结束
LIST的生成方法:
l 整数列表
(a) {start..end}
(b) $(seq [start [[step]] end)
l 直接给出列表
l glob
l 命令生成
例:命令生成
1 #!/bin/bash
2 for i in $(ls);do
3 echo $i
4 done
例: 整数列表
1 #!/bin/bash
2 for i in {1..20};do
3 echo $i
4 done
------------
1 #!/bin/bash
2 for i in $(seq 1 2 20);do
3 echo $i
4 done
例:直接给出列表
1 #!/bin/bash
2 user1="aa"
3 user2="bb"
4
5 for i in $user1 $user2;do
6 echo $i
7 done
例:glob
1 #!/bin/bash
2 for i in /root/test/*;do
3 echo $i
4 done
while循环:
while CONDTION; do
循环体
控制变量的修正表达式
done
进入条件:当CONDITION为“真”;
退出条件:当CONDITION为“假”;
例:
1 #!/bin/bash
2 i=1
3 while [ $i -lt 20 ];do
4 echo $i
5 let i++
6 done
----------------------
打印99乘法表
1 #!/bin/bash
2 i=1
3 while [ $i -le 9 ];do
4 j=1
5 while [ $j -le $i ];do
6 echo -n -e "${j}x$i=$[$j*$i]\t"
7 let j++
8 done
9 echo -e "\n"
10 let i++
11 done
unitl循环:
until CONDITION; do
循环体
循环控制变量的修正表达式
done
进入条件:当CONDITION为“假”时
退出条件:当CONDITION为“真”时
例:打印99乘法表
1 #!/bin/bash
2
3 i=1
4 j=1
5
6 until [ $i -gt 9 ];do
7 j=1
8 until [ $j -gt $i ];do
9 echo -e -n "${j}x$i=$[$j*$i]\t"
10
11 let j++
12 done
13 echo -e "\n"
14 let i++
15 done
循环控制:
continue [n]:提前结束本轮循环,而直接进入下一轮;
break [n]:提前结束循环;
例:
01.#!/bin/bash
02.
03.LIMIT=19 # 上限
04.
05.echo "Printing Numbers 1 through 20 (but not 3 and 11)."
06.
07.a=0
08.
09.while [ $a -le "$LIMIT" ]
10.do
11. a=$(($a+1))
12.
13. if [ "$a" -eq 3 ] || [ "$a" -eq 11 ] # 除了3和11.
14. then
15. continue # 跳过本次循环剩余的语句.
16. fi
17.
18. echo -n "$a " # 在$a等于3和11的时候,这句将不会执行.
19.done
20.
21.echo
22.exit 0
执行结果:
1. Printing Numbers 1 through 20 (but not 3 and 11).
2. 1 2 4 5 6 7 8 9 10 12 13 14 15 16 17 18 19 20
同样的程序改为break,说明break是程序跳出循环,之后的循环过程无法得到执行,break命令还可以带一个参数,一个不带参数的break命令只能退出最内层的循环,而break N可以退出N层循环。参数 N还是少用吧,比较绕。
01.#!/bin/bash
02.
03.LIMIT=19 # 上限
04.
05.echo "Printing Numbers 1 through 20 (but not 3 and 11)."
06.
07.a=0
08.
09.while [ $a -le "$LIMIT" ]
10.do
11. a=$(($a+1))
12.
13. if [ "$a" -eq 3 ] || [ "$a" -eq 11 ] # 除了3和11.
14. then
15. break # 跳过本次循环剩余的语句.
16. fi
17.
18. echo -n "$a " # 在$a等于3和11的时候,这句将不会执行.
19.done
20.
21.echo
22.exit 0
执行结果:
1. Printing Numbers 1 through 20 (but not 3 and 11).
2. 1 2
循环的一些特殊用法:
死循环:
while true; do
循环体
if CONDTION; then
break
fi
done
until false; do
循环体
if CONDITION; then
break
fi
done
示例:每隔3秒钟查看当前系统上是否有名为“gentoo”的用户登录;
如果某次查看gentoo登录了,则显示gentoo已经登录;
如果未登录,就显示仍然未来,并显示这是已经是第多少次查看了;
#!/bin/bash
#
username=$1
declare -i count=0
while true; do
if who | grep "^$username" &> /dev/null; then
echo "$username is logged."
break
else
let count++
echo "$count $username is not login."
fi
sleep 3
done
while循环的特殊用法:
遍历文件的每一行:
while read VARIABLE; do
循环体
done < /PATH/FROM/SOME_FILE
示例:找出UID为偶数的所有用户,显示其用户名和ID号;
#!/bin/bash
#
while read line; do
userid=$(echo $line | cut -d: -f3)
if [ $[$userid%2] -eq 0 ]; then
echo $line | cut -d: -f1,3
fi
done < /etc/passwd
for循环的特殊用法(C语言风格):
for ((expr1;expr2;expr3)); do
循环体
done
expr1: 定义控制变量,并初始赋值;
expr2: 循环控制条件;
进入条件:控制条件为“真”
退出条件:控制条件为“假”
expr3: 修正控制变量
示例:求100以内所有正整数之和;
#!/bin/bash
#
declare -i sum=0
for ((i=1;i<=100;i++)); do
let sum+=$i
done
echo "Sum: $sum."
Bash也支持函数:function:
把一段具有独立功能代码封装在一起,并给予命名;后续用到时,可直接通过给定函数名来调用整体代码;可实现代码重用,模块化编程
函数格式:
function func () {
statementsreturn 1;
}
函数返回值
1、如果使用函数返回值,return只能返回一个整数(0~255),不能返回字符串,且返回值保存在$?变量中,不能直接赋值给其它变量例如,下面获得函数返回值的写法是错误的
function func () { return 3; }
i=`func`
2、如果return没有指定参数,则是最后一行脚本的退出状态值
3、如果要将函数返回值赋值给一个变量,有两种方式:
a)用$?赋值
funci=$?
b)在函数中,用echo打印返回值,再赋值
function func () { echo 3; }
i=`func`
函数参数
1、向函数传递的参数被当作位置参量来处理,在函数中是本地变量2、函数参数用$1, $2 ,..., $n来表示,但和通过命令行传递给脚本的参数不同。调用方式如下:
func param1 param2
函数中的变量
1、在一个shell中的变量无局部和全局之分,随用随声明,无作用域的概念。例如,在一个if...fi块中定义的变量,出了这个块的作用域仍然有效。所以我们尽量在函数中定义局部变量,使用local来定义
func() {
local count
echo $count
}
函数调用
1、使用function只是定义函数,要执行函数中的命令必须在脚本中或命令行上调用函数,例如: func param1 param2
a) 将函数单独放入一个脚本里,再在命令行上执行脚本(直接执行,或使用.,或source),是不会执行函数里的命令的
b) 将函数单独放入一个脚本,然后执行,相当于在执行该脚本的shell环境中定义了该函数
例如:下面的命令只是在shell环境中定义函数,并不会调用函数
./func_script.sh
或
source ./func_script.sh
2、函数可以递归:函数可以自己调用自己,调用次数没有限制
3、函数中使用exit命令退出整个脚本。
一些函数相关的命令
1、查看定义了哪些函数
declare -f
declare -F //只列出函数名
2、撤消函数定义unset func_name
3、将函数输出给子export -f func_name
脚本调试的一些技巧:
bash -n: 检查脚本中的语法错误;
bash -x:调试执行脚本;