速成shell脚本教程,一起来看!!!

shell脚本

  • 一、简介
  • 二、shell脚本的运行方式
  • 三、shell注释
  • 四、shell脚本文件的书写规范
  • 五、shell脚本中的变量
  • 六、特殊符号
  • 七、变量子串
    • 7.1扩展特殊变量
  • 八、数值运算:用两个小括号括(())起来
  • 九、条件测试
    • 9.1test命令测试
    • 9.2文件测试
    • 9.3字符串判断方法
    • 9.4二元比较操作
    • 9.5逻辑运算符
  • 十、条件判断
    • 10.1if判断
    • 10.2case判断
  • 十一、循环
    • 11.1while循环
    • 11.2until循环
    • 11.3for循环
  • 十二、循环控制
  • 十三、函数
  • 十四、数组

一、简介

什么是shell?
shell是一个命令解释器,在Linux系统中shell是用户和内核的接口,通常被称为壳。

什么是shell脚本?
shell脚本就是一个包含一系列命令的文件,shell读取这个文件,按照文件中的顺序执行文件中的命令,就像在命令行输入命令一样。

shell脚本的好处

  • 系统批量管理
  • 实现自动化,提高运维效率
  • 对系统做监控

shell脚本的编程语言
Linux中的shell有很多种各有优缺点

  • Bourne-Again Shell(bash)是Linux中最常用的shell类型,是Bourne shell升级版本,包含了c shell和k shell的很多优点

  • C shell是一种更适合编程的shell语言,语法与C语言类似

  • K shell集合了C shell和Bourne shell的优点,支持任务控制
    Linux中的shell还有很多,bash、C shell、K shell是比较常用的几个shell

  • 我们最常用的shell是bash

  • 查看Linux系统版本:cat /etc/redhat-release

  • 查看系统bash版本:bash --version

  • 查看bash是否需要升级:env x='(){:;} echo be careful ’ bash -c “echo this is a test”

  • 返回值为be careful this ia a test 则需要升级系统

  • 升级bash命令:yum update bash -y

  • 查看系统shell:cat /etc/shells

  • 查看系统默认shell:echo $SHELL

shell脚本在运行的时候会先加载环境变量,加载了环境变量之后才会执行脚本文件,加载环境变量的顺序如下:

