Shell本身是一个用C语言编写的程序,它是用户使用Unix/Linux的桥梁,用户的大部分工作都是通过Shell完成的。
Shell既是一种命令语言,又是一种程序设计语言:
作为命令语言,它交互式地解释和执行用户输入的命令;
作为程序设计语言,它定义了各种变量和参数,并提供了许多在高级语言中才具有的控制结构,包括循环和分支。
Shell它虽然不是Unix/Linux系统内核的一部分,但它调用了系统核心的大部分功能来执行程序、建立文件并以并行的方式协调各个程序的运行。Shell是用户与内核进行交互操作的一种接口,目前最流行的Shell称为bash Shell(Bourne Again Shell)
Shell是一门编程语言(解释型的编程语言),即shell脚本(就是在用linux的shell命令编程),Shell脚本程序从脚本中一行一行读取并执行这些命令,相当于一个用户把脚本中的命令一行一行敲到Shell提示符下执行
Shell是一种脚本语言,那么,就必须有解释器来执行这些脚本
Unix/Linux上常见的Shell脚本解释器有bash、sh、csh、ksh等,习惯上把它们称作一种Shell。我们常说有多少种Shell,其实说的是Shell脚本解释器,可以通过cat /etc/shells命令查看系统中安装的shell,不同的shell可能支持的命令语法是不相同的
sh是Unix 标准默认的shell,由Steve Bourne开发,是Bourne Shell的缩写。
bash是Linux标准默认的shell,本教程也基于bash讲解。bash由Brian Fox和Chet Ramey共同完成,是Bourne Again Shell的缩写。
Shell本身支持的命令并不多,内部命令一共有40个,但是它可以调用其他的程序,每个程序就是一个命令,这使得Shell命令的数量可以无限扩展,其结果就是Shell的功能非常强大,完全能够胜任Linux的日常管理工作,如文本或字符串检索、文件的查找或创建、大规模软件的自动部署、更改系统设置、监控服务器性能、发送报警邮件、抓取网页内容、压缩文件等。
shell脚本的代码写在普通文本文件中,通常以.sh结尾,虽然不是强制要求,但希望大家最好这么做
我们先写一个hello.sh的亘古不变的"你好世界"
我们以.sh结尾.进入编辑器后,主要就写两行 #后面的是注释
保存退出即可.
在这里,我们就写好了一个shell脚本,第一行是固定需要的,表明用哪一种shell解析器来执行我们的这个脚本程序。本质上,shell脚本里面的代码都是就是一些流程控制语句加一些特殊语法再加shell命令组成。其中,我们可以当做每一个命令就是shell编程当中的关键字。
1、sh方式或者bash方式
sh helloworld.sh
bash helloworld.sh ## 直接指定用系统默认的bash shell解释执行
2、source方式或者.方式
source命令也称为“点命令”,也就是一个点符号(.),是bash的内部命令。
功能:使Shell读入指定的Shell程序文件并依次执行文件中的所有语句
source命令通常用于重新执行刚修改的初始化文件,使之立即生效,而不必注销并重新登录。
注意:. 和脚本名称之间是有空格的
3、直接执行该脚本文件
可以有两种方式,不过这两种方式的执行,都需要该文件有执行权限
所以在执行之前,我们要更改他的执行权限 chmod 命令
为什么执行不了呢? 我们上面说过,shell脚本文件的第一行写的#!/bin/bash表示用哪一种shell解析器来解析执行我们的这个脚本程序,这句话只对自执行有效,对于使用sh helloworld.sh无效 .我们将这行注释删掉,就可以直接执行了.
单行注释:Shell脚本中以#开头的代码就是注释
多行注释:Shell脚本中也可以使用多行注释:
:<
echo "dd" ## 这句话是注释,也就是说在 :< 注释语句 ! 中间的都是注释
!
Linux Shell中的变量分为“系统变量”和“用户自定义变量”
系统变量可以通过set命令查看,用户环境变量可以通过env查看:
常用系统变量:$PWD $SHELL $USER $HOME
语法
变量=值 (例如STR=abc) 等号两侧不能有空格 变量名称一般习惯为大写 使用变量: $STR |
注意:
命令:A=xiao ming,定义变量时中间带有空格,那么一定要带引号,不然不能定义
命令:A='xiao ming',带了单引号则原样输出。表示引号中间的值是整体字符串
命令:A="xiao ming",带了双引号,表是字符串中运行出现引用变量和转移字符等
上面说了三点,貌似什么也没看出来.别急,在引号当中要引用变量的时候,单引号和双引号的区别就明显啦:
如果是单引号,则引号当中的任何东西都当做字符串,即特殊字符会被脱意
如果是双引号,那么$ABC能打印出变量的值
单引号和双引号总结:
单引号: 1、单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的 2、单引号字串中不能出现单引号(对单引号使用转义符后也不行) |
双引号: 1、双引号里可以有变量 2、双引号里可以出现转义字符 |
我们再看一下下面这个案例:遇到下面的情况,我们要用{}来讲变量括起来
变量的高级用法
撤销变量:unset A
声明静态变量:readonly A= 'abc' 特点是这种变量是只读的,不能unset
现在写两个脚本,在a.sh中调用b.sh执行,那我们想知道a脚本能不能获取到b脚本的变量,b脚本能不能获取到a脚本的变量?
#!/bin/bash A="A in a.sh" echo $A echo $B /root/bin/b.sh |
#!/bin/bash B="B in b.sh" echo $A echo $B |
上面的是a.sh ,下面是b.sh..我们来执行bash a.sh
在a.sh中只打印出了变量A的值,这个好理解,因为shell是顺序解析执行的,在打印变量B的时候,就算它能获取到b.sh当中的变量,它也还没执行,所以,肯定获取不到
那再看b.sh打印出来的结果:可以发现虽然a.sh当中的语句执行完了再调用b.sh来执行,但是b.sh脚本依然也没法获取到a.sh的变量
那怎么解决?
使用export关键字
export A="A in a.sh"
意味着把变量提升为当前shell进程中的全局环境变量,可供其他子shell程序使用,
A变量就成了a.sh脚本所在bash进程的全局变量,该进程的所有子进程都能访问到变量A
另外一种使用方式:
如果在a.sh脚本中用如下方式调用b.sh:. /root/bin/b.sh ## 注意:重点关注最前面那个“.”号 或者 source /root/bin/b.sh
用上述两种方式意味着:b.sh就在a.sh所在的bash进程空间中运行
总结:
1、a.sh中直接调用b.sh,会让b.sh在A所在的bash进程的“子进程”空间中执行
2、而子进程空间只能访问父进程中用export定义的变量
3、一个shell进程无法将自己定义的变量提升到父进程空间中去
4、source或者“.”号执行脚本时,会让脚本在调用者所在的shell进程空间中执行
反引号赋值
a=`ls -l /root/bin` ##反引号,运行里面的命令,并把结果返回给变量a
另外一种写法:
a=$(ls -l /root/bin)
变量小技巧
形式 |
说明 |
${var} |
变量本来的值 |
${var:-word} |
如果变量 var 为空或已被删除(unset),那么返回 word,但不改变 var 的值 |
${var:+word} |
如果变量 var 被定义,那么返回word,但不改变 var 的值 |
${var:=word} |
如果变量 var 为空或已被删除(unset),那么返回 word,并将 var 的值设置为 word |
${var:?message} |
如果变量 var 为空或已被删除(unset),那么将消息 message 送到标准错误输出,可以用来检测变量 var 是否可以被正常赋值。 若此替换出现在Shell脚本中,那么脚本将停止运行 |
$? 表示上一个命令退出的状态码 0 $$ 表示当前进程编号 $0 表示当前脚本名称 $n 表示n位置的输入参数(n代表数字,n>=1) $# 表示参数的个数,常用于循环 $*和$@ 都表示参数列表 |
[root@hadoop02 bin]# vi d.sh #!/bin/bash echo "test var" echo $$ echo $0 echo $1 echo $2 echo $# echo $* echo $@ echo $? |
注:$*与$@区别
$* 和 $@ 都表示传递给函数或脚本的所有参数
不被双引号" "包含时 ==== $* 和 $@ 都以$1 $2 … $n 的形式组成参数列表
当它们被双引号" "包含时 ==== "$*" 会将所有的参数作为一个整体,以"$1 $2 … $n"的形式组成一个整串; "$@" 会将各个参数分开,以"$1" "$2" … "$n" 的形式组成一个参数列表 |
2.2.1、算数运算符
expr
(())
((1+2)) (((2+3)*4)) count=1 ((count++)) ((++count)) echo $count
但是要想取到运算结果,需要用$引用 a=$((1+2)) |
$[]
SS=$[2+3] echo $SS SS=$[2*3] echo $SS echo $[(2 +3)*3] |
[]中,取变量的值时,可以不加$
let
first=1 second=2 let third=first+second echo ${third} |
注意:以上命令都只对整形数值有效,不适用于浮点数
如果有浮点数参与运算,可以将echo与bc命令结合起来使用,代码如下:
看bc的一些强大作用:
echo "1.212*3" | bc ## 简单浮点运算 echo "scale=2;3/8" | bc ##将输出结果设置为2位 echo "obase=2;127" | bc ##输出运算结果的二进制 echo "obase=10;ibase=2;101111111" | bc ##将二进制转换成十进制 echo "10^10" | bc ##求幂指数 echo "sqrt(100)" | bc ##开平方 |
使用bc还可以用来比较浮点数的大小:
[root@hadoop02 bin]# echo "1.2 < 2" |bc 1 [root@hadoop02 bin]# echo "1.2 > 2" |bc 0 [root@hadoop02 bin]# echo "1.2 == 2.2" |bc 0 [root@hadoop02 bin]# echo "1.2 != 2.2" |bc 1 看出规律了嘛?运算如果为真返回 1,否则返回 0,写一个例子: [root@hadoop02 bin]# [ $(echo "2.2 > 2" |bc) -eq 1 ] && echo yes || echo no yes [root@hadoop02 bin]# [ $(echo "2.2 < 2" |bc) -eq 1 ] && echo yes || echo no no |
2.2.2、关系运算符
下面给出一张关系运算符的列表:
运算符 |
等同运算符 |
说明 |
-eq |
= |
检测两个数是否相等,相等返回true |
-ne |
!= |
检测两个数是否相等,不相等返回true |
-ge |
>= |
检测左边的数是否大等于右边的,如果是,则返回true |
-gt |
> |
检测左边的数是否大于右边的,如果是,则返回true |
-le |
<= |
检测左边的数是否小于等于右边的,如果是,则返回true |
-lt |
< |
检测左边的数是否小于右边的,如果是,则返回true |
2.2.3、布尔运算符
运算符 |
等同运算符 |
说明 |
! |
! |
非运算,表达式为 true 则返回false,否则返回true |
-a |
&& |
与运算,两个表达式都为true 才返回true |
-o |
|| |
或运算,有一个表达式为true 则返回true |
2.2.4、字符串运算符
运算符 |
说明 |
= |
检测两个字符串是否相等,相等返回true |
!= |
检测两个字符串是否相等,不相等返回true |
-z |
检测字符串长度是否为0,为0返回true |
-n |
检测字符串长度是否为0,不为0返回true |
str |
检测字符串是否为空,不为空返回true |
2.2.5、文件运算符
运算符 |
说明 |
-d |
检测文件是否是目录,如果是,则返回true |
-f |
检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回true |
-e |
检测文件(包括目录)是否存在,如果是,则返回true |
-s |
检测文件是否为空(文件大小是否大于0),不为空返回true |
-r |
检测文件是否可读,如果是,则返回true |
-w |
检测文件是否可写,如果是,则返回true |
-x |
检测文件是否可执行,如果是,则返回true |
-b |
检测文件是否是块设备文件,如果是,则返回true |
-c |
检测文件是否是字符设备文件,如果是,则返回true |
语法格式:
if [ condition ] then statements [elif [ condition ] then statements. ..] [else statements ] fi |
示例程序:
[root@hadoop02 bin]# vi g.sh #!/bin/bash ## read a value for NAME from stdin read -p "please input your name:" NAME ## printf '%s\n' $NAME if [ $NAME = root ] then echo "hello ${NAME}, welcome !" elif [ $NAME = hadoop ] then echo "hello ${NAME}, welcome !" else echo "I don’t know you !" fi |
规则解释:
[ condition ] (注:condition前后要有空格) #非空返回true,可使用$?验证(0为true,>1为false) [ hadoop ] #空返回false [ ] 注意[ ]内部的=周边的空格
短路运算符(理解为三元运算符) [ condition ] && echo OK || echo notok 条件满足,执行&&后面的语句;条件不满足,执行||后面的语句
条件判断组合 条件判断组合有两种使用方式: [] 和 [[]] 注意它们的区别: [[ ]] 中逻辑组合可以使用 && || 符号 [] 里面逻辑组合可以用 -a -o
常用判断运算符: 1、字符串比较 = 判断相等 != 判断不相等 -z 字符串长度是为0返回true -n 字符串长度是不为0返回true
2、整数比较 -lt 小于 less than -le 小于等于 -eq 等于 -gt 大于 great than -ge 大于等于 -ne 不等于
3、文件判断 -d 是否为目录 if [ -d /bin ]; then echo ok; else echo notok;fi -f 是否为文件 if [ -f /bin/ls ]; then echo ok; else echo notok;fi -e 是否存在 if [ -e /bin/ls ]; then echo ok; else echo notok;fi |
2.3.2、while
语法格式:
while expression do command …… done |
i=1 while ((i<=3)) do echo $i let i++ done |
#!/bin/bash i=1 while [ $i -le 3 ] do echo $i let i++ done |
命令执行完毕,控制返回循环顶部,从头开始直至测试条件为假
换种方式:循环体会一直执行,直到条件表达式expression为false
注意:上述let i++ 可以写成 i=$(($i+1))或者i=$((i+1))
Case语法(通过下面这个例子展示):
case $1 in start) echo "starting" ;; stop) echo "stoping" ;; *) echo "Usage: {start|stop}" esac |
语法格式:
for 变量 in 列表 do command …… done |
列表是一组值(数字、字符串等)组成的序列,每个值通过空格分隔。每循环一次,就将列表中的下一个值赋给变量 |
三种方式
方式一:
for N in 1 2 3; do echo $N; done |
方式二:
for N in {1..3}; do echo $N; done |
方式三:
for ((i=0; i<=2; i++)); do echo "welcome $i times"; done |
语法结构:
until expression do command …… done |
expression一般为条件表达式,如果返回值为 false,则继续执行循环体内的语句,否则跳出循环。
换种方式说:循环体会一直执行,直到条件表达式expression为true
示例:
#!/bin/bash ## vi util.sh a=0 until [ ! $a -lt 3 ] do echo $a a=`expr $a + 1` done |
在Shell中,用括号来表示数组,数组元素用“空格”符号分割开。定义数组的一般形式为:
array_name=(value1 ... valuen)
例子:mingxing=(huangbo xuzheng wangbaoqiang)
也可以单独定义:mingxing[3]=liujialing
读取数组元素的格式是:${array_name[index]}
获取数组下标:
[linux@linux ~]$ echo ${!mingxing[@]}
输出数组的所有元素:
[linux@linux ~]$ echo ${mingxing[*]}
[linux@linux ~]$ echo ${mingxing[@]}
获取数组的长度:
[linux@linux ~]$ echo ${#mingxing[*]}
[linux@linux ~]$ echo ${#mingxing[@]}
数组对接:
[linux@linux ~]$ mingxing+=(liuyifei liuyufeng)
删除数组元素,但是会保留数组对应位置,就是该值的下标依然会保留,会空着,之后,还可以填充其他的值进来。
删除第一个元素:[linux@linux ~]$ unset mingxing[0]
遍历数组:
#!/bin/bash IP=(192.168.1.1 192.168.1.2 192.168.1.3) # 第一种方式 for ((i=0;i<${#IP[*]};i++)) do echo ${IP[$i]} done #第二种方式 for ip in ${IP[*]} do echo $ip done |
效果:
数组的分片:
${arr[@]:number1:number2}
这里number1从下标number1开始取值,number2往后取几个元素,即取到的新的数组的长度
函数的语法使用示例:
[root@hadoop02 bin]# vi i.sh #!/bin/sh hello(){ echo "`date +%Y-%m-%d`" # return 2 } hello echo “huangbo” # echo $? A="mazhonghua" echo $A |
函数的调用方式就是直接写函数名就OK了
注意: 1、必须在调用函数地方之前,先声明函数,shell脚本是逐行运行。不会像其它语言一样先预编译 2、函数返回值,只能通过$? 系统变量获得,可以显示加:return 返回,如果不加,将以最后一条命令运行结果,作为返回值。 return后跟数值n(0-255) |
脚本调试:
使用-x选项跟踪脚本调试shell脚本,能打印出所执行的每一行命令以及当前状态 sh -x i.sh 或者在代码中加入:set -x |
[root@hadoop02 bin]# vi funcWithParam.sh #!/bin/bash # filename=funcWithParam funcWithParam(){ echo "第一个参数为 $1 !" echo "第二个参数为 $2 !" echo "第十个参数为 $10 !" echo "第十个参数为 ${10} !" echo "第十一个参数为 ${11} !" echo "参数总数有 $# 个!" echo "作为一个字符串输出所有参数 $* !" } funcWithParam 1 2 3 4 5 6 7 8 9 34 73 |