花了大概一天半的时间比较全面的接触了一下shell脚本,对于这种语言我对自己的要求很简单,不求深度精通,能写工具类脚本,能阅读别人的脚本,能在在相应场合无障碍沟通就好。这里整理一下这个阶段的知识结构,准备再花一到两天时间接触一下一些高级玩法,到时候Shell就算完篇了。
Shell 也是一门编程语言,学习编程语言我觉得需要关注这么一些点:
只要相应的关注整理一下,入门还是很快的,我相信读完这篇文章以后,你基本上可以认为自己能独立读写一些简单的Shell脚本,可以有底气的说自己是一个初级的Sheller了。
变量声明 首先要了解一下Shell的变量声明,引用等语法规则。有两个command(declare,local
)与变量的声明有关:
1 2 3 declare a=1 #声明一个全局变量 local b="hello~~" #声明一个局部变量,一般在函数体中使用 c=1 #实际情况是,这两个command可用可不用,不使用同样可以声明变量
变量的声明特别要注意赋值等号(=)两边不能有空格。
变量声明了以后还有一个变量读取
,Shell读取变量的语法如下:
1 ${var} 或者 $var #作为一个资深Coder,当然知道前者更专业,可以避免在字符串中解析出错
数据类型 shell 不像那些严谨的编程语言有完整的数据体系,我现在接触到的就这么几种:
字符串
数值(整形/浮点)
数组(简单数组/关联数组)
字符串 这个是最常用也是最简单的数据类型了,首先来感受一下
1 2 3 echo "hello,shell~~" #输出字符串到控制台 echo "hello, shell " > a.txt #将字符串输入到文件a.txt中,输入之前会清空文件 echo "hello,shell" >> a.txt #将字符串输入到文件a.txt中,输入之前不清空文件
和其他的编程语言一样,字符串也有相应的一些操作,整理下来基本上就是这么一些:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 取字符串长度 : ${#string} 子串截取: ${string:position:length} 从起始位置截取指定长度 ${string:position} 从起始位置截取到末尾 子串替换: ${string/substring/replacement} 替换第一个匹配的子串 ${string//substring/replacement} 替换所有子串 子串删除 ${string%substring} 从变量$string的结尾, 删除最短匹配$substring的子串 ${string%%substring} 从变量$string的结尾, 删除最长匹配$substring的子串 .....
数值 数值分为整形和浮点型,他们的操作完全不一样,所以得分开说。 数值类型的话关注点就在运算了,Shell对于整形的运算有三种方式,如下: ** let **
1 2 3 4 a=1; b=2; let c=a+b; echo $c;
** [ ] 中括号**
1 2 3 4 a=1; b=2; c=$[a+b]; echo $c;
** (( )) 双小括号 **
1 2 3 4 a=1; b=2; c=$(( $a+$b)); echo $c;
*这里总结一下, 这三种方式还是有一些细微的差别的,let 方式最为简单。[ ]需要注意在外面要用$对返回值进行引用。 (( ))最为复杂容易出错,首先在括号内变量的必须要加$引用,其次左括号必须和变量间有空格。 *
bash 不支持浮点运算,如果需要进行浮点运算,需要借助bc,awk
处理。
1 2 c=$(echo "5.01-4*2.0"|bc) c=$(awk 'BEGIN{print 7.01*5-4.01 }')
bc,awk
我暂时也还没有接触到,先放到这吧。todo
数组 数组的定义(初始化)比较简单:
1 2 3 4 5 arr=(0 1 2 3 4 5 6 7 8 9); 或者 arr[0]=0; arr[1]=1; arr[2]=2;
下面就是关于数组的一些操作了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #循环打印数组元素 for i in ${arr[@]} ; do echo $arr[i] done; #这里牵涉到了两个知识点: #(1)${arr[@]} 表示数组中的所有元素 #(1)$arr[i] 获取数组元素的形式,其中i为索引 #顺便说了for结构,对于for结构也可以这样: for i in 1 2 3 4 5 ;do echo $i done; # 还可以这样: for((i=0;i<=10;i++));do echo $i; done; #回到数组,获取数组的长度: ${#arr[@]}
数组的基本操作就是这些了,这里所说的数组都是简单数组,它的索引只能是数值,关联数组的不同之处在于索引可以是其他类型如字符串。需要bash4.0以上才支持
1 2 declare -A phone phone=([jim]=135 [tom]=136 [lucy]=158)
数据类型基本上就是这些了,接下来看看一些常见的结构控制及语法。我们都知道程序的结构有三种:
就从这个结构来入手语法吧。
语法 顺序结构就没什么好说的了,重点是后面两种。
选择结构 选择结构说白了就是会存在逻辑判断,Shell里面也提供两种大结构来支持判断。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 # ------数字比较-------- if test 100 -gt 50 ; then ehco " 100 大于50"; else ehco "100 小于50" fi # ------字符串比较-------- if test a \> b ; then ehco " a 大于b"; else ehco "a 小于b" fi #------case 子句---------- case "$Keypress" in [[:lower:]] ) echo "Lowercase letter";; [[:upper:]] ) echo "Uppercase letter";; [0-9] ) echo "Digit";; * ) echo "Punctuation, whitespace, or other";; esac
上面只是简单的示例,现在来说说具体的语法。对于if
结构,后面的比较操作
有三种写法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 # 1. test if test 100 -gt 50 ; then ehco " 100 大于50"; else ehco "100 小于50" fi # 2. [ ]中括号 if [ a \> a ] then echo "a>a" else echo "a<a" fi; # 3. [[ ]] 双中括号 if [[ a == a && 2 > 3 ]] then echo " all true"; else echo " not all true"; fi;
总结一下,这三种写法中 test, [ ] 最为复杂,对于<,>
等操作符必须要要转义\>, \<
,而且不支持多条件判断,所以一般情况下就用双中括号[[ ]]来得简单。
shell中提供的支持判断的操作符比较别扭,分别正对字符串,数值比较各有一套:字符串比较 :
1 2 3 4 5 6 > , >= : 大于,大于等于 <, <= : 小于,小于等于 == : 等于 != : 不等于 -z : 字符串为空 -n : 字符串非空
数值比较 :
1 2 3 4 5 6 -eq 等于 -ne 不等于 -lt 小于 -le 小于等于 -gt 大于 -ge 大于或等于
文件比较
1 2 3 -e 文件是否存在 -f 是一个标准文件 -d 是一个目录
个人感觉这个定义好别扭,字符串和数值完全没有必要弄两套比较体系,而且数值比较很直观的东西变得很复杂。接下来,最蛋痛的事情到了,上面说到if
后面的写法有三种:test , [] , [[ ]]
。其中test, []
是严格按照比较操作符的定义来的,如果是字符串比较,只能用符号比较>,<
等,如果是数值比较,只能用文字比较符 -gt ,-lt
等,但是对于[[ ]] 无论是文字比较还是数值比较,两种比较符都可以任意使用 。所以一般建议只用[[]]
,你以为这就完了?不,还有一种情况,三目运算:
1 2 echo $(( 3 > 2?100:999)); echo $(( a > b?101:991));
三目运算表达式只能用符号比较符 > ,< 等
!!! 在这一段的学习过程中我是感觉非常混乱,绕了好久才把这些点给理清楚,现在统一总结一下:
test , [] , [[ ]]
三种结构中,test ,[]
需要对<,>
等转义。而且两种比较符各司其职,不能跨界操作。而 [[]]
不需要转义,比较符号也可以跨界操作,非常方便。所以一般就只用[[]]
.
三目运算表达式中的比较只能用符号比较符 >, <
.
Case子句的写法就没这么纠结了,当然在我看来,语法还是很蛋痛的。来记录一下它的语法规则:
1 2 3 4 5 6 case "$Keypress" in [[:lower:]] ) echo "Lowercase letter";; [[:upper:]] ) echo "Uppercase letter";; [0-9] ) echo "Digit";; * ) echo "Punctuation, whitespace, or other";; esac
条件判断分支必须用右括号 )
结束。
条件子句必须用两个分号;;
结束。
循环结构 Shell的循环结构比较简单,就是for, while。都来看看吧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #1. for 指定数组 for loop in 1 2 3 4 5 6 do echo $loop done #2. for 变量自增长 for i in `seq 100` do echo $i; done #3. for 循环已定义数组 arr=(1 2 3 4 5 6 7 8 9 0) for i in ${arr[@]} ;do echo $i; done; #4. 指定范围 for i in {1..10} ;do echo $i; done; #45. while (( )) COUNTER=0 while(($COUNTER<=5)) do echo $COUNTER let "COUNTER++"; done #5. while [[ ]] while [[ $num != 4 ]] do echo $num; let "num++"; done;
循环结构远没有选择结构那么复杂,基本上看看这些示例就OK了。
函数 语法部分还有一个比较重要的方面就是函数了。函数的定义和其他语言一样:
1 2 3 4 5 6 7 8 9 function myfun(){ echo "共传入了:$# 个参数,分别是:" for i in $*; do echo $i done } # 1. $# 参数个数 # 2. $* 所有参数的数值 #3. function 可写可不写
Shell似乎不支持带参函数,所以如何在函数内获取参数呢?
1 2 3 4 5 6 function myfun(){ echo ${1}; echo ${2}; echo ${3}; } #按照索引来获取参数就好了。注意,索引从1开始,${0}获取的是脚本名。
函数的执行也和其它程序不一样,来看一个例子:
1 2 3 4 5 6 7 8 sum=0 function myadd(){ for i in $*; do let sum+=$i done } myadd 1 2 3 4 # 执行函数 echo $sum;
如果函数有返回值,则可以直接赋值:
1 2 3 4 5 6 7 8 function myadd(){ for i in $*; do let sum+=$i done return $sum; } $(myadd 1 2 3 4) # 执行函数 num=$? #获取返回值
到此为止,基本上语法就都走了一遍,接下来要了解一些shell内置的一些变量,命令等,这个是真正能玩转起来的重要因素,看看有哪些东西是需要关注的吧。
内置变量及命令 首先来了解一下一些特殊的内置变量
1 2 3 4 5 6 7 8 9 10 11 12 13 $0 脚 本名字 $1- $9 位置参数 #1 - #9 ${10} 位置参数 #10 $# 位置参数的个数 "$*" 所有的位置参数(作为单个字符串) * "$@" 所有的位置参数(每个都作为独立的字符串) ${#*} 传递到脚本中的命令行参数的个数 ${#@} 传递到脚本中的命令行参数的个数 $? 返回值 $$ 脚本的进程ID(PID) $- 传递到脚本中的标志(使用set) $_ 之前命令的最后一个参数 $! 运行在后台的最后一个作业的进程ID(PID)
对于初次接触者,可能这么几个要重点关注下
1 2 3 4 5 6 7 8 9 10 11 12 # 1. 位置参数,在上面的示例中已经用过,函数内部使用传递参数的写法 $1 - $9 位置参数 #1 - #9 ${10} 位置参数 #10 # 2. 参数的数量 $# 位置参数的个数 #3. 参数的集合,在一些不关心具体参数值的函数中特别有用,例如 add $@ 所有的位置参数(每个都作为独立的字符串) #4. 函数的返回值, 不能直接将函数执行后复制给变量,必须通过这个返回值变量 $? 返回值
命令的话太多了,这里就不纠结这个话题,通篇下来,shell的初步接触应该比较完备了,后续再研究下高端玩法。