shell 编程
shell介绍
概述
Shell是一种具备特殊功能的程序,它提供了用户与内核进行交互操作的一种接口。它接收用户输入的命令,并把它送入内核去执行。内核是Linux系统的心脏,从开机自检就驻留在计算机的内存中,直到计算机关闭为止,而用户的应用程序存储在计算机的硬盘上,仅当需要时才被调入内存。Shell是一种应用程序,当用户登录Linux系统时,Shell就会被调入内存去执行。Shell独立于内核,它是连接内核和应用程序的桥梁,并由输入设备读取命令,再将其转为计算机可以理解的机械码,Linux内核才能执行该命令。
优势
Shell脚本语言的好处是简单、易学、易用,适合处理文件和目录之类的对象,以简单的方式快速完成某些复杂的事情通常是创建脚本的重要原则,脚本语言的特性可以总结为以下几个方面:
- 语法和结构通常比较简单。
- 学习和使用通常比较简单。
- 通常以容易修改程序的“解释”作为运行方式,而不需要“编译。
- 程序的开发产能优于运行效能。
Shell脚本语言是Linux/Unix系统上一种重要的脚本语言,在Linux/Unix领域应用极为广泛,熟练掌握Shell脚本语言是一个优秀的Linux/Unix开发者和系统管理员必经之路。利用Shell脚本语言可以简洁地实现复杂的操作,而且Shell脚本程序往往可以在不同版本的Linux/Unix系统上通用。
shell编程
基本格式
Shell脚本的文件名后缀通常是.sh (当然你也可以使用其他后缀或者没有后缀,.sh是为了规范)
程序编写格式:
#!/bin/bash
代码示例:
[root@localhost day01]# vi a.sh
#!/bin/bash
echo hello shell
# 赋予 a.sh 可执行权限
[root@localhost day01]# chmod +x a.sh
# 执行(调用/bin/bash执行a.sh脚本)
[root@localhost day01]# ./a.sh
hello shell
变量
变量不需要声明,初始化不需要指定类型
变量命名
-
只能使用数字,字母和下划线,且不能以数字开头
-
变量名区分大小写
-
建议命令要通俗易懂
**注意:**变量赋值是通过等号(=)进行赋值,在变量、等号和值之间不能出现空格。
显示变量值使用 echo命令(类似于java中的system.out) ,加上 $变量名,也可以使用 ${变量名}
例如:
echo $JAVA_HOME
echo ${JAVA_HOME}
变量的声明和使用
[root@localhost day01]# num=10
[root@localhost day01]# echo $num
10
变量分类
本地变量、环境变量、局部变量、位置变量、特殊变量
本地变量
- 只对当前shell进程有效的,对当前进程的子进程和其它shell进程无效。
- 定义:VAR_NAME=VALUE
- 变量引用:${VAR_NAME} 或者 $VAR_NAME
- 取消变量:unset VAR_NAME
- 相当于java中的私有变量(private),只能当前类使用,子类和其他类都无法使用。
比如在一个bash命令窗口下再使用bash,则变成了子进程,本地变量不会被这个子进程所访问。
环境变量
自定义的环境变量对当前shell进程及其子shell进程有效,对其它的shell进程无效
定义:export VAR_NAME=VALUE
对所有shell进程都有效需要配置到配置文件中
[root@localhost day01]# vi /etc/profile
[root@localhost day01]# source /etc/profile
相当于java中的protected修饰符,对当前类,子孙类,以及同一个包下面可以共用。
自定义的环境 变量
局部变量
- 在函数中调用,函数执行结束,变量就会消失
- 对shell脚本中某代码片段有效
- 定义:local VAR_NAME=VALUE
- 相当于java代码中某一个方法中定义的局部变量,只对这个方法有效
位置变量
脚本中的参数
$0: 脚本自身
$1: 脚本的第一个参数
$2: 脚本的第二个参数
相当于java中main函数中的args参数,可以获取外部参数。
[root@localhost day01]# vi args.sh
#!/bin/bash
echo $0
echo $1
echo $2
执行结果
特殊变量
$?:接收上一条命令的返回状态码,返回状态码在0-255之间
$#:参数个数
$*:或者$@:所有的参数
$$:获取当前shell的进程号(PID)(可以实现脚’'本自杀)(或者使用exit命令直接退出也可以使用exit [num])
#!/bin/bash
echo $0
echo $1
echo $2
echo "参数个数 $#"
echo "所有的参数 $*"
echo "所有的参数 $@"
echo "当前shell的进程号 $$"
echo "上一条命令的返回状态 $?"
执行结果
引号
Shell编程中有三类引号:单引号、双引号、反引号。
‘’ 单引号不解析变量
echo '$name'
“” 双引号会解析变量
echo "$name"
`` 反引号是调用命令的输出,或把命令的输出赋予变量,和 $(命令) 是一样的
echo `ls`
示例:
[root@localhost day01]# name=zhansan[root@localhost day01]# echo '$name'$name[root@localhost day01]# echo "$name"zhansan[root@localhost day01]# echo `$name`-bash: zhansan: 未找到命令[root@localhost day01]# echo `echo $name`zhansan[root@localhost day01]# echo `ls`args.sh a.sh[root@localhost day01]# echo $(ls)args.sh a.sh
循环
for循环
- 格式1
for ((i=0;i<10;i++))do ...done
- 格式2
for i in 0 1 2 3 4 5 6do ...done
- 格式3
for i in {0..9}do ...done
**注意:**for i in {0…9} 等于for i in {0…9…1} , 第三个参数为跨步。
例如:
{0…9…2} 表示 0,2,4,6,8
while循环
格式
while [ 条件 ]do ...done
#!/bin/bash# fori循环for((i=0;i<5;i++))doecho "fori 循环:$i"done# for in循环for i in 0 1 3 4doecho "for in 循环: $i"done# for in {}for i in {0..9..2}doecho "for in {} 循环: $i"done
#!/bin/bashnum=0while [ $num -lt 10 ]doecho $numlet num=$num+1done
注意 [] 里的内容开始和结尾要用空格
循环控制
berak
break命令是在处理过程中跳出循环的一种简单方法,可以使用break命令退出任何类型的循环,包括while循环和for循环
continue
continue命令是一种提前停止循环内命令,而不完全终止循环的方法,这就需要在循环内设置shell不执行命令的条件
条件
test [ EXPR ]
[ EXPR ]:注意中括号和表达式之间的空格
整型测试
-gt:大于:
-lt:小于
-ge:大于等于
-le:小于等于
-eq:等于
-ne:不等于
例如[ $num1 -gt $num2 ]或者test $num1 -gt $num2
字符串测试
= 等于,例如判断变量是否为空 [ $str = “” ] 或者[ -z $str ]
!= 不等于
判断
if 判断
- 单分支
if [ 条件 ]; then 选择分支fi
- 双分支
if [ 条件 ]; then 选择分支1else 选择分支2fi
- 多分支
if [ 条件1 ]; then 分支1elif [ 条件2 ]; then 分支2elif [ 条件3 ]; then 分支3 ...else 分支nfi
实例:
#!/bin/bashnum1=4if [ $num1 -gt 5 ]; then echo "$num1 大于 5"else echo "$num1 小于 5"fistr1=""if [ $str1="" ]; thenecho "str 为空"fi
算数运算符
let varName=算术表达式
varName=$[算术表达式]
varName=$((算术表达式))
varName=`expr $num1 + $num2`
使用这种格式要注意两个数字和+号中间要有空格。
实例:
#!/bin/bashnum=1let num=$num+1num=$[ $num+1 ]num=$(($num+1))num=`expr $num + 1`echo $num
结果 5
逻辑运算符
if [ 条件A && 条件B ] 在shell中怎么写?
if [ 条件A && 条件B ];then 是不对的
解决方法:
- 需要用到shell中的逻辑操作符
-a 与
-o 或
! 非如if [ 条件A -a 条件B ]
-
if [ 条件A ] && [条件B ]
-
if((A&&B))
-
if [[ A&&B ]]
#!/bin/bashnum1=10num2=20num3=15# 方式一if [ $num1 -lt $num3 -a $num2 -gt $num3 ]; then echo "num3 在10-20之间"else echo "other"fi# 方式二if [[ $num1 -lt $num3 && $num2 -gt $num3 ]]; then echo "num3 在10-20之间"else echo "other"fi
自定义函数
格式:
function 函数名(){
…
}
- 引用自定义函数文件时,使用source func.sh
- 有利于代码的重用性
- 函数传递参数(可以使用类似于Java中的args,args[1]代表Shell中的$1)
- 函数的返回值,只能是数字
#!/bin/bashfunction func(){ echo "this is a function" if [ $1 ]; then echo "第一个参数 $1" fi return 0}# 函数调用funcfunc 'hello'
函数调用
[root@localhost day01]# vi test.sh #!/bin/bash# 外部调用函数source func.shfuncfunc 10
read
read命令接收标准输入(键盘)的输入,或者其他文件描述符的输入。得到输入后,read命令将数据放入一个标准变量中。
格式:
read VAR_NAME
read如果后面不指定变量,那么read命令会将接收到的数据放置在环境变量REPLY中
read -p "Enter your name" VAR_NAME# -p 表示输入时的提示字符串
read -t 5 -p "Enter your name" VAR_NAME# -t 表示输入等待时间
read -s -p "Enter your password" VAR_NAME# -s 表示安全输入,键入密码时不会显示
declare
用来限定变量的属性
-r 只读
-i 整数:某些算术计算允许在被声明为整数的变量中完成,而不需要特别使用expr或let来完成。
-a 数组
示例:
只读
整数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iiHCmeDa-1647676741351)(img/image-20210823095458130.png)]
数组
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pE9kkPEU-1647676741352)(img/image-20210823095659401-1629724340786.png)]
字符串操作
获取长度
${#VAR_NAME}
字符串截取
${variable:offset:length} 或者 ${variable:offset}
尾部截取字符串
${variabel: -length} 冒号后有个空格
大小写转换
小->大
${variabe^^}
大->小
${vaiable,}
示例:
数组
定义:declare -a:表示定义普通数组
特点
- 支持稀疏格式
- 仅支持一维数组
数组赋值方式
a[0]=$RANDOM # 一次对一个元素赋值a=(a b c d) # 一次对多个元素赋值a=([0]=a [3]=b [1]=c) # 按索引进行赋值
使用read命令read -a ARRAY_NAME查看元素
${ARRAY[index]} #查看数组指定角标的元素${ARRAY} # 查看数组的第一个元素${ARRAY[*]} 或者 ${ARRAY[@]} # 查看数组的所有元素
获取数组的长度
${#ARRAY[*]}${#ARRAY[@]}
获取数组内元素的长度
${#ARRAY[0]}
注意:${#ARRAY[0]}表示获取数组中的第一个元素的长度,等于${#ARRAY}
从数组中获取某一片段之内的元素(操作类似于字符串操作)
${ARRAY[@]:offset:length}# offset:偏移的元素个数# length:取出的元素的个数# ${ARRAY[@]:offset:length}:取出偏移量后的指定个数的元素# ${ARRAY[@]:offset}:取出数组中偏移量后的所有元素
数组删除元素
unset ARRAY[index]
示例:
[root@localhost day01]# declare -a arr2[root@localhost day01]# arr2=(a b c d)[root@localhost day01]# arr2[1]=x[root@localhost day01]# echo ${arr2[*]}a x c d[root@localhost day01]# echo ${#arr2[*]}4[root@localhost day01]# echo ${arr2[*]:1}x c d[root@localhost day01]# unset arr2[2][root@localhost day01]# echo ${arr2[*]}a x d[root@localhost day01]#
其他命令
date
显示当前时间
- 格式化输出 +%Y-%m-%d %H:%M:%S
- 格式%s表示自1970-01-01 00:00:00以来的秒数
- 指定时间输出 --date=‘2009-01-01 11:11:11’
- 指定时间输出 --date=‘3 days ago’ (3天之前,3天之后可以用-3)
示例:
[root@localhost day01]# date2021年 08月 23日 星期一 10:31:06 CST[root@localhost day01]# echo `date "+%Y-%m-%d %H:%M:%S"`2021-08-23 10:35:38[root@localhost day01]# echo `date +%s`1629686170[root@localhost day01]# [root@localhost day01]# echo `date --date='2020-10-01 10:10:10'`2020年 10月 01日 星期四 10:10:10 CST[root@localhost day01]# echo `date --date='3 days ago'`2021年 08月 20日 星期五 10:38:23 CST[root@localhost day01]#
后台运行脚本
在脚本后面加一个&
test.sh &
这样的话虽然可以在后台运行,但是当用户注销(logout)或者网络断开时,终端会收到Linux HUP信号(hangup)信号从而关闭其所有子进程
nohup命令
不挂断的运行命令,忽略所有挂断(hangup)信号
nohup test.sh &
- nohup会忽略进程的hangup挂断信号,所以关闭当前会话窗口不会停止这个进程的执行。
- nohup会在当前执行的目录生成一个nohup.out日志文件
标准输入/输出重定向
command > file | 将输出重定向到 file。 |
---|---|
command < file | 将输入重定向到 file。 |
command >> file | 将输出以追加的方式重定向到 file。 |
n > file | 将文件描述符为 n 的文件重定向到 file。 |
n >> file | 将文件描述符为 n 的文件以追加的方式重定向到 file。 |
n >& m | 将输出文件 m 和 n 合并。 |
n <& m | 将输入文件 m 和 n 合并。 |
<< tag | 将开始标记 tag 和结束标记 tag 之间的内容作为输入。 |
crontab 定时器
基本格式 :
* * * * * command# 分 时 日 月 周 命令# 第1列表示分钟1~59 每分钟用*或者 */1表示# 第2列表示小时1~23(0表示0点)# 第3列表示日期1~31# 第4列表示月份1~12# 第5列标识号星期0~6(0表示星期天)# 第6列要运行的命令
-
星号(*):代表所有可能的值,例如month字段如果是星号,则表示在满足其它字段的制约条件后每月都执行该命令操作。
-
逗号(,):可以用逗号隔开的值指定一个列表范围,例如,“1,2,5,7,8,9”
-
中杠(-):可以用整数之间的中杠表示一个整数范围,例如“2-6”表示“2,3,4,5,6”
-
正斜线(/):可以用正斜线指定时间的间隔频率,例如“0-23/2”表示每两小时执行一次。同时正斜线可以和星号一起使用,例如*/10,如果用在minute字段,表示每十分钟执行一
编写定时任务
crontab -e
列出目前的定时任务
crontab -l
删除定时任务
crontab -l
示例:
# 每分钟执行执行一次* * * * * /bin/ls# 在 12 月内, 每天的早上 6 点到 12 点,每隔 3 个小时 0 分钟执行一次 /usr/bin/backup:0 6-12/3 * 12 * /usr/bin/backup# 周一到周五每天下午 5:00 寄一封信给 alex@domain.name:0 17 * * 1-5 mail -s "hi" alex@domain.name < /tmp/maildata# 每月每天的午夜 0 点 20 分, 2 点 20 分, 4 点 20 分....执行 echo "haha":20 0-23/2 * * * echo "haha"
具体实例:
# 每两个小时重启一次apache 0 */2 * * * /sbin/service httpd restart# 每天7:50开启ssh服务 50 7 * * * /sbin/service sshd start# 每天22:50关闭ssh服务 50 22 * * * /sbin/service sshd stop# 每月1号和15号检查/home 磁盘 0 0 1,15 * * fsck /home# 每周一至周五3点钟,在目录/home中,查找文件名为*.xxx的文件,并删除4天前的文件。0 3 * * 1-5 find /home "*.xxx" -mtime +4 -exec rm {} \;
**注意:**当程序在你所指定的时间执行后,系统会发一封邮件给当前的用户,显示该程序执行的内容,若是你不希望收到这样的邮件,请在每一行空一格之后加上 > /dev/null 2>&1 即可,如:
# 每两个小时重启一次apache 0 */2 * * * /sbin/service httpd restart > /dev/null 2>&1
ps
ps:用来显示进程的相关信息
ps 显示当前shell启动的所有进程
ps -e 显示系统中所有进程
ps -ef|grep java 查找所有的java进程
shell应用实例
根据时间创建文件夹
需求:创建10个目录,目录名称以当天时间开头,后面拼上目录编码
例如:1970-01-01_1
#!/bin/bashmkdir -p /home/shell/testcd /home/shell/testfor i in {1..10}do date=`date +%Y-%m-%d` dirname=${date}_$i echo $dirname mkdir $dirnamedone