脚本文件格式:
第一行,顶格:#!/bin/bash
#!/bin/bash 前面不能有任何字符或空白字符,空白行更是不行。称之为shebang 标记为以bash来执行。让内核调用解释器来运行,而不是当作二进制来执行。
目录:
一、顺序执行
二、条件测试
三、算术运算
四、选择执行
五、循环执行
六、数组使用
七、函数
bash脚本,面向过程的编程中
顺序执行: 默认法则,逐条执行各语句。
选择执行: 有不同的分支,经过条件判断, 符合条件的分支就会执行。
循环执行: 将同一段代码代码反复执行多次, 有循环退出条件。
一、顺序执行
这个没有什么说的。我们来看一下基本的脚本格式。和一些杂七杂八的东西。
第一行顶格必须是#!/bin/bash。上面我们也说了。
#!/bin/bash # echo "Hello World"
可以用bash命令直接执行脚本:
star@sst-pp:/tmp$ bash test.sh Hello World star@sst-pp:/tmp$
也可以给予执行权限,再直接执行:
star@sst-pp:/tmp$ chmod +x test.sh star@sst-pp:/tmp$ ./test.sh Hello World star@sst-pp:/tmp$
命令引用:
把命令替换成命令的执行结果。
#!/bin/bash # echo "Hello World" echo "This time is `date +%F-%T`" #这里是键盘上tab键上面的那个` echo "This time is $(date +%F-%T)" #命令引用还可以用$()的方式。
执行结果:
star@sst-pp:/tmp$ ./test.sh Hello World This time is 2016-02-12-21:03:14 This time is 2016-02-12-21:03:14 star@sst-pp:/tmp$
注意:在当前的执行环境中,正在执行的命令和在命令引用里的命令是不一样的。如:
star@sst-pp:~/mydata/linux$ date +%F 2016-02-12 star@sst-pp:~/mydata/linux$ `date +%F` 2016-02-12:未找到命令 star@sst-pp:~/mydata/linux$
在命令引用出来以后就是命令的结果,当然如果结果碰巧也是可以执行的命令那就另一说了。了解一下,一会儿在if语句那里再提一下。
变量引用和命令引用都是用来获取数据的。$abc 获取abc的数据,$(cat /etc/passwd) 获取cat命令的执行结果。
双引号,单引号:
而这里的双引号是用来把字符串括起来,不然中间有空格的话,传给echo的就只是一部分字符了。
在bash里面单双引号是不同的。
单引号:强引用,单引号里面的数据完全成了普通字符串,bash不会过多处理它。
双引号:弱引用,双引号里面的数据除了空格以外,其它的特殊字符,bash都会处理。
#!/bin/bash # echo 'This time is `date +%F-%T`' #单引号 echo "This time is $(date +%F-%T)" #双引号 echo "$$" #显示当前shell的PID。 可以不加双引号。 echo '$$' #这里用的单引号。
执行结果:
star@sst-pp:/tmp$ ./test.sh This time is `date +%F-%T` This time is 2016-02-12-21:25:52 3014 $$ star@sst-pp:/tmp$
单引里面的变量引用和命令引用都不能使用,其它的特殊字符也一样。
位置参数:
#!/bin/bash echo "$1-$2-$3" #位置变量 echo "total:$*" #总的参数 echo "count:$#" #参数数量 echo "path:$0" #执行路径 echo "script_name:`basename $0`" #脚本名称
执行结果:
star@sst-pp:/tmp$ ./test.sh 1 3 5 7 9 10 1-3-5 total:1 3 5 7 9 10 count:6 path:./test.sh script_name:test.sh star@sst-pp:/tmp$
二、条件测试:
如果变量有可能会为空,一定要加上双引号,在任何测试中,不然为报:需要一元表达式的错误。
测试方法
test EXPRESSION
[ EXPRESSION ]
[[ EXPRESSION ]]
EXPRESSION为测试表达式,把用于测试条件的表达式放在那个位置就可以,它会返回0或1。再由if或while之类的语句做出判断。在bash里面0为真,非0为假。
注意:EXPRESSION两端必须有空白字符,否则为语法错误;
任何命令的执行结果都可以做为判断条件。用命令的状态返回值做判断。命令会返回0或非0。
常用判断类型:
整数测试: 比较大小。
字符测试: 比较字符串大小, 是否为空。
文件测试: 文件类型,文件是否存在。
(1)字符测试
双目测试符:
> 大于
< 小于
== 等于,等值比较。有时用一个=号也可以,但是为了规范,还是用两个。等号两边有空格
[ "stringVarA" == "stringVarB" ]
=~ 左侧是字符串或变量,右侧是一个模式,判定左测的字符串能否被右侧的模式所匹配,通常只能在[[]]双中括号中使用。字符测试中的=~模式匹配要在[[]]里面。测试发现 \< \> 单词锚定不可用, 不会报错, 但返回值是不正确的。模式不要加引号。
[[ "strinsA" =~ cd ]] 用来测试变量strinsA中是否有cd这个字符串。
字符测试中的字符串或变量最好要加上引号,用来确定这个里面就是数据,避免如果是空变量的话,测试结果会不一样。
!=,<> 都是不等于的意思。一般都用!=
在字符测试中有时候要用双中括号,不然会报类似的信息。
user.sh: 第 6 行: [: ==: 需要一元表达式
单目测试符:
-n "$stringVar" 判断字符串或变量是否不空。这个其实只是判断后面是否有数据,就算是命令返回的数据也一样。只不过命令要放在命令引用里面。
[ -n "$stringVar" ] 判断变量是否不空。不空为真,空为假。
-z "$stringVar" 判断字符串或变量是否为空。空为真,不空为假。
注意: 变量一定要加上双引号, 因为变量如果为空,没有引号就是没有指定给判断符为空的值。这样的情况最后结果会是变量不空。
(2)数值测试:
-gt 大于 greater than
-lt 小于 less than
-ge 大于等于
-le 小于等于
-ne 不等于
-eq 等于 equal to
使用方式都一样。[ $num1 -eq $num2 ]
(3)文件测试:
单目测试:
-e file : 测试文件是否存在。 存在则为真,不存在则为假。
-a file : 也是测试文件是否存在。 存在则为真,不存在则为假。
-f file : 测试文件是否为普通文件
-d file : 测试文件是否为目录文件
-b file : 测试文件是否为块设备
-c file : 测试文件是否为字符设备
-h file : 测试文件是否为符号链接文件。 -L 也是同样的功能
-p file : 测试文件是否为管道文件。
-S file : 测试文件是否为套接字文件。 大写的S
-s file : 测试文件是否不空。
-r file : 测试执行用户是否对文件有读取权限。
-w file : 测试执行用户是否对文件有写权限。
-x file : 测试执行用户是否对文件有执行权限。
测试文件的时候 不要在文件右边加上斜杠,如 file/,这样会把file/ 当作一个文件,而不是file. 所以就总是假。
双目测试:
-nt -ot 中第二个文件如果不存在,也会认为第一个文件更新或更老, 也就是结果为真。
file1 -nt file2 测试是否第一个文件比第二个文件新。
file1 -ot file2 测试是否第一个文件比第二个文件老。
file1 -ef file2 测试两个文件是否在相同的设备并且相同的inode号。
三、算术运算
算术运算,用来把字符变量进行算术运算。最终把字符的结果,如:23+3 变成数值的结果。不然的话:
[root@nfs ~]# a=5 [root@nfs ~]# b=4 [root@nfs ~]# c=a+b [root@nfs ~]# echo $c a+b [root@nfs ~]# c=$a+$b [root@nfs ~]# echo $c 5+4
都是字符相加了。
算术运算方式:
declare
-i 明确声明为整型变量,可以直接算术运算, a=$b+$c
只要把存储结果的变量声明为整型就可以。 上面的a.
let 指明为算术运算
let varName=算术表达式 , let sum=$a+$b
不能直接显示出结果,必须以这种把值赋给前面的变量的方式来显示结果。
let 只是让后面的变量以整形计算。而至于结果给谁,怎么显示let都不会管。
所以我们需要手动把结果给一个存储空间,在这里也就是需要赋给变量了。
如果计算结果存在小数, 将会被圆整。
varName=$[算术表达式] 也可以把中括号换成两个小括号 (())
可以直接计算出数值,所以可以像下面这样直接用echo 显示出结果。
如: echo $[$a+$b]
$[$a+$b] 其实就相当于 $[12+23]
$[$a+$b]写成$[a+b]也是可以的。
expr 来指定进行算术运算。 变量与中间的 运算符 必须要有空格。
varName=`expr $num1 + $num2`
这里是直接用的命令引用,所以这个是直接计算出结果以后再给变量varName的。
expr $num1 + $num2 就可以直接得出结果。
因为expr是一个命令,后面的数值和运算符都是参数,所以才要空格分开。
算术运算操作符:
+ | 加 |
- | 减 |
* |
乘 |
/ |
除 |
% |
取余 |
** |
乘方 |
取余运算可以用来判断是否奇偶数, 与2 得0 也就是偶数,与2得1也就是奇数
注意:有些时候乘法符号可能需要转义,因为它是通配符啊。
增强型赋值:
变量做某种算术运算后回存至此变量中;#表示数字。
一般是这样: let i=$i+#
现在是这样: let i+=#
+=,-=,*=,/=,%=
自加,自减,自乘,。。。。。
自增:
VAR=$[$VAR+1]
let VAR+=1
let VAR++
自减:
VAR=$[$VAR-1]
let VAR-=1
let VAR--
如果总是在自身的基础上+=1 就可以用 ++ 来表示。
let varName++ 用 let 算术运算++。
如果只是单纯的++,还可以用(())来替换let,注意不是$(())啊。
((varName++))
额外:
++的方式有两种,一是在变量后面++ 一种是在变量前面++
++varName 在前面的是先加1再引用。 后面的是先引用再加1.
正常给变量赋值可能看不出来什么, 因为我们使用的只是变量。 但是:
看这个:
[root@Test ~]# a=
[root@Test ~]# echo $[a++]
0
[root@Test ~]# echo $a
1
[root@Test ~]#
很明显是算术表达式 $[] 在运算之前,先给了echo 0 。
然后才给a加了1.
那么如果把++放在前面:
[root@Test ~]# a=
[root@Test ~]# echo $[++a]
1
[root@Test ~]# echo $a
1
[root@Test ~]#
这就是先由算术表达式$[]给a进行了++ 运算,然后再把值给echo。
上面的$[]如果换成$(())也是一样的。 它们都是算术表达式。
脚本执行:
bash -x 显示脚本的执行过程,用于调试。bash -n 检测有没有语法错误。
四、选择执行:
(1) &&, ||
在逻辑运算里面。如果两个条件做与运算,那么只要第一个条件为假,整体的运算结果一定为假。
而做或运算,只要第一个条件为真,那么整体一定也为真。
&&为与运算符, || 为或运算符。
短路法则:
~]# COMMAND1 && COMMAND2
COMMAND1为“假”,则COMMAND2不会再执行;
否则,COMMAND1为“真”,则COMMAND2执行;
~]# COMMAND1 || COMMAND2
COMMAND1为“真”,则COMMAND2不会再执行;
否则,COMMAND1为“假”,则COMMAND2执行;
例如:
#!/bin/bash # id $1 &> /dev/null && echo "$1 already exis" || useradd $1
执行过程:
star@sst-pp:/tmp$ sudo ./test.sh root root already exis star@sst-pp:/tmp$ sudo ./test.sh sst star@sst-pp:/tmp$ sudo ./test.sh sst sst already exis star@sst-pp:/tmp$
如果id的结果为真,则会执行与运算符后面的操作。而这里只是echo数据,结果也为真。那么与运算结果为真, 或运算只要运算符前面为真,则不会再执行后面的操作。
而如果id为假,与运算符后面的操作也不会再执行。 与运算整体为假, 或运算符前面为假,开始执行或运算符后面的操作。
这里有个小坑:
#!/bin/bash # id $1 &> /dev/null && echo "$1 already exis" || useradd $1 && echo "$1 create success"
执行过程:
star@sst-pp:/tmp$ sudo ./test.sh root root already exis root create success star@sst-pp:/tmp$ sudo ./test.sh user1 user1 create success star@sst-pp:/tmp$ sudo ./test.sh user1 user1 already exis user1 create success star@sst-pp:/tmp$
因为最后一个与运算符前面的条件始终都会为真,所以后面的操作每次都会执行。
如果id为真,第一个与运算符后面的echo也为真,这个与运算结果为真。然后与后面的或运算符再运算,虽然或运算符后面的useradd不会执行,但或运算结果为真。
就算是没有最后一个与运算,而前面的与和或换一下位置也会发生这种情况。
id $1 &> /dev/null || useradd $1 && echo "$1 already exis"
最后再来一个:
#!/bin/bash # [ -r /tmp/var.sh ] && source /tmp/var.sh && echo -e "$user\n$file\n$shell"
执行结果:
star@sst-pp:/tmp$ ./test.sh root ab.txt /bin/bash star@sst-pp:/tmp$
从另一个文件中读入了变量。
(2) if语句
if语句有三种格式:
单分支:
if 条件; then
分支1;
fi
双分支:
if 条件; then
分支1;
else #不满足条件,执行这个。
分支2;
fi
多分支:
if 条件1; then
分支1;
elif 条件2; then
分支2;
elif 条件3; then
分支3;
.....
else #上面都不满足的时候,执行这个分支。也可以不写。
分支n;
fi
注意:即便多个条件可能都能同时满足,也只是会执行其中第一个满足条件的。
例:通过命令行参数给定一个用户名,如果用户存在则输出用户名与shell。
#!/bin/bash if id $1 &> /dev/null;then echo "User: $1" echo "Shell: `grep $1 /etc/passwd | cut -d : -f 7`" fi
执行过程:
star@sst-pp:/tmp$ ./test.sh abc star@sst-pp:/tmp$ ./test.sh root User: root Shell: /bin/bash star@sst-pp:/tmp$ ./test.sh nobody User: nobody Shell: /usr/sbin/nologin star@sst-pp:/tmp$
我们知道,测试一个条件也可以用命令的执行状态,上面用&>重定向把命令的所有输出结果重定向到/dev/null。if只要这个命令的状态码就够了。
注意: 不要加上命令引用,命令引用获取的是命令的输出结果,这样if判断的就是这个命令的输出的数据了。如: if `id root` 执行的时候就变成 if uid=0(root) gid=0(root) 组=0(root) 。
例:通过命令行参数给定一个用户名,判断其ID号是偶数还是奇数;
#!/bin/bash # uid=`id -u $1 2> /dev/null` [ -z "$uid" ] && echo "no user $1" && exit 1 if [ $[$uid%2] -eq 1 ];then echo "$1 uid is Odd number" else echo "$1 uid is Even number" fi
执行过程:
star@sst-pp:/tmp$ ./test.sh root root uid is Even number star@sst-pp:/tmp$ ./test.sh sst sst uid is Odd number star@sst-pp:/tmp$ ./test.sh eer no user eer star@sst-pp:/tmp$
上面的2>是错误重定向,把本来应该输出到屏幕的重定向到/dev/null。下面再细说重定向。
exit退出脚本,并且脚本的返回值为后面所指定的值。 一般情况下没有exit,脚本的返回值会是最后一个命令的状态值。
例:通过命令行参数给定两个文本文件名,如果某文件不存在,则结束脚本执行;都存在时返回每个文件的行数,并说明其中行数较多的文件;
#!/bin/bash # [ $# -lt 2 ] && echo "need more file path" && exit 1 #if all file not exis,elif single file not exis, if ! [ -f $1 -o -f $2 ];then echo "$1 and $2 is not find" exit 1 elif ! [ -f $1 ];then echo "$1 is not find" exit 1 elif ! [ -f $2 ];then echo "$2 is not find" exit 1 fi #line number file1=`wc -l $1 | cut -d' ' -f1` file2=`wc -l $2 | cut -d' ' -f1` if [ $file1 -gt $file2 ];then more=$1 elif [ $file1 -lt $file2 ];then more=$2 else more="same" fi echo "`basename $1` line is: $file1" echo "`basename $2` line is: $file2" echo "more: $more"
执行:
star@sst-pp:/tmp$ ./test.sh /etc/passwd /etc/fsta /etc/fsta is not find star@sst-pp:/tmp$ ./test.sh /etc/passwd /etc/fstab passwd line is: 45 fstab line is: 21 more: /etc/passwd star@sst-pp:/tmp$
I/O重定向:这里就只是简单描述一下了。
I/O 重定向(输入输出重定向)
输入重定向 : <
输出重定向: > , >>
错误重定向: 2> , 2>>
所有输出 &> , &>>
> * 2>&1 , >> * 2>&1
这里是把错误输出加到标准输出中。也可以把标准输出加入到错误输出中。
2> * 1>&2
&是专门用来引用文件描述符的, 也可以手动为某个文件设定自定义的文件描述符。
/dev/null 黑洞,常在重定向中使用。 重定向到些位置的数据都会清除。还有一个这里顺便提一下/dev/zero,泡泡机,常用于生成虚拟磁盘文件。以0填充生成文件,没有实际数据。
<< 可以用于在脚本中创建文件,或者生成菜单。 也算是输入重定向。
如:
star@sst-pp:/tmp$ cat > test << EOF > Hello World > +++++ > ----- > good luck > EOF star@sst-pp:/tmp$ cat test Hello World +++++ ----- good luck star@sst-pp:/tmp$
这里的过程是<<先把数据给了cat,cat正常情况下是要输出到屏幕的,这里重定向到了文件。
cat > test这是一部分,<<EOF是另一个部分,EOF是用于结束输入的,这个字符可以自定,一般都用EOF来表示。
上面说了,一般是用来在脚本中生成显示菜单的。如:
star@sst-pp:/tmp$ cat << EOF > Hello > World > good luck > EOF Hello World good luck star@sst-pp:/tmp$
输入的数据会由cat标准输出到屏幕上。
中间的数据也可以用变量表示,而完成自动向文件输入数据或输出到屏幕。
(3) case语句
case $VARAIBLE in
PAT1)
分支1
;;
PAT2)
分支2
;;
...
*)
分支n
;;
esac
case支持glob风格的通配符:
*:任意长度的任意字符;
?:任意单个字符;
[]:范围内任意单个字符;
a|b:a或b;
也支持一些特定的字符集:
[:upper:] 大写字母 [:lower:] 小写字母
[:alpha:] 所有字母 [:digit:] 所有数字
[:alnum:] 所有字母和数字 [:space:] 空格
[:punct:] 标点符号
这些只是代表一组特定的字符,使用的时候要在[]里面。如:[[:upper:]]。
命令补充:交互式脚本
read
-p 可以添加提示符 如: read -p "Please Enter number" num
-t 超时时间。
数据比变量多的话, 多出的数据会赋给最后一个变量。
变量比数据多的话, 多出的变量为空。 就算以前有值,也会变成空。
case不能像if一样做很复杂的判断,它只是简单的匹配,匹配成功则执行对应的分支。
适用于有很多选择,但不用做复杂的判断的情况。if也可以做很多选择判断,不过就有点臃肿了。
例:显示一个菜单让用户选择,通过不同的选择做出不同的操作。
#!/bin/bash # cat << EOF 1)show cpu info(c) 2)show disk partition info (d) 3)show disk use (s) 4)show memrofy info (m) 5)Quit(q) EOF read -p "Enter your choose:" option #if option is null, default m option=${option:-m} #如果输入的是大写字母,把它改为小写。也可以用option=${option,}来实现,不过不常用。 option=`echo $option | tr [[:upper:]] [[:lower:]]` case $option in c) lscpu ;; d) fdisk -l ;; s) df -Th ;; m) free -m ;; *) exit 0 esac
执行:
star@sst-pp:/tmp$ sudo bash info.sh 1)show cpu info(c) 2)show disk partition info (d) 3)show disk use (s) 4)show memrofy info (m) 5)Quit(q) Enter your choose:q star@sst-pp:/tmp$
数据量太大了,就不贴了。 最后的*)相当于是替补了,因为*是匹配所有的,上面匹配不到的,就到这里执行来了。
五、循环执行
循环执行: 将一段代码重复执行0、1或多次;
进入条件:条件满足时才进入循环;
退出条件:每个循环都应该有退出条件,以有机会退出循环;
for循环
while循环
until循环
1、for循环
两种格式:
(1) 遍历列表
(2) 控制变量
遍历列表:
for VARAIBLE in LIST; do
循环体
done
进入条件:只要列表有元素,即可进入循环;
退出条件:列表中的元素遍历完成;
列表的生成
{start..end} 如:{1..10} 生成1-10的列表。但是里面只能用数字,而不能用变量。
seq [起始] [步长] 结束 起始默认为1 步长默认为1.
如: seq 6 16 生成从6到16的数字列表,可以指定步长。 seq 1 2 6 得出: 1 3 5
列表不只是输入的数据,也可以是文件名,所以可以使用文件名通配符的方式代表目录下边的所有文件,这样每一个文件名都可以赋给变量。
使用命令生成, 如 ls 出来目录下的所有文件, 但是如果文件名有空格就麻烦了。
直接手动给出列表, 每个值用空格分开。
列表中的数据是以空格或tab或换行分开的。
例:求100以内所有正整数之和。
#!/bin/bash # sum=0 for i in {1..100};do let sum+=$i done echo $sum
执行:
star@sst-pp:/tmp$ bash sum.sh 5050 star@sst-pp:/tmp$
那如果是任意数之间的所有正整数之和呢,只要1和100用变量替换就可以啦。
例:计算当前系统上的所有用户的uid和gid之和。直接以命令取出/etc/passwd文件中的uid与gid。
#!/bin/bash # sum_uid=0 sum_gid=0 file=/etc/passwd for i in `cut -d: -f3 $file`;do let sum_uid+=$i done for i in `cut -d: -f4 $file`;do let sum_gid+=$i done echo "uid sum is: $sum_uid" echo "gid sum is: $sum_gid"
结果:
star@sst-pp:/tmp$ bash sum_id.sh uid sum is: 72346 gid sum is: 334031
例:再来个有意思的,输出99乘法表。
#!/bin/bash for i in {1..9};do #这个是控制竖列的从1到9。 for y in `seq 1 $i`;do #这个是控制一行从1到几的。这个几就是上面i的值。 echo -n -e "${y}x${i}=$[$y*$i]\t" done echo done
结果:
star@sst-pp:/tmp$ bash 9x9.sh 1x1=1 1x2=2 2x2=4 1x3=3 2x3=6 3x3=9 1x4=4 2x4=8 3x4=12 4x4=16 1x5=5 2x5=10 3x5=15 4x5=20 5x5=25 1x6=6 2x6=12 3x6=18 4x6=24 5x6=30 6x6=36 1x7=7 2x7=14 3x7=21 4x7=28 5x7=35 6x7=42 7x7=49 1x8=8 2x8=16 3x8=24 4x8=32 5x8=40 6x8=48 7x8=56 8x8=64 1x9=9 2x9=18 3x9=27 4x9=36 5x9=45 6x9=54 7x9=63 8x9=72 9x9=81
如果再修改一下:
#!/bin/bash for i in {1..9};do #这里和上面一样。 for y in `seq 1 $i`;do echo -n -e "${y}x${i}=$[$y*$i]\t" done echo done #下面是逆序的99乘法表。 for i in {1..9};do #这里也是从1到9的循环。 count=$[9-$i] #因为是倒着来的,所以这里就用9减去i。 for y in $(seq 1 $count);do #代表每一行的列表就是越来越小了。 echo -n -e "${y}x$count=$[$y*$count]\t" done echo done
结果:
star@sst-pp:/tmp$ bash 9x9.sh 1x1=1 1x2=2 2x2=4 1x3=3 2x3=6 3x3=9 1x4=4 2x4=8 3x4=12 4x4=16 1x5=5 2x5=10 3x5=15 4x5=20 5x5=25 1x6=6 2x6=12 3x6=18 4x6=24 5x6=30 6x6=36 1x7=7 2x7=14 3x7=21 4x7=28 5x7=35 6x7=42 7x7=49 1x8=8 2x8=16 3x8=24 4x8=32 5x8=40 6x8=48 7x8=56 8x8=64 1x9=9 2x9=18 3x9=27 4x9=36 5x9=45 6x9=54 7x9=63 8x9=72 9x9=81 1x8=8 2x8=16 3x8=24 4x8=32 5x8=40 6x8=48 7x8=56 8x8=64 1x7=7 2x7=14 3x7=21 4x7=28 5x7=35 6x7=42 7x7=49 1x6=6 2x6=12 3x6=18 4x6=24 5x6=30 6x6=36 1x5=5 2x5=10 3x5=15 4x5=20 5x5=25 1x4=4 2x4=8 3x4=12 4x4=16 1x3=3 2x3=6 3x3=9 1x2=2 2x2=4 1x1=1
for循环的特殊用法:控制变量
for ((控制变量初始化;条件判断表达式;控制变量的修正语句)); do
循环体
done
控制变量初始化:仅在循环代码开始运行时执行一次;
控制变量的修正语句:每轮循环结束会先进行控制变量修正运算,而后再做条件判断;
例:还是小99:
#!/bin/bash for ((i=1;i<=9;i++));do for ((y=1;y<=i;y++));do echo -n -e "${y}x${i}=$[$y*$i]\t" done echo done for ((i=8;i>=1;i--));do for ((y=1;y<=i;y++));do echo -n -e "${y}x${i}=$[$y*$i]\t" done echo done
结果与上面一样。
不过我觉得这种方式一般还是用在数据从大减到小的情况时。
for ((y=$soft_num;y!=0;y--));do
而正常情况下的列表方式,是从小增到大。
while循环:
while CONDITION; do
循环体
循环控制变量修正表达式
done
进入条件:CONDITION测试为”真“
退出条件:CONDITION测试为”假“
例:还是求100以内正整数之和。
#!/bin/bash sum=0 num=1 while [ $num -le 100 ];do let sum+=$num ((num++)) done echo $sum
结果就是5050,这里不贴了。
例:上面的显示信息的脚本加上循环,只有输入quit的时候才会退出循环。
#!/bin/bash option=m while [ $option != quit ];do cat << EOF 1)show cpu info(c) 2)show disk partition info (d) 3)show disk use (s) 4)show memrofy info (m) 5)quit EOF #这个必须顶格 read -p "Enter your choose:" option option=${option:-m} option=${option,} case $option in c) lscpu ;; d) fdisk -l ;; s) df -Th ;; m) free -m ;; esac done
还要在while之前重复定义option,好像有点多余,但上面的条件中用的option又不能为空。
可以用true来表示条件,表示真。一直都是真。 而退出循环就要通过break来了。
循环控制语句:
continue:提前结束本轮循环,而直接进入下一轮循环判断;
break:提前跳出循环。
注意: break是跳出循环的,后面可以加上数字,表示跳出几层循环(在循环嵌套中)。而exit是退出脚本的。
如写成这样就可以了。
#!/bin/bash while true;do cat << EOF 1)show cpu info(c) 2)show disk partition info (d) 3)show disk use (s) 4)show memrofy info (m) 5)quit EOF read -p "Enter your choose:" option option=${option:-m} option=${option,,} [ $option == quit ] && break ## case $option in c) lscpu ;; d) fdisk -l ;; s) df -Th ;; m) free -m ;; esac done
while循环的特殊用法(遍历文件的行):
while read VARIABLE; do
循环体;
done < /PATH/FROM/SOMEFILE
依次读取/PATH/FROM/SOMEFILE文件中的每一行,且将值赋值给VARIABLE变量;
例:输出/etc/passwd文件中shell为/bin/bash的用户。
#!/bin/bash # while read line;do user=`echo $line | cut -d: -f1` shell=`echo $line | cut -d: -f7` if [[ $shell == /bin/bash ]];then echo -n "$user " fi done < /etc/passwd ## echo
star@sst-pp:/tmp$ bash user.sh root star sstc
命令的输出也可以经过管道传给while。如把上面脚本中done后面的</etc/passwd去掉以后。
用管道传递的方式,效果也是一样的。
star@sst-pp:/tmp$ cat /etc/passwd | bash user.sh root star sstc
until循环:
until CONDITION; do
循环体
循环控制变量修正表达式
done
进入条件:CONDITION测试为”假“
退出条件:CONDITION测试为”真“
until跟while结构一样。只不过这个是为假才执行,为真就退出。
六、数组使用:
直接来例子:想从一些学生中随机的挑出几个。
#!/bin/bash #discrition: this is random print student name list modu=10 #要与人数相同 count=4 #挑出几个 student=('liMing' 'xisoHui' 'Hua' 'Mals' 'Bili' 'youo' 'dAn' 'zhangsan' 'lisi' 'xiaoli') #学生列表 for i in `seq 1 $count`;do random=$[$RANDOM%$modu] #生成随机的数字。$RANDOM是会生成随机数的变量。 与总人数取余,就可以得出从0到总人数减1之间的随机数。 echo "${student[$random]}" #显示数组中对应的随机索引的学生。 student[$random]=${student[$modu-1]} #把已经显示过的学生直接用最后一个学生覆盖。 let modu-- #总人数减一。 done
最后两句再解释一下:比如上面是要挑出4个学生,也就要执行四次循环,结果很有可能会在4次循环中显示出相同的学生。 而要让显示过的学生下次循环肯定不会再显示,我这里就是用最后一个学生把显示过的学生覆盖,然后下次循环的时候,随机索引就不再包含最后一个学生。
执行结果:
star@sst-pp:~/script$ bash array1.sh zhangsan lisi Hua xiaoli
这种情况如果用每个变量保存一个学生名称就有点费劲了,而且也也太不灵活了。在数组里面可以直接在后面加学生,并把总人数改成对应值即可。
例:有时候在网上下载了一个软件,在系统上计算出效验码以后与软件所提供的做对比,费眼啊。
#!/bin/bash # describe: compare file md5/sha1 and appoint md5/sha1 same. # default -c sha1 # exit 1 par error # exit 3 -k error # exit 4 can't find md5sum or sha1sum # exit 5 can't find -f file [ $# -lt 2 ] && echo "apr too little" && echo "Usage:`basename $0` [-c md5|sha1] -k key_code -f path_file" && exit #取参数 while [ $# -ge 2 ];do case $1 in -c) soft=$2 shift 2 ;; -k) key=$2 shift 2 ;; -f) file=$2 shift 2 ;; *) echo "Usage:`basename $0` [-c md5|sha1] -k key_code -f path_file" exit 1 esac done soft=${soft:-sha1} case $soft in md5) soft=md5sum ;; sha1) soft=sha1sum ;; *) echo "-c md5|sha1" exit 3 esac [ ! which $soft &> /dev/null ] && echo "Can't find software $soft command" && exit 4 [ ! -e "$file" ] && echo "Can't find file $file" && exit 5 #取出变量中字符的长度 key_len=`echo ${#key}` #把-k带入变量中的字符依次赋值给数组。其实可以直接用变量中的字符依次比较,而不用数组。 for i in `seq 0 $[$key_len-1]`;do key_arr[$i]=${key:$i:1} done #计算文件的校验码. file_key=`$soft $file | cut -d' ' -f1` file_key_len=`echo ${#file_key}` #把文件的校验码依次赋值给数组。其实可以直接用变量中的字符依次比较,而不用数组。 for i in `seq 0 $[$file_key_len-1]`;do file_key_arr[$i]=${file_key:$i:1} done [ $file_key_len -ge $key_len ] && count=$file_key_len || count=$key_len #确定输出颜色,并先打印出-k带来的校验码。 for i in `seq 0 $[$count-1]`;do if [ "${key_arr[$i]}" == "${file_key_arr[$i]}" ];then color[$i]=2 else color[$i]=1 fi echo -n -e "\033[3${color[$i]}m${key_arr[$i]}\033[0m" done echo for i in `seq 0 $[$count-1]`;do echo -n -e "\033[3${color[$i]}m${file_key_arr[$i]}\033[0m" done echo
执行结果:
这里用到了shift。
位置参数轮替(shift)
轮替的意思就是 所有位置参数向后移,连$#也是如此,
轮替完以后 位置参数相当于就不再计算前面的参数了。把前面的忽略了。
shift 默认为1个位置轮替,
可以指定 如: shift 2
echo颜色与相关:
echo -e 用来支持控制字符 如, \n 换行 \b 退格 \t 制表符
还可以控制特殊的输出方式。
"\0NNN[##m数据\0NNN[0m"
最后的\033[0m 是用来取消属性的。 不然这个属性会应用到echo以外去。
里面的##
第一个是表示颜色前景或是背景,或是字体格式,如闪烁。
如果第一个是表示的颜色的话,第二个就是具体什么颜色。
如: echo -e "\033[32mHello\033[0m"
如果只有一个#号位:
[5 是跳跃
[7 是前景背景调换
[1 是加粗
[4 是加下划线
如果有两个#号:
[3 是前景色
[4 是背景色
第二个#号:
1红,2绿,3黄,4蓝,5紫,6天蓝,7灰。
如果有多个参数要用,可以用 ; 隔开。
echo -e "\033[32;5;1mHello\033[0m"
前景绿色;跳跃;加粗
reset 可以重启终端, 不小心把参数用到外面来了,用这个恢复默认。
其它的可以控制颜色的命令也可以恢复,如再颜色选项的ls。
echo -n 不换行。
例:实现冒泡算法。随机生成几个数。从小到大排序。
#!/bin/bash #description: # total_num=8 index_num=$[$total_num-1] soft_num=$index_num liushi=0 for i in `seq 0 $index_num`;do num_arr[$i]=`echo $RANDOM` done while [ $soft_num -ne 0 ];do for i in `seq 0 $[$soft_num-1]`;do if [ ${num_arr[$i]} -gt ${num_arr[$i+1]} ];then linshi=${num_arr[$i+1]} num_arr[$i+1]=${num_arr[$i]} num_arr[$i]=$linshi fi done ((soft_num--)) done for i in `seq 0 $index_num`;do echo ${num_arr[$i]} done
结果:
star@sst-pp:~$ bash ooooo.sh 467 2321 2831 7015 9663 15704 29946 31867 star@sst-pp:~$
最外层的while循环是控制整体循环次数,里层的for是负责把大的数字移到数组后边的索引位置。而循环次数不用每次都一样,因为最大的数已经在最上边了,很明显它也不用再移动了。
七、函数:
函数:function
过程式编程:代码重用
注意:定义函数的代码段不会自动执行,在调用时执行;所谓调用函数,在代码中给定函数名即可;函数名出现的任何位置,在代码执行时,都会被自动替换为函数代码;
函数名也是指向内存的地址,只不过这个地址存储的是代码。
语法:两种格式
FuncName(){
函数体
}
函数名和括号之间,括号和大括号之间有没有空格都可以。
function FuncName {
函数体
}
函数可以接受参数:
传递参数给函数:
在函数体中当中,可以使用$1,$2, ...引用传递给函数的参数;还可以函数中使用$*或$@引用所有参数,$#引用传递的参数的个数;
在调用函数时,在函数名后面以空白符分隔给定参数列表即可,例如,testfunc arg1 arg2 arg3 ...
函数返回值:
函数的执行结果返回值:
(1) 使用echo或printf命令进行输出;
(2) 函数体中调用的命令的执行结果;
函数的退出状态码:
(1) 默认取决于函数体中执行的最后一条命令的退出状态码;
(2) 自定义:return
return # 指定状态返回值,退出函数。
跟exit # 功能类似,只不过是退出函数而已。
如果在函数中用exit 也是直接退出脚本。
函数变量作用域:
所有的变量默认情况下都是属于主程序的,并且都是字符型的,虽然没有声明过,虽然没有赋值。这个是因为bash的原因。这种的变量都是本地变量。会向下传递变量,所以会把变量传递给函数,函数可以使用也可以修改。这样的情况下,在函数中给同一个变量赋值会修改变量在主程序中的值,所以在函数中最好使用local来指定局部变量。
local 来指定是局部变量。把变量名和数据放到自己的内存空间存放,不把变量和数据放到主程序所拥有的内存空间。这样不管变量在主程序中有没有值,都是不会变的。
在函数中声明的本地变量,在主程序中不可以使用,因为在函数中声明的变量,是属于这个函数的,只可以向下传递,但是不可以向上把变量传递给主脚本, 可以向下传递给子函数。也不会修改主脚本变量的已有的值。只要声明这个变量,那么它们的所指定的内存空间地址就不一样。不能传递给子shell
如果只是在函数中给变量赋值,那么主程序可以使用这个变量。因为只要是没有指定在函数中声明,这个变量就是属于被主程序声明过的, 而且还是字符型的。谁声明变量,变量就在谁的内存空间中,就算是都声明了同样的变量,那也是会在各自的内存空间,互不干拢。
环境变量只有跨shell才会失效,子shell有效。所以在脚本中声明的环境变量在脚本之外就不能用了
位置变量。 位置变量不属于脚本, 只是简单的给脚本传递参数,所以,脚本的位置变量,在函数中是不可以使用的。不过函数也是有位置参数的,只不过是脚本调用函数的时候指定传递给函数的。
如: functionName Parameters1 Parameters2 ....
并且可以间接的把脚本的位置参数传递给函数。
functionName $1 $2
functionName $*
把脚本的位置变量的数据传递给函数
注意上边是把脚本位置变量的数据,而不是把位置变量直接传递
例:
#!/bin/bash test () { echo $1 $2 echo $* echo $# } test a b c d e
结果:
star@sst-pp:/tmp$ bash fun.sh a b a b c d e 5 star@sst-pp:/tmp$
例:n*n表带上颜色。指定从几乘到几,并且加上颜色。
#!/bin/bash # first=$2 first=${first:-0} #这个函数就是为了生成一个1-7之间随机的返回值,让后面的echo来使用。从而也就有了随机的颜色了。 color () { col=0 while [ $col -eq 0 ];do col=$[$RANDOM%8] done return $col } #need multiply number(second number) nxn () { number=$1 for i in $(seq $first $number);do color echo -e -n "\033[3${?}m${i}x${number}=$[$i*$number]\033[0m\t" done } for i in $(seq $first $1);do nxn $i echo done co=$[$1-1] while [ $co -ge $first ];do nxn $co echo ((co--)) done
结果:
前一个参数是乘到几结束,后面的参数是从几开始。
例:ping 172.16.网段*.1的主机是否在线。
#!/bin/bash # # trap "echo 'quit';exit" INT #信号捕捉,不然的话,我们的ctrl+c的信号很可能是关不了脚本的。 address="172.16" up_count=0 down_count=0 opration () { #这个函数只是负责接受IP地址,然后PING,再根据状态用不同的颜色显示出来。 ping -W 1 -c 1 $1 &> /dev/null && state=0 || state=1 if [ $state -eq 0 ];then echo -e "\033[32m$1\tis up\033[0m" ((up_count++)) else echo -e "\033[31m$1\tis down\033[0m" ((down_count++)) fi } for i in {0..255};do opration "$address.$i.1" done opration "$address.67.1" echo "$up_count is up" echo "$down_count is down"
结果:
信号捕捉:只能在脚本的最前面。
列出信号:
trap -l
kill -l
trap 'COMMAND' SIGNALS
常可以进行捕捉的信号:HUP, INT
man 7 signal 查看信号帮助。
不能捕捉kill(9) 和 term(15) 信号。
trap在脚本开始就开始运行,等待信号。现在一些信号由trap来处理,也就不会再发生关不掉脚本的情况了。如像上面的脚本,如果没有trap的话,关闭信号每可能会被ping捕获。
捕捉到信号以后 执行trap定义的操作。
复制命令的脚本,在做小linux系统的时间可能要复制命令之类的,对应的库当然也要复制啦:
默认复制到/mnt/sysroot
#!/bin/bash # Author: xingxing # Version: 0.1 # Email: # Description: Move software(include library) # soft is software name # outPath is base output dirctory # softPath is software dirctory # Target root(/) dir. Target desk / partition have to mount here. outPath=/mnt/sysroot PATH=/bin:/sbin:/usr/bin:/usr/sbin createDir () { dirname=`dirname $1` if ! [ -d "$outPath$dirname" ];then mkdir -p $outPath$dirname fi } moveSoft () { createDir $softPath cp $softPath $outPath$softPath } moveLib () { ldd $softPath | while read linkData;do libPath=`echo $linkData | grep -o "/.*[[:space:]]"` [ -z "$libPath" ] && continue createDir $libPath cp $libPath $outPath$libPath done } while true;do read -p "Enter software name(quit): " soft if [ -z "$soft" ];then continue elif [ "$soft" == "quit" ];then break elif ! which $soft &> /dev/null;then if type $soft &> /dev/null;then echo -e "\033[35m$soft is build software\033[0m" else echo -e "\033[35m$$soft is not exist\033[0m" fi continue fi softPath=`\which $soft` if [ -e "$outPath$softPath" ];then echo -e "\033[35m$soft is already exist\033[0m" continue fi moveSoft retu1=$? moveLib retu2=$? [ $retu1 -eq 0 -a $retu2 -eq 0 ] && \ echo -e "\033[33m$soft move was success\033[0m" || \ echo -e "\033[35m$soft move was failed\033[0m" done
结果
就这样吧。自己现在也不熟练,想起来什么再来补充吧。希望能帮助到朋友们。