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