shell脚本基础
shell脚本是包含一些命令或声明,并符合一定格式的文本文件
一般格式要求:
1.首行shebang机制
#!/bin/bash
#!/bin/python
#!/usr/bin/perl
2.注释信息
以#开头,可写明脚本用途,作者,版本,时间等
3.脚本正文内容,
创建shell脚本
以bash为例
-
第一行定格#!/bin/bash
-
注释信息:
-
#用途
-
#作者
-
#版本
-
#时间
-
脚本正文内容,保存退出
-
给予脚本文件执行权限,
-
在命令上执行脚本的绝对路径或相对路径
-
直接运行解释器,将脚本作为解释器程序的参数运行
脚本范例
脚本调试
当我们写完脚本时,可以使用指令检测脚本中的语法错误
bash -n /path/to/some_script ==>检测错误
bash -x /path/to/some_script ==>调试执行
变量 :命令的内存空间
变量命名法则:
-
不能使用程序中的保留字:例如if,for ==>不能使用系统自带的变量名
-
只能使用数字,字母及下划线,且不能以数字开头
-
见名知意
-
统一命名规则:驼峰命名法
变量种类
本地变量:生效范围为当前shell进程;对当前shell之外的其他shell进程,包括当前shell的子shell进程均无效
说明:脚本file1.sh定义变量num1,且脚本file2.sh是其子进程,脚本file2.sh中定义变量num2
运行file2.sh,变量num2有效显示,变量num1未显示,运行file1.sh,
变量num1有效显示,变量num2未显示,子进程file2.sh运行,变量num2有效显示,变量num1未显示
由以上可知,以上变量只在当前进程有效
环境变量:生效范围为当前shell进程及其子进程
说明:在上面本地变量的基础上将变量类型改变成环境变量,执行效果也有差异
file2.sh是file.1sh的子进程,file1.sh中定义的变量file2.sh在作为其子进程运行时,
是有效果的,但是在file2.sh不作为file1.sh的子进程运行时,是没效果的,看单独运行
file2.sh中只有变量num2,而在运行file1.sh时,num2没有继承其子进程的变量
所以:生效范围为当前shell进程及其子进程,其父进程也是不生效的
局部变量:生效范围为当前shell进程中某代码片段(通常指函数)
位置变量:$1,$2,…..来表示,用于让脚本在脚本代码中调用通过命令行传递给它的参数
特殊变量:$?,$0,$*,$@,$#
本地变量:
变量赋值:name=‘value’
-
可以直接是字符串;name=“root”
-
变量引用:name="$USER"
-
命令引用:name=`command` ,name=$(command)
变量引用:${name} ,$name
" " :弱引用,其中的变量引用会被替换为变量值
‘ ’:强引用,其中的变量引用不会被替换为变量值,而保持原字符
显示已定义的所有变量:set
删除变量: unset name
环境变量
变量赋值:
export name=VALUE
declare -x name=VALUE
变量引用:${name} ,$name
显示所有环境变量:
export
env
printenv
删除变量: unset name
bash内建的环境变量:PATH,SHELL,USER,UID,HISTSIZE,HOME,PWD,OLPWD,PS1,HISTFILE
只读变量:只能用,不能修改和删除
readonly name
declare -r name
可用来定义一些固定的值,直接引用,例如π
位置变量:在脚本代码中调用通过命令行传递给脚本的参数
$1,$2,…:对应第1,2,….等参数
$0 :命令本身
$* :传递给脚本的所有参数,全部参数何为一个字符串
$@ :传递给脚本的所有参数,每个参数为独立字符串
$# :传递给脚本的参数的个数
备注:$@ $* 只在被双引号包起来的时候才会有差异(后面会专门验证)
算数运算:
bash的算术运算: +,-,*,/,%取余,**(乘方)
实现算术运算:
-
let var=算术表达式
-
var=$[算术运算]
-
var=$((算术运算))
-
var=$(expr arg1 arg2 arg3 ….)
-
declare -i var = 数值
-
echo `算术表达式` |bc
乘法符号游戏场景中需要转义:\*
bash 有内建的随机数生成器:$RANDOM (1-32767) )
echo $[$RANDOM%50] :0-49 之间随机数
赋值
增强型赋值:
变量做某种算数运算后会存至此变量中
i=$[$i+1] ==>每运行一次此命令,变量i的值会增加1
let i+=1 同上
let i+=2每次增加2
let(+=,-=,*=,/=,%=)
自增:
VAR=$[$VAR+1]
let VAR+=1
let VAR++
自减:
VAR=$[$VAR-1]
let VAR-=1
let VAR–
逻辑运算
true :1 false:0
与:
1与1=1
1与0=0
0与1=0
0与0=0
或:
1或1=1
1或0=1
0或1=1
0或0=0
非:!
!1=0
!0=1
短路运算:
短路与:
第一个为0,结果必定为0
第一个为1,第二个必须要参与运算
短路或:
第一个为1,结果必定为1
第一个为0,第二个必须要参与运算
异或:^
异或的两个值相同为假,不同为真
聚集命令
复合式:date;who| wc -l
命令会一个接一个的运行
子shell:(date;who|wc -l)>>/tmp/trace
所有的输出都被发送给单个的STDOUT和STDERR
退出状态
进程使用退出状态来报告成功或失败
0:成功
1-255:失败
$? :变量保存最近的命令退出状态
退出状态码
bash自定义退出状态码
exit [n] :自定义退出状态码
ps:脚本中一旦遇到exit命令,脚本会立即终止;终止状态取决于exit命令后面的数字
如果未给脚本指定退出状态码,整个脚本的推出状态码取决于脚本中执行的最后一条命令的状态码
条件测试
判断某需求是否满足,需由测试机制来实现
如何编写测试表达式以实现所需的测试:
1.执行命令,并利用命令的状态返回值来判断
0:成功
1-255:失败
2.测试表达式
test expression 比较大小
[ expression ]
[[ expression ]]
注意:expression两端必须有空白字符,否则为语法错误
根据退出状态而定,命令可以有条件的运行
&& 代表条件性的AND THEN
|| 代表条件性的OR ELSE
短路与: command1&&command2
如果command1成功,执行command2
如果command1失败,不执行command2
短路或:command1||command2
如果command1成功,跳过command2
如果command1失败,将执行command2
test
长格式
test "$A" == "$B"&& echo "xiangdeng"
test "$A" -eq "$B" && echo "budengde"
简写格式
[ "$A" == "$B" ]&& echo "xiangdeng"
[ "$A" -eq "$B ]&& echo "budengde"
bash 测试类型
数值测试:
-gt:是否大于
-ge:是否大于等于
-eq:是否等于
-ne:是否不等于
-lt:是否小于
-le:是否小于等于
字符串测试
== :是否等于
> :ascii码是否大于ascii码
< :ascii码是否小于ascii码
!=:ascii码是否不等于ascii码
=~ :左侧字符串是否能够被右侧pattern所匹配
注意:此表达式一般用于[[ ]]中
-z “string”:字符串是否为空,空为真,不空为假
-n “string”:字符串是否不空,不空为真,空为假
注意:用于字符串比较时的用到的操作数都应该使用引号
[root@localhost ~]# A=bbb [root@localhost ~]# B=aaa [root@localhost ~]# C=aaa [root@localhost ~]# [[ "$C" == "$B" ]]&& echo right right [root@localhost ~]# [[ "$C" == "$A" ]]&& echo right [root@localhost ~]# [[ "$B" == "$A" ]]&& echo right [root@localhost ~]# [[ "$B" == "$A" ]]&& echo right||echo wrong wrong [root@localhost ~]# [[ "$B" == "$c" ]]&& echo right||echo wrong wrong [root@localhost ~]# [[ "$B" == "$C" ]]&& echo right||echo wrong right [root@localhost ~]# [[ "$A" == "$C" ]]&& echo right||echo wrong wrong
文件测试
存在性测试
-e file:文件存在性测试,存在为真,否则为假
-a file:同-e
存在性及类别测试
-b file :是否存在且为块设备文件
-c file :是否存在且为字符设备文件
-d file :是否存在且为目录文件
-f file :是否存在且为普通文件
-h file 或者-L file:存在且为符号链接文件
-p file :是否存在且为命名管道文件
-S file : 是否存在且为套接字文件
文件权限测试:
-r file:是否存在且为可读
-w file: 是否存在且为可写
-x file: 是否存在且为可执行
文件特殊权限测试
-g file :是否存在且拥有sgid权限
-u file :是否存在且拥有suid权限
-k file :是否存在且拥有sticky权限
文件大小测试
-s file :是否存在且为非空
文件是否打开
-t fd :fd表示文件描述是否已经打开且与某终端相关
-N file:文件自上一次被读取之后是否被修改过
-O file:当前有效用户是否为文件属组
-G file: 当前有效用户是否为文件属组
双目测试
file1 -ef file2 :file1与file2是否指向同一个设备上的相同inode
file1 -nt file2: file1 是否新于file;
file1 -ot file2: file1 是否旧于file; ;
组合测试条件
第一种:
command1$$command2 并且
command1||command2 或者
!command 非
例如[[ -e file ]] &&[[ -r file ]]
第二种:
expression1 -a expression2 并且
expression1 -o expression2 或者
!expression
必须使用测试命令进行
关于位置变量总结
1.位置变量中$@和$*的区别
当$@和$*不使用“”包起来时效果一样
当$@和$*用“”包起来时,执行$@正常运行
但是执行$*时报错显示的是没有“a b”这个文件后目录,
说明使用“”时,$*是把所有参数当作一个字符串的
$@则是每个参数都是分开的
$@和$*换个顺序看看有影响没
结果一样,所以上面结论成立
上课的时候老师好像不是这样验证的,我尝试下老师的方法
首先我创建了三个脚本,内容如下分别是t2.sh,t3.sh,t1.sh
其中t1.sh是t2.sh,t3.sh子脚本(后面简称脚本1.2.3)
我们先看下运行三个脚本结果如何
运行脚本1时,1.2.4命令正常执行,说明命令和参数都没有问题,从其结果看也是正确的
命令3执行时显示的的是“a b c”这个文件,这是把a b c当作的是一个参数,和上面的结论一样的
这样说明了$*是把多个参数当作一个字符串的
运行脚本3时,命令1.2正常执行,且结果正确,然后执行子脚本t1.sh参数是$*
但是t1第一行显示的a b c,t2第二行命令没显示,说明第二行没参数,所以没显示
t1第三四行显示正常结果,再次证明t1.sh运行时只有一个参数“a b c”
在运行脚本2
所有参数都是正常执行,
在强调一下结论:
在被双引号包起来时 $*传递给脚本的所有参数,全部参数合为一个字符串,变成一个参数
$@不受影响,每个参数依然是独立的
$#:传递到脚本的参数个数
$0:命令本身
$$:脚本运行的当前进程ID号
$!:后台运行的最后一个进程的进程ID号
$?:显示最后命令的退出状态结果
0:成功
1-255:失败
练习
1、编写脚本/root/bin/systeminfo.sh,显示当前主机系统信息,包括主机名,IPv4地址,操作系统版本,内核版本,CPU型号,内存大小,硬盘大小。
脚本内容如下:
1 #!/bin/bash 2 #The current host system information 3 echo -e "hello,everyone, \nThe current host system information is as follows" 4 echo -e " " 5 echo "hostname :`hostname`" 6 echo "" 7 echo "ip:`ifconfig |head -n2|tail -n1|tr " " ":"|tr -s ":"|cut -d: -f3`" 8 echo "" 9 echo -e "system version:`cat /proc/version`" 10 echo "" 11 echo "kenel version:`uname -r`" 12 echo "" 13 echo -e "CPU Model:`lscpu |grep "Model name"|cut -d: -f2|sed 's@^[[:space:]]\+@@'`" 14 echo "" 15 echo -e "the memory capacity :`free -h|tr " " ":"|tr -s ":"|head -2|tail -1|cut -d: -f2`" 16 echo "" 17 echo -e "hard drive size:`fdisk -l |head -2|tail -1|cut -d: -f2|cut -d, -f1`"
运行结果如下:
2、编写脚本/root/bin/backup.sh,可实现每日将/etc/目录备份到/root/etcYYYY-mm-dd中
脚本内容如下
1 #!/bin/bash 2 #discription:wen jian bei fen 3 cp -a /etc/ /root/etc`date +%Y-%m-%d`
因为暂时还没学到每日实现,只能做到手动备份,结果如下
3、编写脚本/root/bin/disk.sh,显示当前硬盘分区中空间利用率最大的值
指令如下
1 #!/bin/bash 2 #ci pan li yong lv 3 df|grep "sda"|tr " " ":"|tr -s ":"|cut -d: -f5|sort -rn|head -n1|tr -d "%"
结果:
4、编写脚本/root/bin/links.sh,显示正连接本主机的每个远程主机的IPv4地址和连接数,并按连接数从大到小排序
命令如下
1 #!/bin/bash 2 netstat -nt|grep tcp|tr " " ":"|tr -s ":"|cut -d: -f6|sort |uniq -c|sort -rn
5、写一个脚本/root/bin/sumid.sh,计算/etc/passwd文件中的第10个用户和第20用户的ID之和
命令如下:
1 #!/bin/bash 2 user1_id=`cat /etc/passwd |head -10|tail -1|cut -d: -f3` 3 echo "the 10 user_id: $user1_id" 4 user2_id=`cat /etc/passwd |head -20|tail -1|cut -d: -f3` 5 echo "the 20 user_id: $user2_id" 6 sumid=$[$user1_id+$user2_id] 7 echo "the sumid:$sumid"
结果:
6、写一个脚本/root/bin/sumspace.sh,传递两个文件路径作为参数给脚本,计算这两个文件中所有空白行之和
空行和空白行表示的应该是不一样的
空行:^$
空白行:^[[:space:]]*$space
指令1如下
1 #!/bin/bash 2 num1=`cat /etc/rc.d/init.d/functions |grep "^[[:space:]]*$"|wc -l` 3 echo "the 1st is: $num1" 4 num2=`cat /etc/fstab |grep "^[[:space:]]*$"|wc -l` 5 echo "the 2st is: $num2" 6 sumspace=$[$num1+$num2] 7 echo "the sum is: $sumspace"
指令2如下
1 #!/bin/bash 2 num1=`cat /etc/rc.d/init.d/functions |grep "^$"|wc -l` 3 echo "the 1st is: $num1" 4 num2=`cat /etc/fstab |grep "^$"|wc -l` 5 echo "the 2st is: $num2" 6 sumspace=$[$num1+$num2] 7 echo "the sum is: $sumspace"
7、写一个脚本/root/bin/sumfile.sh,统计/etc, /var, /usr目录中共有多少个一级子目录和文件
命令:
1 #!/bin/bash 2 num1=`ls -A /etc|wc -l` 3 echo "etc: $num1" 4 num2=`ls -A /var|wc -l` 5 echo "var: $num2" 6 num3=`ls -A /usr|wc -l` 7 echo "usr :$num3" 8 sumfile=$[$num1+$num2+$num3] 9 echo "the sumfile: $sumfile"
8、写一个脚本/root/bin/argsnum.sh,接受一个文件路径作为参数;如果参数个数小于1,则提示用户“至少应该给一个参数”,并立即退出;如果参数个数不小于1,则显示第一个参数所指向的文件中的空白行数
命令:
1 #!/bin/bash 2 num1=1 3 [[ $# -lt $num1 ]] &&( echo "zhishaoyinggai geiyige canshu" ;exit)|| grep "^$" $1|wc -l
9、chmod -rw /tmp/file1,编写脚本/root/bin/per.sh,判断当前用户对/tmp/fiile1文件是否不可读且不可写
命令
1 #!/bin/bash 2 [ -r /tmp/file1 -o -w /tmp/file1 ]&&echo 读写至少一个 ||echo 不可读且不可写
10、编写脚本/root/bin/nologin.sh和login.sh,实现禁止和充许普通用户登录系统。
说明下,当/etc/nologin文件存在时,用户是禁止登录的,知道了这个上面的就简单了
禁止登录
1 #!/bin/bash 2 [ -f /etc/nologin ] && echo "user cannot login" || (touch /etc/nologin; echo "user cannot login")
允许登录
1 #!/bin/bash 2 [ -f /etc/nologin ]&& (rm -rf /etc/nologin ; echo "user can login") ||echo "user already login"
11、写一个脚本/root/bin/hostping.sh,接受一个主机的IPv4地址做为参数,先判断是否合格IP,否,提示IP格式不合法并退出,是,测试是否可连通。如果能ping通,则提示用户“该IP地址可访问”;如果不可ping通,则提示用户“该IP地址不可访问”
命令:
1 #!/bin/bash 2 ping -c1 -w1 $1 &>/dev/null &&echo "the ip can access" ||echo "the ip cannot access"
12、计算1+2+3+…+100的值
1 #!/bin/bash 2 echo the sumnumis: `echo {1..100}|tr " " "+"|bc`
13、计算从脚本第一参数A开始,到第二个参数B的所有数字的总和,判断B是否大于A,否提示错误并退出,是则计算之
1 #!/bin/bash 2 [[ $1 -lt $2 ]] && seq -s+ $1 $2|bc || (echo "error" ;exit)
原创文章,作者:qiuwei,如若转载,请注明出处:http://www.178linux.com/33529
评论列表(1条)
总结的很好很详细,态度认真,内容充实,排版精美,再接再厉。