shell加载环境变量的顺序分为两类
登录式shell:

	su -  username  echo $PATH  有用户名和账户输入的shell
	/etc/profile  /etc/profile.d/*.sh  ~/.bash_profile  ~/.bashrc  /etc/bashrc,

非登录式shell

	su username     echo $PATH  
	计划任务crontab本身就是非登录式shell(可能会丢失环境变量)
	ansible的playbook			/etc/profile  /etc/profile.d/*.sh  ~/.bash_profile  ~/.bashrc  /etc/bashrc

二、shell脚本的运行方式

1.bash/sh 脚本文件名(.sh文件)
	脚本可以没有执行权限,脚本内容没有指定解释器
	可以接收标准输入------输入重定向/管道符
2.路径执行(绝对路径/相对路径)
	shell脚本文件必须有可执行权限,可以不用指定解释器
	绝对路径:……/basename
	相对路径:./basename
3.source 文件名
	读入或者加载文件内容到父shell
	相当于加载到环境变量

三、shell注释

shell脚本文件中的注释分为两类:
单行注释:

在行首加上#
但是在第一行的行首加上#代表的是魔幻符
shell只有在第一行加上的#!代表的是声明解释器
其他行都是注释

多行注释

:<

四、shell脚本文件的书写规范

1.开头指定解释器
	第一行#!/bin/nash
	不指定就会使用系统默认的shell解释器
2.开头加版本权限信息
	#!/bin/bash						解释器
	#Author:zhang 					作者
	#Blog:https://blog.csdn.net/m0_51141557?spm=1011.2124.3001.5343		作者博客
	#date:	2021年1月9日15:06:32		时间
	#Function:						作用
	#Version:						版本
	#Mail:							联系方式
3.注释规范
	脚本中尽量不要使用中文注释
	使用中文注释在切换系统时可能会出错
	注释要占文件内容的30%
	单行注释可以放在代码的尾部或者上部
	多行注释放在程序体中或者头部
4.多使用内部命令
	内部命令效率高
	type命令:查看命令时内部命令还是外部命令
5.没必要使用cat命令,少用管道符,影响脚本速度
	cat可以使用grep guru代替
6.代码缩进
	可以使用vim的环境变量实现
7.学会处理报错
有时候我们修改了某个错误并再次运行后,系统依旧会报错。然后我们再次修改,但系统再次报错。这可能会持续很长时间。但实际上,旧的错误可能已经被纠正,只是由于出现了其它一些新错误才导致系统再次报错
8.shell脚本以.sh结尾

**在Linux中文件内容和文件名内关系,与Windows上不同,.sh结尾时为了更好区分

五、shell脚本中的变量

在Linux中变量分为两类:

环境变量(全局变量):
	可以在当前shell和派生的任意子进程生效
	定义全局变量:export NAME=……
	Linux中的变量命名:数字、字母、下划线组成,不能以数字开头
普通变量(局部变量):
	在创建的shell函数或者脚本中定义的变量,在本身函数或者脚本的任何位置都可以使用,只能在脚本或者函数中生效
	对于bash派生出来的子进程不生效
	对于bash的子进程也不生效
	在bash定义的变量,在bash退出后,下次链接时变量失效
	可以将变量写入环境变量中,下次连接时依旧生效
	根据环境变量的加载顺序可以指定用户能否使用
	在/etc/bashrc文件中的变量所有人都可以使用
	
在bash命令行定义的变量是局部变量,在前面加上export后为定义全局变量

将shell中的普通变量变为全局变量
. 文件名,将文件加载到环境变量中,文件中的局部变量也被加载到环境变量中,变成了全局变量

  • set:查看所有变量
  • env:查看全局变量
  • declare:查看所有函数,变量和已经导出的变量
  • 双引号+反单引号:可以解析 shell命令
  • echo命令中-e选项使用\转义
  • 一般添加环境变量不在/etc/profile中添加,改文件是系统自己设置的
    一般在/etc/profile.d/目录下添加.sh结尾的文件

环境变量初始化与对应文件生效顺序

系统运行shell的方式:
	1.通过系统用户登录运行默认的shell
	2.交互式运行shell
	3.执行脚本运行非交互式的shell
	
可以在脚本中加入两句话使其按照自己指定的方式加载变量
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usrlocal/sbin:~/bin
export PATH

shell脚本中常用的生成数

生成自然数
	[root@localhost ~]# echo {0..10}
	0 1 2 3 4 5 6 7 8 9 10
	
	写一个简单shell脚本
	[root@localhost tmp]# cat file.sh
	#!/bin/bash   #bash
	
	read -p "please input digit:  " one   #输入第一个数
	read -p "please input digit:  " two   #输入第二个数
	
	echo {$one..$two}                  #输出one到two的所有自然数
	运行后
	[root@localhost tmp]# sh file.sh
	please input digit:  2
	please input digit:  5
	{2..5}
	明显发现没有解析完,不是我们要的结果,此时可以将shell脚本中的echo命令前面加上eval变为 eval echo {$one..$two},再次运行结果为
	[root@localhost tmp]# sh file.sh
	please input digit:  2
	please input digit:  5
	2 3 4 5
	这里的eval后面跟shell命令,对command扫描多次,解析多次
	set 11 22 33 44(定义四个未知参数)
	echo $1 --->11
	echo $2---->22
	……
	echo $# --->4(set了四个参数)

这里有一个命令seq
seq:用于产生从某一个数到另外一个数的所有整数

常用参数
	-f%#g		输出内容占#个字符,右对齐
	-f%-#g		输出内容占#个字符,左对齐
	-f%mng		输出内容占n位,用m填充空余部分,右对齐
	-fstr%#g	输出内容占#个字符,右对齐,前面用str拼接
	-w			按照最高位数补全
	-s				指定分隔符,使用反单引号
		"`echo -e "\t"`"(用tab隔开)

seq示例:

[root@localhost tmp]# seq 10				1到10的所有自然数(包括1)
1
2
3
4
5
6
7
8
9
10
[root@localhost tmp]# seq 10 20				10到20的所有自然数(包括10)
10
11
12
13
14
15
16
17
18
19
20
[root@localhost tmp]# seq 2 2 20			2到20的所有偶数(包括2)
2
4
6
8
10
12
14
16
18
20
[root@localhost tmp]# seq 1 2 20			2到20的所有奇数(包括1)
1
3
5
7
9
11
13
15
17
19

生成随机数

echo $RANDOM
范围是0~32767

生成400000~500000的随机数
可以写一个shell脚本
#!/bin/bash								解释器
function rand(){						定义一个函数,利用RADDOM得到的随机数进行运算得到自己想要的数
	min=$1								
	max=$(($2-$min+1))
	num=$(($RANDOM+100000000))
	echo $(($num%$max+$min))
}
rnd=$(rand 400000 500000)				调用函数运算得到400000~500000的数赋值给rnd
echo $rnd								输出rnd(得到的数)
exit 0									结束
执行后结果为
[root@localhost tmp]# sh test.sh
420186
[root@localhost tmp]# sh test.sh
409856
还有很多方法,这里不再多说

shell脚本中命令的执行顺序

;没有逻辑判断,执行命令有顺序
&&:与,有逻辑,必须前后两个条件都满足才为真
||:或,有逻辑,只要满足一个条件就为真
&&和||的返回值为 真或者假

shell通配符

*		任意长度任意字符
?		任意单个字符
[]		指定范围内的单个字符
[^]		指定范围外的任意单个字符
[a-z]	a-z内任意单个字符,不区分大小写
[0-9]	0-9内任意单个数字
  • cp tcp.dump{,.bak} ------>将tcp.dump文件复制,复制后文件名为tcp.dump.bak
  • cp tcp.{dump,.bak} ------>将tcp.dump文件复制,复制后文件名为tcp…bak

六、特殊符号

sh test.sh 1 2 3 4 ... 10
$0	当前执行脚本的文件名(有路径,包含路径)
$1	第一个未知参数
$n	第n个未知参数,只能是0~9,如果是$123就会变成$1后面拼接23
$*	打印所传的参数,加引号后将所传的所有参数当做一个参数
$#	所带参数的个数
$@	与不带引号的$*相同,加不加引号不受影响
${123} 大于两位数就要用大括号括起来
$?
	判断命令、脚本或函数等程序是否执行成功
	成功返回0,失败返回1
	若在脚本中调用执行“exit 数字”,则会返回这个数字给“$?”变量
	判断命令、脚本或函数等程序是否执行成功。
	若在脚本中调用执行“exit 数字”,则会返回这个数字给“$?”变量。如果是在函数里,则通过“return数字”把这个数字以函数返回值的形式传给"$?"
$$	获取当前执行shell的进程号
$_	获取上一条命令或者脚本的最后一个参数
$!	获取上一个在后台工作的进程的进程号

shell 中数字运算带两个小括号(())---格式

exec结束后会杀掉当前进程

read接收用户输入的参数
	-p “提示”  (后面用引号引起来,内容是给用户的提示)

shift(将全部参数向左移动一个位置,相当于删除第一个位置的参数,其他的向上补位)
	
exit 退出
可以选择一个数作为返回状态(数字,最大118)

七、变量子串

name="param"
echo $name等价于echo ${name}
echo ${#name}:打印变量长度
echo ${name:o}(o为数字,表示从第几个后面开始取)
echo ${name:o:n}(o,n都为数字,表示从第o-1开始取长度为n的字符串)
echo ${name#word}(从开头开始删除第一个word匹配到的字符串)
echo ${name#word*word}(最短匹配最短的字符串并删除)
echo ${name##word*word}{最长匹配}
echo ${name%word}(从右往左与#类似)
#必须以第一个字符开头
%必须以最后一个字符结尾
一个为匹配最短,两个为匹配最长,最短最长只在*时使用
echo ${name/old/new}(替换,将第一个匹配到的old替换为new)
echo ${name//old/new}(替换,匹配到的字符串全部替换)

统计字符串长度的方法
	echo ${#name}		效率最高
	expr length "$name"   仅次于第一个
	echo ${name}  |wc -L	内部命令
	echo ${name}  |awk '{print length($0)}' 外部命令,效率最低

7.1扩展特殊变量

echo ${name:-wangwu}(定义值的时候输出定义的值,变量没有定义值的时候,输出默认的值,但是不定义给变量)
[root@localhost tmp]# name=""
[root@localhost tmp]# echo ${name:-wangwu}
wangwu
	
name=""
echo ${name:=wangwu}(定义值输出定义的,没定义输出默认,并且将默认值赋值给变量)
[root@localhost tmp]# name=""
[root@localhost tmp]# echo ${name}
						空
[root@localhost tmp]# echo ${name:=wangwu}
wangwu
[root@localhost tmp]# echo ${name}
wangwu
name=""
echo ${name:+wangwu}(name定义了值输出wangwu,没定义输出为空,不讲wangwu赋值给name)
[root@localhost tmp]# name=""
[root@localhost tmp]# echo ${name:+wangwu}
					空
[root@localhost tmp]# echo ${name}
					空
[root@localhost tmp]# name="zhangsan"
[root@localhost tmp]# echo ${name:+wangwu}
wangwu
[root@localhost tmp]# echo ${name}
zhangsan
[root@localhost tmp]# echo ${name}
zhangsan
[root@localhost tmp]# echo ${name:+wangwu}
wangwu
[root@localhost tmp]# echo ${name}
zhangsan
name=""
echo ${name:?……}
name定义了值输出定义的值
没有定义输出?后面……的提示
判断变量有没有被赋值
[root@localhost tmp]# name=""
[root@localhost tmp]# echo ${name:?xxxxxxx}
-bash: name: xxxxxxx
[root@localhost tmp]# echo ${name}
空
[root@localhost tmp]# name="zhangsan"
[root@localhost tmp]# echo ${name:?xxxxxxx}
zhangsan
	
示例:
删除七天前的文件
	shell脚本文件
	DIRNAME=/data/
	find ${DIRNAME:-/tmp/} -mtime +4 -exec rm -rf {} \;
	
	需要用到的命令
		创建带时间戳的文件:
			touch `date "+%Y%m%d"`.tar.gz
		目录打包归档:
			tar -czvf /data/`date "+%Y%m%d"`.tar.gz /root/
		调整系统时间:
			date -s "20210111"
		删除查找的文件:
			find /data/ -mtime +4 -exec rm-rf {} \;
			find /data/ -mtime +4 |xargs rm -rf
			xargs 接收标准输出,作为后面命令的标准输入
			rm命令不能接收标准输入

八、数值运算:用两个小括号括(())起来

+、-、*、/、%、**、++、--、!、&&、||、<、<=、>、>=、==、!=、=、<<、>>、~、|、&、^、=、+=、-=	、*=、/=	、%=	、(())
	let :后面可以直接写表达式,用于整数运算,类似于(())
	expr:使用expr计算的时候运算符号必须前后以空格分开,除了整数运算还有其他功能
		整数计算
		字符串匹配
		字符串长度
	bc:Linux下的一个计算器
	$[]:用于整数运算
	awk:可以用于整数运算,也可以用于小数运算
	declare:定义变量的值和属性,-i参数可以用于定义整型变量,做运算
	
	
四则运算:
sulum(){
	 if [ $# -eq 2 ]
	then
		echo $(($1+$2))
		echo $(($1-$2))
		echo $(($1*$2))
		echo $(($1/$2))
		return 50
	else
		echo "USER:$0 var1 var2"
		#exit 100
		return 111
	fi
	}
	sulum num1 num2
	echo $?

九、条件测试

在Linux中条件测试的方法一般有五种

9.1test命令测试

语法:四种语法如下

  • test <测试表达式>
  • [<测试表达式>]
  • [ [ <测试表达式>] ]
  • ((<测试表达式>))

9.2文件测试

在Linux中shell脚本常用的条件测试一般使用下列选项进行判断文件
常用选项

选项 说明
-f 判断文件存在
-d 判断目录是否存在
-e 判断文件或者目录是否存在
-r 判断文件是否有读权限
-s 文件存在并且大小不为0,即空文件返回真
-w 判断文件是否有写权限
-x 判断文件是否有执行权限
-L 文件存在,且链接文件为真,返回真
file1 -nt file2 判断file1比file新,返回真
file1 -ot file2 判断file1比file旧,返回真

test命令是对文件或者目录进行条件测试的命令

9.3字符串判断方法

语法 作用
-n “str” 字符串长度不为0,返回真,判断变量必须加""
-z “str” 字符串长度为0,返回真,判断变量必须加""
“str1” == “str2” 判断字符串是否相等,相等返回真,变量加""
“str1” == “str2” 判断字符串是否不相等,不相等返回真,变量加""

9.4二元比较操作

对于文件和字符串的操作上述说到可以使用上面的操作进行,而对于整数的二元比较操作可以使用下列选项进行操作

选项 说明 在(())和[[]]中一般使用
-eq 判断是否相等 ==
-ne 判断是否不相等 !=
-gt 大于 >
-ge 大于等于 >=
-lt 小于 <
-le 小于等于 <=

对于(())、[ ]、[[ ]]的使用区别

(( ))中只能进行数值运算,不能使用-eq的写法,可以使用>、<的写法
[ ]中用类似>、<的写法在语法上虽然可能没错,但逻辑结果不对,可以使用=、!=正确比较,可以使用通配符
[[ ]]中用类似-eq等的写法是对的,[]中用类似>、<的写法也可能不对,有可能会只比较第一位,逻辑结果不对
整数加双引号的比较是对的
[[ ]]是扩展的test命令,其语法更丰富也更复杂。对于实际工作中的常规比较,不建议使用,会给Shell学习带来很多麻烦,除非是特殊的正则匹配等,在无法使用的场景下才会考虑使用[]

9.5逻辑运算符

符号 说明
-a 逻辑与,在(( ))和[[ ]]中使用&&
-o 逻辑或,在(( ))和[[ ]]中使用 | |
! 逻辑非,在(( ))和[[ ]]中使用 !

在此为了方便使用,我们可以在下列环境使用对应的条件,既方便,又不会出错

  • 数值比较:-eq -ne -lt -gt -le -ge -a -o !
  • 字符串比较:= == !=

示例:

1.test -f/e/d filename判断
[root@localhost tmp]# test -f file
[root@localhost tmp]# echo $?
0
[root@localhost tmp]# test -f test
[root@localhost tmp]# echo $?
1	
	
2.[]一个中括号判断(不能使用> < = && ||,使用 -a -o -ht -lt替代)
[root@localhost tmp]# [ -f test ] && echo "ok" || echo "no ok"
no ok
[root@localhost tmp]# [ -f file ] && echo "ok" || echo "no ok"
ok

3.[[]]两个中括号判断(可以使用通配符,可以使用> < = && ||)
[root@localhost tmp]# [[ -f file ]] && echo "ok" || echo "no ok"
ok
[root@localhost tmp]# [[ -f test ]] && echo "ok" || echo "no ok"
no ok

4.(())两个小括号判断(只能做数值运算)
[root@localhost tmp]# echo $((2>1))
1
[root@localhost tmp]# echo $((2<1))
0

十、条件判断

在Linux中shell脚本判断有两大类,一种是if’判断,一种是case判断,两种判断各有特点

10.1if判断

if判断有单分支、双分支和多分枝结构,if判断有两种写法
	1.单分支结构
	if <条件>
	then
		执行内容
	fi
	或者
	if <条件> ; then
		执行内容
	fi
	2.双分支结构
	if <条件>
	then
		执行内容
	else
		执行内容
	fi
	
	3.三分支结构
	if <条件>
	then
		执行内容
	elif <条件>
	then
		执行内容
	else
		执行内容
	fi
	
使用if判断时要注意语法补全和首行缩进

10.2case判断

语法:
	case  变量  in
		1)
			执行内容
			;;
		2)
			执行内容
			;;
		3)
			执行内容
			;;
		……
		*)
			执行内容
			;;
	esac
		
	但所选择的变量符合条1、2、3的时候执行对应的内容,当什么都不符合的时候就执行*)对应的内容
	使用case进行判断时需要注意语法和缩进

十一、循环

shell也有属于自己的循环判断,在shell中的循环方式有三种for、while、until

11.1while循环

while循环语法
while <条件>
do
	循环体
done
当条件不成立的时候结束循环
示例
	while遍历文件
	一行一行遍历
		exec < ip.txt
		while read line
		do
			sleep 1
			echo $line
		done
	或者
		while read line
		do
			sleep 1
			echo $line
		done 

11.2until循环

until语法:
	until <>
	do
		循环体
	done
	
	直到条件成立才结束,即条件成立为假
	示例
		n=0
		until (($n>10))
		do
			echo $n
			let n++
		done
	until循环与while循环类似,判断条件的方式相反而已

11.3for循环

for循环的语法有两种
	1)for 变量名 in 变量取值列表
	  do
		  循环体
	  done

	2)for ((exp1;exp2;exp3))
	  do
	  	  循环体
	  done
 示例
	1.遍历文件
	默认以空格为分隔符,一段一段遍历
		for i in `cat ip.txt`
		do
			sleep 1
			echo $i
		done
	可以修改$IFS变量来让其以其他符号分割	
		OLD_IFS=$IFS
		IFS=$'\n'
		n=0
		for i in `cat ip.txt`
		do
			sleep 1
			echo $i
			let n++
		done
		echo $n
		IFS=$OLD_IFS
	
	2.九九乘法表
		for i in {1..9}
		do
			for j in `eval echo {1..$i}`
			do
				echo -ne "$j*$i=$(($i*$j))\t"
			done
			echo -e "\n"
		done

十二、循环控制

在shell脚本中有着循环控制这种东西,将它加入到循环中可以使循环更加好运,更加灵活

命令 说明
break # #为1或者没有时跳出当前循环,进入下次循环, #为几跳出几层循环,跳出并结束第#层循环
continue # 跳过本次循环进行下次循环,与break不同, #为指定跳到第几层循环,进行下一次循环,可以结束程序
return # 返回值结束程序,可以通过$?接收返回值
exit # 以#的状态码退出,可以通过$?接收返回值

十三、函数

在shell脚本中也可以像c和java一样使用函数或者方法
函数的好处

  • 将功能模块化
  • 使功能重用
  • 代码块较少

在shell脚本中定义函数的方法如下

第一种
	TestFunc01(){
	echo "123"
	}

	TestFunc01			#调用函数
	
第二种
	function TestFunc02(){
	echo "456"
	}

	TestFunc02			#调用函数
	
第三种
	function TestFunc03
	{
	echo "789"
	}

	TestFunc03			#调用函数

有函数就有参数,在函数中传参也有shell特有的方法

传参
	TestFun(){
    echo -e "name: $1 \t age: $2"	#使用$1 $2..接收参数
	}

	TestFun zhangsan 18		#在调运函数后面直接加参数

既然有函数那就回有返回值,不是所有的函数都有返回值的,有返回值的函数,可以使用$?来接收返回值

TestFun(){
   echo -e "name: $1 \t age: $2"
   return "222"
}
TestFun zhangsan 18	#调用函数
echo $?				#接收返回值
  • 如果在脚本中想使用其他文件中定义的函数可以使用. /PATH/filename来调用
  • 但是调用脚本中的变量和函数名不能冲突

十四、数组

shell脚本也可以像c和java一样利用数组进行一些操作,在shell中数组的操作也有自己独有的方法

1.数组定义
	单个值:
		array[i]="zhangsan"
		array[j]="lisi"
	定义多个值
		array=(zhangsan "li si" 567 $name [20]="linux")
	
2.获取数组元素的个数
	echo ${#name[@]} 或者 echo ${#name[*]}
	输出数组中的所有元素
	echo ${name[@]}  或者 echo ${name[*]}
	输出第#个元素
	echo ${name[#]}
	获取第n个元素长度
	echo ${#name[n]}

2.数组遍历
	array=(`ls`)
	for i in ${array[@]}
	do
			echo $i
	done
	
	命令行输入格式
		for i in ${name[@]};do echo $i;done

	第二种方法
		array=(`ls`)
		for ((i=0;i<${#array[@]};i++))
		do
				echo ${array[$i]}
		done


3.增删改查
	查看数组
		declare -a 查看所有定义的数组
		
	修改数组
		declare -a name=([0]="name1" [1]="name2" ...)
	
	获取索引
		echo ${!name[@]}
		
	切片
		echo ${name[@]:n:m}
		从第n个开始取m个,没有m往后全取
		与变凉切片类似

你可能感兴趣的:(shell,运维,linux)