Shell 编程详解
部分引用自:https://blog.csdn.net/qq_22075977/article/details/75209149
一、概述
Shell是一种具备特殊功能的程序,它提供了用户与内核进行交互操作的一种接口。它接收用户输入的命令,并把它送入内核去执行。内核是Linux系统的心脏,从开机自检就驻留在计算机的内存中,直到计算机关闭为止,而用户的应用程序存储在计算机的硬盘上,仅当需要时才被调入内存。Shell是一种应用程序,当用户登录Linux系统时,Shell就会被调入内存去执行。Shell独立于内核,它是连接内核和应用程序的桥梁,并由输入设备读取命令,再将其转为计算机可以理解的机械码,Linux内核才能执行该命令。
二、优势
Shell脚本语言的好处是简单、易学、易用,适合处理文件和目录之类的对象,以简单的方式快速完成某些复杂的事情通常是创建脚本的重要原则,脚本语言的特性可以总结为以下几个方面:
1、语法和结构通常比较简单。
2、学习和使用通常比较简单,
3、通常以容易修改程序的“解释”作为运行方式,而不需要“编译。
4、程序的开发产能优于运行效能。
5、Shell脚本语言是Linux/Unix系统上一种重要的脚本语言,在Linux/Unix领域应用极为广泛,熟练掌握Shell脚本语言是一个优秀的Linux/Unix开发者和系统管理员必经之路。利用Shell脚本语言可以简洁地实现复杂的操作,而且Shell脚本程序往往可以在不同版本的Linux/Unix系统上通用。
三、变量
1、用户自定义变量
这种变量只支持字符串类型,不支持其他字符,浮点等类型,常见有这 3 个前缀:
a、unset:删除变量
b、readonly:标记只读变量
c、export:指定全局变量
#!/bin/bash echo "定义普通变量" CITY=CHENGDU echo "定义全局变量" export NAME=cdeveloper echo "定义只读变量" readonly AGE=21 echo "打印变量的值" echo $CITY echo $NAME echo $AGE echo "删除 CITY 变量" unset CITY # 不会输出 CHENGDU echo $CITY
运行结果:
定义普通变量 定义全局变量 定义只读变量 打印变量的值 CHENGDU cdeveloper 21 删除 CITY 变量
2、预定义变量
预定义变量常用来获取命令行的输入,有下面这些:
a、$0 :脚本文件名
b、$1-9 :第 1-9 个命令行参数名
c、$# :命令行参数个数
d、$@ :所有命令行参数
e、$* :所有命令行参数
f、$? :前一个命令的退出状态,可用于获取函数返回值
g、$$ :执行的进程 ID
编辑脚本1.sh
#!/bin/bash echo "print $" echo "1、打印脚本文件名$0" echo -e "\$0 = $0 \n" echo "2、打印第一个命令行参数$1" echo -e "\$1 = $1\n" echo "3、打印第二个命令行参数$2" echo -e "\$2 = $2\n" echo "4、打印命令行参数个数$#" echo -e "\$# = $#\n" echo "5、打印所有命令行参数$@" echo -e "\$@ = $@\n" echo "6、打印所有命令行参数" echo -e "\$* = $*\n" echo "7、打印前一个命令的退出状态" echo -e "\$? = $?\n" echo "8、打印执行的进程ID" echo "\$\$ = $$"
执行结果:
print $ 1、打印脚本文件名1.sh $0 = 1.sh 2、打印第一个命令行参数1 $1 = 1 3、打印第二个命令行参数2 $2 = 2 4、打印命令行参数个数4 $# = 4 5、打印所有命令行参数1 2 3 4 $@ = 1 2 3 4 6、打印所有命令行参数 $* = 1 2 3 4 7、打印前一个命令的退出状态 $? = 0 8、打印执行的进程ID $$ = 30839
3、环境变量
环境变量默认就存在,常用的有下面这几个:
a、HOME:用户主目录
b、PATH:系统环境变量 PATH
c、TERM:当前终端
d、UID:当前用户 ID
e、PWD:当前工作目录,绝对路径
编辑1.sh
#!/bin/bash echo -e "print env\n" echo "家目录" echo -e "\$HOME=$HOME\n" echo "环境变量" echo -e "\$PATH=$PATH\n" echo "当前终端" echo -e "\$TERM=$TERM\n" echo "当前工作目录" echo -e "\$PWD=$PWD\n" echo "当前用户ID" echo "\$UID=$UID"
执行结果
print env 家目录 $HOME=/root 环境变量 $PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/application/jdk1.8.0_181/jre/bin:/application/jdk1.8.0_181/bin:/usr/local/rsync/bin:/application/sersync/bin:/root/bin 当前终端 $TERM=xterm 当前工作目录 $PWD=/script 当前用户ID $UID=0
四、shell 运算
1、我们经常需要在 Shell 脚本中计算,掌握基本的运算方法很有必要,下面就是 4 种比较常见的运算方法,功能都是将 m + 1:
a、m=$[ m + 1 ]
b、m=expr $m + 1 # 用 “ 字符包起来
c、let m=m+1
d、m=$(( m + 1 ))
e、实例,编辑1.sh
#!/bin/bash m=1 m=$[ m + 1 ] echo $m m=`expr $m + 1` echo $m # 注意:+ 号左右不要加空格 let m=m+1 echo $m m=$(( m + 1 )) echo $m
执行结果:
2 3 4 5
2、文件测试
a、-d:测试是否为目录(Directory)
b、-e:测试目录或文件是否存在(Exist)
c、-f:测试是否为文件(File)
d、-r:测试当前用户是否有权限读取(Read)
e、-w:测试当前用户是否有权限写入(Write)
f、-x:测试当前用户是否有权限执行(eXcute)
g、实例
[root@wohaoshuai1 script]# [ -d /media/cdrom ] && echo "YES" || echo "no" no
3、数值比较
a、-eq:等于
b、-ne:不等于
c、-gt:大于
d、-lt:小于
e、-le:小于等于
f、-ge:大于或等于
g、实例 编辑1.sh
#!/bin/bash [ $(who | wc -l) -eq 2 ] && echo "等于2" [ $(who | wc -l) -ne 2 ] && echo "不等于2" [ $(who | wc -l) -gt 2 ] && echo "大于2" [ $(who | wc -l) -lt 2 ] && echo "小于2" [ $(who | wc -l) -ge 2 ] && echo "大于或等于2" [ $(who | wc -l) -le 2 ] && echo "小于或等于2"
4、字符串比较
a、= :字符串内容相同
b、!=:字符串内容不同,!表示相反的意思
c、-z:字符串内容为空
d、-n:字符串内容不为空
[root@wohaoshuai1 script]# echo $a 1 [root@wohaoshuai1 script]# [ $a = "b" ] && echo "a等于b" || echo "a不等于b" a不等于b [root@wohaoshuai1 script]# [ $a != "b" ] && echo "a不等于b" || echo "a等于b" a不等于b [root@wohaoshuai1 script]# [ -z $a ] && echo "a为空" || echo "a不为空" a不为空 [root@wohaoshuai1 script]# [ -n $a ] && echo "a不为空" || echo "a为空" a不为空
5、逻辑测试
a、-a 或 && : 逻辑与,“而且” 的意思
b、-o 或 || : 逻辑或,“或者” 的意思
c、! :逻辑否
6、[] 与 [[]] 区别
a、[[]] 用 "&&" 而不是"-a" 表示逻辑与,用"||"而不是"-o"表示逻辑或。
[root@wohaoshuai1 ~]# [[ 1 < 2 && b > a ]] && echo "true" || echo "false" true [root@wohaoshuai1 ~]# [[ 1 < 2 -a b > a ]] && echo "true" || echo "false" -bash: syntax error in conditional expression -bash: syntax error near `-a'
b、[ ... ]为shell命令,所以在其中的表达式应是它的命令行参数,所以串比较操作符”>” 与”<”必须转义,否则就变成IO改向操作符了。[[中"<"与">"不需转义:
[root@wohaoshuai1 ~]# [ 1 \< 2 -a b \> a ] && echo "true" || echo "false" true
c、[[ ... ]]进行算术扩展,而[ ... ]不做
[root@wohaoshuai1 ~]# [[ 99+1 -eq 100 ]] && echo "true" || echo "false" true [root@wohaoshuai1 ~]# [ 99+1 -eq 100 ] && echo "true" || echo "false" -bash: [: 99+1: integer expression expected false [root@wohaoshuai1 ~]# [ $((99+1)) -eq 100 ] && echo "true" || echo "false" true
d、[[]]能用正则,而[]不行,并且[[]]在正则匹配中不能使用双引号。
[root@wohaoshuai1 ~]# [[ "test.php" = *.php ]] && echo "true" || echo "false" true [root@wohaoshuai1 ~]# [ "test.php" = *.php ] && echo "true" || echo "false" false [root@wohaoshuai1 ~]# [[ "test.php" = "*.php" ]] && echo "true" || echo "false" false
五、shell 语句
1、if 语句
a、这个跟高级语言的 if - else - if
类似,只是格式有些不同而已。
#!/bin/bash read -p "请输入整数:" VART if [ $VART -eq 10 ];then echo "true" elif [ $VART -gt 10 ];then echo "大于10" else echo "false" fi
执行结果:
[root@wohaoshuai1 script]# sh 1.sh 请输入整数:1 false [root@wohaoshuai1 script]# sh 1.sh 请输入整数:10 true [root@wohaoshuai1 script]# sh 1.sh 请输入整数:100 大于10
2、case语句,case 语句有些复杂,要注意格式
#!/bin/bash read -p "请输入一个字符,并按enter结束:" key case "$key" in [./]|[a-z]|[A-Z]) echo "你输入的是字母" ;; [0-9]) echo "你输入的是数字" ;; *) echo "你输入的是空格,功能键或其他控制字符" esac
执行结果:
请输入一个字符,并按enter结束:a 你输入的是字母 [root@wohaoshuai1 script]# sh 1.sh 请输入一个字符,并按enter结束:. 你输入的是字母 [root@wohaoshuai1 script]# sh 1.sh 请输入一个字符,并按enter结束:/ 你输入的是字母
3、for循环
#!/bin/bash # 普通 for 循环 echo "普通for循环如下:" for ((i = 1; i <= 3; i++)) do echo $i done echo -e "\n" # VAR 依次代表每个元素 echo "第二种for循环:" for VAR in `seq 1 10` do echo $VAR done
执行结果:
普通for循环如下: 1 2 3 第二种for循环: 1 2 3 4 5 6 7 8 9 10
4、while循环
#!/bin/bash VAR=1 # 如果 VAR 小于 10,就打印出来 while [ $VAR -lt 10 ] do echo $VAR # VAR 自增 1 VAR=$[ $VAR + 1 ] done
执行结果:
1 2 3 4 5 6 7 8 9
5、until循环,until 语句与while循环的不同点是它的结束条件为 1
#!/bin/bash i=0 # i 大于 5 时,循环结束 until [[ "$i" -gt 5 ]] do echo $i i=$[ $i + 1 ] done
运行结果:
0 1 2 3 4 5
6、break,Shell 中的 break
用法与高级语言相同,都是跳出循环。
#!/bin/bash for VAR in `seq 1 3` do # 如何 VAR 等于 2 就跳出循环 if [ $VAR -eq 2 ];then break fi echo $VAR done
执行结果:
[root@wohaoshuai1 script]# sh 1.sh 1
7、continue,用来跳过本次循环,进入下一次循环
#!/bin/bash for VAR in 1 2 3 do # 如果 VAR 等于 2,就跳过,直接进入下一次 VAR = 3 的循环 if [ $VAR -eq 2 ];then continue fi echo $VAR done
执行结果:
[root@wohaoshuai1 script]# sh 1.sh 1 3
六、shell函数
1、定义函数,有两种常见的格式
#!/bin/bash function hello_world() { echo "hello world fun" echo $1 $2 return 1 } hello() { echo "hello fun" }
2、调用函数
#!/bin/bash function hello_world() { echo "hello world fun" echo $1 $2 return 1 } hello() { echo "hello fun" } # 1. 直接用函数名调用 hello 函数 echo "1. 直接用函数名调用 hello 函数执行结果:" hello echo -e "\n" # 2. 使用「函数名 函数参数」来传递参数 echo "2. 使用「函数名 函数参数」来传递参数执行结果:" hello_world 1 2 echo -e "\n" # 3. 使用「FUN=`函数名 函数参数`」 来间接调用 echo "3. 使用「FUN='函数名 函数参数'」 来间接调用:" FUN=`hello_world 1 2` echo -e $FUN
执行结果:
[root@wohaoshuai1 script]# sh 1.sh 1. 直接用函数名调用 hello 函数执行结果: hello fun 2. 使用「函数名 函数参数」来传递参数执行结果: hello world fun 1 2 3. 使用「FUN='函数名 函数参数'」 来间接调用: hello world fun 1 2
3、获取返回值,使用 $?获取返回值
#!/bin/bash function hello_world() { echo "hello world fun" echo $1 $2 return 2 } hello() { echo "hello fun" } # 1. 直接用函数名调用 hello 函数 echo "1. 直接用函数名调用 hello 函数执行结果:" hello echo -e "\n" # 2. 使用「函数名 函数参数」来传递参数 echo "2. 使用「函数名 函数参数」来传递参数执行结果:" hello_world 1 2 echo "返回值为:$?" echo -e "\n" # 3. 使用「FUN=`函数名 函数参数`」 来间接调用 echo "3. 使用「FUN='函数名 函数参数'」 来间接调用:" FUN=`hello_world 1 2` echo -e $FUN
执行结果:
[root@wohaoshuai1 script]# sh 1.sh 1. 直接用函数名调用 hello 函数执行结果: hello fun 2. 使用「函数名 函数参数」来传递参数执行结果: hello world fun 1 2 返回值为:2 3. 使用「FUN='函数名 函数参数'」 来间接调用: hello world fun 1 2
4、定义本地变量
fun() { local x=1 echo $x }
七、Shell 调试
1、使用下面的命令来检查是否有语法错误
sh -n 1.sh
2、使用下面的命令来执行并调试 Shell 脚本
sh -x 1.sh
3、实例
#!/bin/bash for VAR in 1 2 3 do if [ $VAR -eq 2 ];then continue fi echo $VAR done
执行结果:
[root@wohaoshuai1 script]# sh -x 1.sh + for VAR in 1 2 3 + '[' 1 -eq 2 ']' + echo 1 1 + for VAR in 1 2 3 + '[' 2 -eq 2 ']' + continue + for VAR in 1 2 3 + '[' 3 -eq 2 ']' + echo 3 3
其中带有 +
表示的是 Shell
调试器的输出,不带 +
表示我们程序的输出。
八、Shell数组
1、数组分为两种类型,一是数值类型,二是字符串类型。
a、数值类型的数组:一对括号表示数组,数组中元素之间使用“空格”来隔开。
arr_number=(1 2 3 4 5)
b、字符串类型数组,同样,使用一对括号表示数组,其中数组中的元素使用双引号或者单引号包含,同样使用“空格”来隔开。
arr_string=("abc" "edf" "sss"); 或者 arr_string=('abc' 'edf' 'sss');
2、数组的操作
a、获取数组长度,arr_length=${#arr_number[*]}或${#arr_number[@]}均可,即形式:${#数组名[@/*]} 可得到数组的长度。
[root@wohaoshuai1 script]# a=(1 2 3 4 5) [root@wohaoshuai1 script]# echo "${#a[*]}" 5 [root@wohaoshuai1 script]# echo "${#a[@]}" 5
b、读取某个下标的值,arr_index2=${arr_number[2]},即形式:${数组名[下标]}
[root@wohaoshuai1 script]# a=(1 2 3 4 5) [root@wohaoshuai1 script]# echo "${a[2]}" 3
c、对某个下标赋值。
1)、若下标元素已经存在那么会修改该下标的值为新的指定值。
[root@wohaoshuai1 script]# a=(1 2 3 4 5) [root@wohaoshuai1 script]# a[2]=100 [root@wohaoshuai1 script]# echo ${a[*]} 1 2 100 4 5 [root@wohaoshuai1 script]# echo ${a[@]} 1 2 100 4 5
2)、如果指定的下标已经超过当前数组的大小,如上述的a的大小为5,指定下标为10或者11或者大于5的任意值那么新赋的值会被追加到数组的尾部。
[root@wohaoshuai1 script]# a=(1 2 3 4 5) [root@wohaoshuai1 script]# a[2]=100 [root@wohaoshuai1 script]# echo ${a[*]} 1 2 100 4 5 [root@wohaoshuai1 script]# echo ${a[@]} 1 2 100 4 5 [root@wohaoshuai1 script]# a[10]=200 [root@wohaoshuai1 script]# echo ${a[@]} 1 2 100 4 5 200 [root@wohaoshuai1 script]# echo "${a[10]}" 200 [root@wohaoshuai1 script]# echo "${a[5]}"
d、删除操作
1)、清空某个元素
[root@wohaoshuai1 script]# a=(1 2 3 4 5) [root@wohaoshuai1 script]# unset a[1] [root@wohaoshuai1 script]# echo "${a[*]}" 1 3 4 5
2)、清空整个数组
[root@wohaoshuai1 script]# a=(1 2 3 4 5) [root@wohaoshuai1 script]# unset a [root@wohaoshuai1 script]# echo "${a[*]}"
e、分片访问,分片访问形式为:${数组名[@或*]:开始下标:结束下标},注意,不包括结束下标元素的值。
1)、从索引2开始访问,访问4个,如果个数不够默认会访问到最后一个
[root@wohaoshuai1 script]# a=(1 2 3 4 5) [root@wohaoshuai1 script]# echo ${a[@]:2:4} 3 4 5
f、模式替换,形式为:${数组名[@或*]/模式/新值}
[root@wohaoshuai1 script]# a=("aa","bb","cc","aa") [root@wohaoshuai1 script]# echo ${a[@]/aa/x} x,bb,cc,aa [root@wohaoshuai1 script]# echo ${a[@]//aa/x} x,bb,cc,x [root@wohaoshuai1 script]# echo ${a[@]} aa,bb,cc,aa
g、数组的遍历
#!/bin/bash b=(aa bb cc dd ee) for i in ${b[@]} do echo ${i} done
执行结果:
[root@wohaoshuai1 script]# sh 1.sh aa bb cc dd ee
九、Shell字符串处理
1、获取字符串长度
[root@wohaoshuai1 script]# str="abcdefg" [root@wohaoshuai1 script]# echo "${#str}" 7
2、对字符串进行大小写转换
a、将变量值中的小写字母转换为大写
[root@wohaoshuai1 script]# echo "${str}" abcdefg [root@wohaoshuai1 script]# echo "${str^^}" ABCDEFG
b、将变量中的大写字母转换为小写
[root@wohaoshuai1 script]# echo "${str}" ABCDEFG [root@wohaoshuai1 script]# echo "${str,,}" abcdefg
3、当变量值为空或非空时操作变量
a、${var:=value},意思为如果var为空则返回value,并将value赋给var,如果var不为空则返回var本身的值,var不为空时,var值不会被改变,var为空时,var的值会被设置成指定值。
#!/bin/bash a="wohaoshuai" b=${a:="123"} echo -e "b为:${b}\n" c=${d:="456"} echo -e "c为:${c}\n" echo -e "d为:${d}\n"
执行结果:
[root@wohaoshuai1 script]# sh 1.sh b为:wohaoshuai c为:456 d为:456
b、${var:-value},意思为如果var为空,则返回value,如果var不为空,则返回var的值,无论var是否为空,var本身的值不会改变。
#!/bin/bash a="wohaoshuai" b=${a:-"123"} echo -e "b为:${b}\n" c=${d:-"456"} echo -e "c为:${c}\n" echo -e "d为:${d}\n"
执行结果:
[root@wohaoshuai1 script]# sh 1.sh b为:wohaoshuai c为:456 d为:
c、${var:+value},意思为如果var不为空,则返回value,如果var为空,则返回空值,无论var是否为空var本身的值不会改变。
#!/bin/bash a="wohaoshuai" b=${a:+"123"} echo -e "b为:${b}\n" c=${d:+"456"} echo -e "c为:${c}\n" echo -e "d为:${d}\n"
执行结果为:
[root@wohaoshuai1 script]# sh 1.sh b为:123 c为: d为:
d、${var:?error_info},意思为如果var为空,那么在当前终端打印error_info,如果var的值不为空,则返回var的值,无论var是否为空,var本身的值都不会改变。
#!/bin/bash a="wohaoshuai" b=${a:?"123"} echo -e "b为:${b}\n" c=${d:? "err"} echo -e "c为:${c}\n" echo -e "d为:${d}\n"
执行结果:
[root@wohaoshuai1 script]# sh 1.sh b为:wohaoshuai 1.sh: line 7: d: err
4、从指定位置截取字符串,截取到字符串的末尾。
a、从正数第四个字符以后开始截取,直到字符串的末尾(从0开始计数)
[root@wohaoshuai1 script]# a="abcdefg" [root@wohaoshuai1 script]# echo ${a:4} efg
b、从倒数第四个字符开始截取,直到字符串的末尾。并且冒号与负号之间必须存在任意字符(通常使用0或空格占位,当然用其它字符也可以),否则无法起到截取字符串的作用。
[root@wohaoshuai1 script]# echo ${a:0-4} defg [root@wohaoshuai1 script]# echo ${a: -4} defg [root@wohaoshuai1 script]# echo ${a:b-4} defg
5、从指定位置截取字符串,并且截取指定的长度
a、从第四个字符以后开始截取,截取五个字符
[root@wohaoshuai1 script]# a="abcdefghijklmn" [root@wohaoshuai1 script]# echo ${a:4:5} efghi
b、从倒数第八个开始截取,截取5个字符
[root@wohaoshuai1 script]# echo ${a:0-8:5} ghijk [root@wohaoshuai1 script]# echo ${a:b-8:5} ghijk [root@wohaoshuai1 script]# echo ${a: -8:5} ghijk
c、Centos7中截取长度可以为负数,Centos6中不行,Centos7从正数第四个字符以后开始截取到字符串末尾,再将截取后的字符串的最后3个字符删除。
[root@wohaoshuai1 script]# echo ${a:4} efghijklmn [root@wohaoshuai1 script]# echo ${a:4:-3} efghijk
d、Centos7从倒数第四个字符开始截取,截取到字符串的末尾,再将截取后的字符串的最后一个字符删除。
[root@wohaoshuai1 script]# echo ${a:0-4} klmn [root@wohaoshuai1 script]# echo ${a:0-4:-1} klm
6、替换字符串操作
a、将变量值中第一个遇到的str1替换成str2
[root@wohaoshuai1 script]# a="www" [root@wohaoshuai1 script]# echo ${a/w/abc} abcww
b、将变量值中的所有遇到的str1替换成str2
[root@wohaoshuai1 script]# echo ${a//w/abc} abcabcabc