什么是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
1.bash/sh 脚本文件名(.sh文件)
脚本可以没有执行权限,脚本内容没有指定解释器
可以接收标准输入------输入重定向/管道符
2.路径执行(绝对路径/相对路径)
shell脚本文件必须有可执行权限,可以不用指定解释器
绝对路径:……/basename
相对路径:./basename
3.source 文件名
读入或者加载文件内容到父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结尾时为了更好区分
在Linux中变量分为两类:
环境变量(全局变量):
可以在当前shell和派生的任意子进程生效
定义全局变量:export NAME=……
Linux中的变量命名:数字、字母、下划线组成,不能以数字开头
普通变量(局部变量):
在创建的shell函数或者脚本中定义的变量,在本身函数或者脚本的任何位置都可以使用,只能在脚本或者函数中生效
对于bash派生出来的子进程不生效
对于bash的子进程也不生效
在bash定义的变量,在bash退出后,下次链接时变量失效
可以将变量写入环境变量中,下次连接时依旧生效
根据环境变量的加载顺序可以指定用户能否使用
在/etc/bashrc文件中的变量所有人都可以使用
在bash命令行定义的变量是局部变量,在前面加上export后为定义全局变量
将shell中的普通变量变为全局变量
. 文件名,将文件加载到环境变量中,文件中的局部变量也被加载到环境变量中,变成了全局变量
环境变量初始化与对应文件生效顺序
系统运行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内任意单个数字
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)}' 外部命令,效率最低
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中条件测试的方法一般有五种
语法:四种语法如下
在Linux中shell脚本常用的条件测试一般使用下列选项进行判断文件
常用选项
选项 | 说明 |
---|---|
-f | 判断文件存在 |
-d | 判断目录是否存在 |
-e | 判断文件或者目录是否存在 |
-r | 判断文件是否有读权限 |
-s | 文件存在并且大小不为0,即空文件返回真 |
-w | 判断文件是否有写权限 |
-x | 判断文件是否有执行权限 |
-L | 文件存在,且链接文件为真,返回真 |
file1 -nt file2 | 判断file1比file新,返回真 |
file1 -ot file2 | 判断file1比file旧,返回真 |
test命令是对文件或者目录进行条件测试的命令
语法 | 作用 |
---|---|
-n “str” | 字符串长度不为0,返回真,判断变量必须加"" |
-z “str” | 字符串长度为0,返回真,判断变量必须加"" |
“str1” == “str2” | 判断字符串是否相等,相等返回真,变量加"" |
“str1” == “str2” | 判断字符串是否不相等,不相等返回真,变量加"" |
对于文件和字符串的操作上述说到可以使用上面的操作进行,而对于整数的二元比较操作可以使用下列选项进行操作
选项 | 说明 | 在(())和[[]]中一般使用 |
---|---|---|
-eq | 判断是否相等 | == |
-ne | 判断是否不相等 | != |
-gt | 大于 | > |
-ge | 大于等于 | >= |
-lt | 小于 | < |
-le | 小于等于 | <= |
对于(())、[ ]、[[ ]]的使用区别
(( ))中只能进行数值运算,不能使用-eq的写法,可以使用>、<的写法
[ ]中用类似>、<的写法在语法上虽然可能没错,但逻辑结果不对,可以使用=、!=正确比较,可以使用通配符
[[ ]]中用类似-eq等的写法是对的,[]中用类似>、<的写法也可能不对,有可能会只比较第一位,逻辑结果不对
整数加双引号的比较是对的
[[ ]]是扩展的test命令,其语法更丰富也更复杂。对于实际工作中的常规比较,不建议使用,会给Shell学习带来很多麻烦,除非是特殊的正则匹配等,在无法使用的场景下才会考虑使用[]
符号 | 说明 |
---|---|
-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判断,两种判断各有特点
if判断有单分支、双分支和多分枝结构,if判断有两种写法
1.单分支结构
if <条件>
then
执行内容
fi
或者
if <条件> ; then
执行内容
fi
2.双分支结构
if <条件>
then
执行内容
else
执行内容
fi
3.三分支结构
if <条件>
then
执行内容
elif <条件>
then
执行内容
else
执行内容
fi
使用if判断时要注意语法补全和首行缩进
语法:
case 变量 in
1)
执行内容
;;
2)
执行内容
;;
3)
执行内容
;;
……
*)
执行内容
;;
esac
但所选择的变量符合条1、2、3的时候执行对应的内容,当什么都不符合的时候就执行*)对应的内容
使用case进行判断时需要注意语法和缩进
shell也有属于自己的循环判断,在shell中的循环方式有三种for、while、until
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
until语法:
until <>
do
循环体
done
直到条件成立才结束,即条件成立为假
示例
n=0
until (($n>10))
do
echo $n
let n++
done
until循环与while循环类似,判断条件的方式相反而已
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 $? #接收返回值
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往后全取
与变凉切片类似