一、shell脚本基本语法
1、什么是shell
Shell是一个命令编辑器,它在操作系统的最外层,负责直接与用户进行对话,把用户输入的命令给操作系统,并处理各种各样的操作系统的输出结果,输出到屏幕反馈给用户,这种对话方式可以是交互式也可以是非交互式的
我们所输入的命令计算机是不识别的,这是就需要一种程序来帮助我们翻译,变成计算机能识别的二进制程序,同时把计算机生成的结果返回给我们
2、编程语言分类
编程语言主要用:低级语言和高级语言
1.低级语言:
机器语言:二进制语言
汇编语言:符号语言,使用助记符来代替操作码,也就是用符号代替机器语言的二进制码
它们都是面向机器的语言
2.高级语言
它是比较接近自然语言或者说人类语言的一种程序,用人们能够容易理解的方式进行编写程序
静态语言:编译型语言 如:c , c++ , java
动态语言:解释型语言 如:php , shell , python , perl
gcc编译器:将人类理解的语言翻译成机器理解的语言
3、什么是shell脚本
shell脚本:就是说我们把原来Linux命令或语句放在一个文件中,然后通过这个程序文件去执行时,我们就说这个程序为shell脚本或shell程序,我们可以在脚本中输入一系统的命令以及相关的语法语句组合,比如变量,流程控制语句等,把他们有机结合起来形成了一个功能强大的shell脚本
例:写一个脚本
[root@localhost ~]# vim test.sh
注释:
1、#!/bin/bash 作用:告诉脚本使用的是哪种命令解释器。若不指shell,以当前shell作为执行的shell
2、在shell中以#开头的表示注释,执行时被忽略
3、shell程序一般以.sh结尾
创建shell程序的步骤:
第一步:创建一个包含命令和控制结构的文件
第二步:修改这个文件的权限使它可以执行 ---使用chmod u+x
第三步:检测语法错误
第四步:执行脚本 ./脚本名
shell脚本的执行通常有几种方式:
1./root/test.sh 或者 ./test.sh(当前路径下执行脚本的话要有执行权限 chmod +x test.sh)
2.bash test.sh 或 sh test.sh (这种方式可以不对脚本文件添加执行权限)
3.source test.sh(可以没有执行权限)
4.sh< test.sh 或者 cat test.sh | sh(bash)
二、SHELL变量
1、shell变量
变量时shell传递数据的一种方法,变量是用来代表每个值的符号名,我们可以把变量当成一个容器,通过变量,可以在内存中存储数据,也可以在脚本执行中进行修改和访问存储的数据
变量的设置规则:
1.变量名称通常是大写字母,它可以由数字,字母和下划线组成,变量名区分大小写,但是变量名称不能以数字开头
2.等号用于为变量分配值,在使用过程中等号两边不能有空格
3.变量存储的数据类型是整数值和字符串值
4.在对变量赋于字符串值时,建议使用引号将其括起来,因为如果字符串中存在空格隔符号,需要使用单引号或双引号
5.要对变量进行调用,可以在变量名称前加美元符号$
6.如果需要增加变量的值,那么可以进行变量值的叠加,不过变量需要用双引号包含“$变量名” 或 ${变量名}包含
2、变量的分类
1.用户自定义变量
2.环境变量:这种变量中主要保存的是和系统操作环境相关的数据
3.位置参数变量:这种变量主要是用来向脚本当中传递参数或数据的,变量名不能自定义,变量作用是固定的
4.预定义变量:是bash中已经定义好的变量,变量名不能自定义,变量作用也是固定的。PATH
按照变量作用域可以分成2类:全局变量和局部变量
局部变量是shell程序内部定义的,其使用范围仅限于定义它的程序,对其他程序不可见,包括:用户自定义变量,位置变量和预定义变量。
全局变量是环境变量,其值不随shell脚本的执行结束而消失
1-用户定义变量
变量名命名规则:由字母或下划线打头,不允许数字开头,后面由字母,数字或下划线组成,并且大小写字母意义不同。在使用变量时,在变量名前加$
例1:给变量VAR1赋值
variable :变量
例2:错误的赋值方式:不允许数字开头,等号两边不能有空格
例3:变量值的叠加,使用${}
{name}的简化版本,在一些情况下必须使用花括号括起来来消除歧义
3、命令的替换,使用$()或反引号
例1:在命令中调用date命令输出值
例2:命令的嵌套使用,使用$($())
三、SHELL特殊变量
1、shell中单引号和双引号区别
''在单引号中所有的字符包括特殊字符($,'',`和\)都将解释成为字符本身而成为普通字符
""在双引号中。除了$,'',`和、以外所有的字符都解释成字符本身,拥有“调用变量的值”,“引用命令”和“转义符”的特殊含义
注:\转义符,跟在\之后的特殊符号将失去特殊含义,变为普通字符。如\”符号,而不是当作变量引用
例1:
例2:给变量赋予多个单词,需要使用单引号和双引号
例3:赋值时单引号和双引号的区别
注:单引号之间的内容原封不动的赋值给变量,双引号之间的内容如果有特殊符号会保留它的特殊含义
删除变量:unset VAR1
2、环境变量
在bash shell中,环境变量分为两类:全局变量和局部变量
全局变量:对于shell会话和所有的子shell都是可见的
局部变量:它只在自己的进程中使用
1、 例1:使用export把局部变量输出为全局变量
例2:让变量永久生效,可以把定义好的变量写入配置文件
上面的设置在重新登录或新的ssh连接后就会失效,可以写入配置文件
当登录系统或新开启一个ssh连接来启动bash进程时,一定会加载这4个配置文件:
/etc/profile | 系统全局环境和登录系统的一些配置 |
/etc/bashrc | bash全局自定义配置文件,用于自定义bash |
/root/.bashrc | 用于单独自定义某个用户的bash |
/root/.bash_profile | 用户单独自定义某个用户的系统环境 |
思考:如何知道新建一个ssh连接,加载这4个配置文件先后顺序
可以每个文件的最后,追加一个echo命令,输出一下文件的名字:
3、设置永久变量
在文件的最后插入要赋值的变量,等号两边不能有空格
4、设置PATH环境变量
SHELL要执行某一个程序,它要在系统中去搜索这个程序的路径,PATH变量是用来定义命令和查找命令的目录,当我们安装了第三方程序后,可以把第三方程序bin目录添加到这个PATH路径内,就可以在全局调用这个第三方程序
5、shell位置变量
Shell解释执行用户的命令时,将命令的第一个字符作为命令名,而其它字符作为参数
$0 获取当前执行shell脚本的文件文件名,包括脚本路径,命令本身
$n 获取当前脚本的第n个参数 n=1,2......n,当n大于9时用${10}表示
例1:
[root@77 ~]# vim a.sh
使用场景:服务器启动传参数
[root@77 ~]# /etc/init.d/network restart
6、特殊变量
有些变量时一开始执行Script脚本时就会设定,且不能被修改,但我们不叫它只读的系统变量,而叫它特殊变量,这些变量执行程序时就有了,以下是一些特殊变量:
$* | 以一个单字符串显示所有向脚本传递的参数,如'$*'用[]括起来的情况,以'$1 $2...$n'的形式输出所有参数 |
$# | 传递到脚本的参数个数 |
$$ | 当前进程的进程号PID |
$? | 显示最后命令的退出状态,0表示没有错误,其他任何值表明有错误 |
$! | 后台运行的最后一个进程的进程号pid |
[root@77 ~]# vim tst.sh
四、数学运算
1、expr命令
1.对数字的基本计算,做比较时,输出结果假为0,真为1,特殊符号用转义符
2.对字符串的处理
2、使用$(())
格式:$((表达式1,表达式2))
特点:
1.在双括号结构中,所有表达式可以像c语言一样,如:a++,b--等,a++等价于a=a+1
2.在双括号结构中,所有变量可以不加入“$”符号前缀
3.双括号可以进行逻辑运算,四则运算
4.双括号结构扩展了for,whlie,if条件测试运算
5.支持多个表达式运算,各个表达式之间用“,”分开
常见的算数运算符:
运算符 | 意义 |
++ -- | 递增及递减,可前置也可以后置 |
+ - ! ~ | 一元运算的正负号,逻辑与取反 |
+ - * / % | 加减乘除与余数 |
< <= > >= | 比较大小符号 |
== != | 相等,不相等 |
>> << | 向左位移,向右位移 |
& ^ | | 位的与,位的异或,位的或 |
&& || | 逻辑与,逻辑或 |
? : | 条件判断 |
例1:四则运算
例2:递增和递减
注:a++或a--为先赋值再+1或-1;++a或--a为先加1或减1,然后再进行赋值
例3:求1到100的和
五、read命令键盘读取变量的值
从键盘读取变量的值,通常用在shell脚本中与用户进行交互的场合,该命令可以一次读取多个变量的值,变量和输入的值都需要使用空格隔开,在read命令后面,如果没有指定变量名,读取的数据将被自动赋值给特定的变量REPLY
例1:read从键盘读入数据,赋给变量
1、read常见用法及参数
例1:从标准输入读取一行并赋值给变量passwd
例2:读取多个值,从标准输入读取一行,直至遇到第一个空白符或换行符,把用户键入的第一个词存到变量first中,把改行的剩余部分保存到变量last中
例3:read -s passwd 将你输入的东西隐藏起来,值赋给passwd。用在用户输入密码时,隐藏密码信息,-p 提示信息
例4:输入的时间限制
例5:输入的长度限制
例6:使用-r 参数,允许让输入中的内容包括:空格,\,/,?等特殊字符串
例7:-p用于给出提示
例8:姓名年龄性别脚本
六、流程控制语句if
1、语法格式:
if command(条件)
then
commands
fi
if语句流程图:
注:根据我们的命令退出码来进行判断(echo $? =0),如果是0,那么就会执行痛恨后面的命令
1.单分支if语句
2、双分支if语句
语法格式:
if 条件 #使用封号可以在一排;then
then
commands
else
commands
fi
流程图:
发现当条件为假的时候出现报错信息,提示第6行出错,可以查看第6行
发现少写一个echo,修改后再次执行
3、多分支if语句
语法结构:
if 条件测试操作1;then
commands
elif 条件测试操作2;then
commands
elif 条件测试操作3;then
commands
......
else
commands
fi
流程图:
例1:判断用户在系统中是否存在,是否有家目录
七、test测试命令
Shell中的test命令用于检测某个条件是否成立,它可以进行数值,字符和文件三个方面的测试
格式:test测试条件
注:如果结果为真,用$?=0表示,如果结果为假,用非0表示
1、数值比较
参数 | 说明 | 示例 |
-eq | 等于则为真 | ["$a" -eq "$b"] |
-ne | 不等于则为真 | ["$a" -ne "$b"] |
-gt | 大于则为真 | ["$a" -gt "$b"] |
-ge | 大于等于则为真 | ["$a" -ge "$b"] |
-lt | 小于则为真 | ["$a" -lt "$b"] |
-le | 小于等于则为真 | ["$a" -le "$b"] |
例1:比较大小
例2:比较整数的大小
2、字符串比较
参数 | 说明 | 示例 |
= = | 等于则为真 | [ "$a" == "$b"] |
!= | 不等于则为真 | [ "$a" != "$b"] |
-z 字符串 | 字符串的长度为0则为真 | [ -z "$a" ] |
-n 字符串 | 字符串的长度不为空则为真 | [ -n "$a" ] |
str1 > str2 | str1大于str2为真 | [ str1 \> str2 ] |
str1 < str2 | str1小于str2为真 | [ str1 \< str2 ] |
例1:
例2:在做字符串大小比较的时候,注意字符串的顺序
大于号和小于号必须转义,要不然shell会把它当成重定向符号
大于和小于它们的顺序和sort排序是不一样的
在test比较测试中,它使用的是ASCII顺序,大写字母是小于小写字母的;sort刚好相反
扩展:ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。它是现今最通用的单字节编码系统,并等同于国际标准ISO/IEC 646。
3、文件比较
参数 | 说明 | 示例 |
-e文件名 | 如果文件或目录存在则为真 | [ -e file ] |
-r文件名 | 如果文件存在且可读则为真 | [ -r file ] |
-w文件名 | 如果文件存在且可写则为真 | [ -w file ] |
-x文件名 | 如果文件存在且可执行则为真 | [ -x file ] |
-s文件名 | 如果文件存在且至少有一个字符则为真 | [ -s file ] |
-d文件名 | 如果文件存在且为目录则为真 | [ -d file ] |
-f文件名 | 如果文件存在且为普通文件则为真 | [ -f file ] |
-c文件名 | 如果文件存在且为字符型文件则为真 | [ -c file ] |
-b文件名 | 如果文件存在且为块特殊文件则为真 | [ -b file ] |
file1 -nt file2 | 检查file1是否比file2新 | [ file1 -nt file2 ] |
file1 -ot file2 | 检查file1是否比file2旧 | [file1 -ot file2 ] |
例1:检查/etc/passwd是否存在
例2:清空日志目录
注:退出码exit,取值范围是0-255。
注:这是sshd的配置文件,#后面直接写入的参数是系统默认执行的参数,#空格后面写入的是注释
八、流程控制过程中复杂条件和通配符
1、判断
判断第一种:两个条件都为真或有一个为真就执行
if [ 条件判断一 ] &&(||)[ 条件判断二 ] ; then
命令一
elif [ 条件判断三 ] &&(||)[ 条件判断四 ] ; then
命令二
else
执行其他
fi
判断第二种:
if [条件判断一 -a(-o)条件判断二 -a(-o)条件判断三] ;then
elif [条件判断三 -a(-o)条件判断四] ;then
else
执行其他
fi
判断第三种:
if [[条件判断一 &&(||)条件判断二]];then
elif [[条件判断三 &&(||)条件判断四]];then
else
执行其他
fi
例1:设置umask,参考/etc/profile
例2:[[......]]和[......]的区别
[[......]]运算符是[......]运算符的扩充;[[......]]能够支持*,<,>等符号且不需要转义符
总结:
1.所有的字符与逻辑运算符直接用”空格“分开,不能连到一起
2.在[...]表达式中,常见的>,<需要加转义符\,大小比较
3.进行逻辑运算符&&,||比较时,如果用[ ]符号,则用在外面,如[...]&&[...]||[...]如果在[...]里面进行逻辑与或的比较,则用-a,-o进行表示,如[ x=y -a x<z -o x>m ]
4.[[...]]运算符只是[...]运算符的扩充,能够支持<,>符号不需要转义符,它还是以字符串比较大小,里面支持逻辑运算符 || ,&&,不能再使用-a , -o
5.[[...]]用 && 而不是-a表示逻辑”与“,用 || 而不是-o表示逻辑”或“
6.[[...]]可以进行算术扩展,而[...]不可以
7.[[...]]能用正则表达式,而[...]不可以
8.双括号(( ))用于数学表达式
9.双方括号[[ ]]用于高级字符串处理,如”模糊匹配“
2、shell中常见的通配符
字符 | 含义 | 示例 |
* | 匹配0或多个字符 | a*b:a与b之间可以有任意长度的任意字符,也可以一个也没有 |
? | 匹配任意一个字符 | a?b:a与b之间必须也只能有一个字符,可以是任意字符 |
[list] | 匹配list中的任意一个单一字符 | a[xyz]b:a与b之间必须也只能有一个字符,且这个字符只能是x,y,z |
[!list] | 匹配除list中的任意一个单一字符 | a[!0-9]b:a与b之间必须也只能有一个字符,但是不能为数字 |
[0-9] | 匹配0-9中的任意一个数字 | a[0-9]b:a与b之间必须也只能有一个数字 |
{string1,string2,...} | 匹配string1或string2或更多其一字符串 | a{abc,xyz,123}b:a与b之间只能是abc或xyz或123这三个字符串之一 |
九、流程控制语句:case
1、case
控制语句:用来实现对程序流程的选择,循环,转向和返回等进行控制。case是开关语句的一个组成部分,它是根据变量的不同进行取值比较,然后针对不同的取值分别执行不同的命令操作,适用于多分支,是一个多选择语句
语法:
case 变量或表达式 in
变量或表达式1)
命令序列1
;;
变量或表达式2)
命令序列2
;;
......
*)
默认命令序列
esac
case语句执行流程控制图:
执行流程:
首先使用“变量或表达式”的值与值1进行比较,若取值相同则执行值1后的命令序列,直到遇见双分号“;;“后跳转至esac,表示分支结束;
若与值1不匹配,则继续与值2进行比较,若取值相同则执行值2后的命令序列,直到遇见双分号后跳转至esac,表示结束分支
依此类推,若找不到任何匹配的值,则执行默认模式” *)“后的命令序列,直到遇见esac后结束分支
注意事项:
1.”变量或表达式“后面必须为单词in,每一个”变量或表达式“的值必须以右括号结束,取值可以为变量或常数,匹配发现取值符合某一模式后,其间所有命令开始执行直至 ;;
2.匹配中的值可以是多个值,通过 ”|“来分隔
例1:编写一个操作文件的脚本
例2:编写一个启动apache服务脚本
十、循环语句
1、for-do-done
语法格式:
for var in list
do
commands
done
或:
for var in list ; do
commands
done
流程图:
例1:取值列表有多种取值方式,如可以直接读取in后面的值,默认以空格做分隔
例2:列表中的复杂值,可以使用,引号或转义符来加以约束
例3:
例4:从变量中取值
例5:从命令中取值
2、自定义shell分隔符
默认情况下,base shell会以空格,换行符作为分隔符,通过IFS来自定义为分隔符
指定单个字符做分隔符:
IFS=: #以冒号做分隔符
可以指定多个分隔符:
如 IFS='\n':;" #这个赋值会将反斜杠,n,冒号,分号和双引号作为字段分隔符
IFS='\n' #将字符\和字符n作为换行符
例1:以'\n'回车作为换行符
3、C语言风格的for
语法格式:
for ((i=0;i<10;i++))
do
commands
done
例1:单个变量,输出1到10之间的数字
例2:多个变量同时输出1-9的升序和降序
十一、while循环语句和循环嵌套
1、while-do-done
重复测试指令的条件,只要条件成立就反复执行对应的命令操作,直到命令不成立或为假
语法格式:
while 测试命令
do
命令
done
流程图:
注:避免陷入死循环 while true ,循环退出根据测试条件的退出码来定
例1:降序输出10到1的数字
例2:输出1*1到9*9相乘的效果
自增操作:let var++
自减操作:let var--
2、嵌套循环
例1:批量添加a.txt文件中的5个用户
编写脚本的思路:
1.明确脚本的功能
2.编写脚本时会用到哪些命令:useradd ,passwd ,for
3.把变化的数据使用变量表示
4.选择合适的流程控制 (选择、循环、分支)
例2:打印九九乘法表
十二、跳出循环
1、break,continue
在使用循环语句进行循环的过程中,有时候需要在未达到循环结束条件时强制跳出循环,shell给我们提供了两个命令来实现该功能:break 和 continue
break:跳出整个循环
continue:跳出本次循环,进行下次循环
break概述:跳出当前整个循环或结束当前循环,在for,while等循环语句中,用于跳出当前所在的循环体,执行循环体之后的语句,后面如果上面也不加,表示跳出当前循环等价于break1,也可以在后面加数字,break3表示跳出第三层循环
continue概述:忽略本次循环剩余的代码,直接进行下一次循环;在for,while等循环语句中,用于跳出当前所在的循环体,执行循环体之后的语句,如果后面加的是数字1,表示忽略本次条件循环,如果是2的话,忽略下来2次条件的循环
例:使用交互式方法批量添加用户
#!/bin/bash
while :
do
read -p "Please enter prefix & password & num: " pre pass num
printf "user information:
***********************
user prefix: $pre
user password: $pass
user number: $num
***********************
"
read -p "Are you sure?[y/n] " action
if [ "$action" == "y" ] ; then
break
fi
done
for i in $( seq $num )
#从i=1开始,到$num;$(seq $num)等于`seq $num`;$(命令);${变量};[表达式/条件]
do
user=${pre}${i}
id $user &> /dev/null
if [ $? -ne 0 ] ; then
useradd $user
echo "$pass" | passwd --stdin $user &> /dev/null
if [ $? -eq 0 ] ; then
echo -e "\033[31m$user\033[0m creat"
#\033[31m\033[0m表示颜色
fi
else
echo "user $user exist"
fi
done
2、shift参数左移指令
shift命令用于对参数的移动(左移),通常用于在不知道传入参数个数的情况下一次遍历每个参数然后进行相应处理(常见于Linux中各种程序的启动脚本),在扫描处理脚本程序的参数时,经常要用到的shift命令,如果你的脚本需要10个或10个以上的参数,你就需要用shift命令来访问第10个及后面的参数
作用:每执行一次,参数序列顺次左移一个位置,$#的值减1,用于分别处理每个参数,移出去的参数,不再可用
例:
#!/bin/bash
if [ $# -le 0 ] ; then
echo "没有足够的参数"
exit
fi
sum=0
while [ $# -gt 0 ]
do
sum=$[ $sum + $1 ]
shift
#shift2 一次移动2个参数
done
echo result is $sum
十三、函数的使用
函数是一个脚本代码块,你可以对它进行自定义命名,并且可以在脚本中任意位置使用这个函数,要使用这个函数,只要使用这个函数名称就可以了,使用函数的好处:模块化,代码可读性强
1、函数创建语法
方法1:
function name{
commands
}
方法2:
name(){
commands
}
注:name后面的括号表示你正在定义的函数
调用函数语法:
函数名 参数1 参数2 ...
调用函数时,可以传递参数,在函数中用$1、$2...来引用传递的参数
例:调用函数
注:函数名的使用,如果在一个脚本中定义了重复的函数名,那么以最后一个为准
2、把函数值赋给变量使用
3、函数的参数传递
第一种:通过脚本传递参数给函数中的位置参数$1
第二种:调用函数时直接传递参数
第三种:函数中多参数传递和使用方法
测试: