一、shell的基本知识
1、简介
shell编程可以简化日常的系统维护工作。shell特点是大部分都是linux命令,而且是面向过程的语言,不需要编译即可执行。
shell:实质上是一个命令解释器,它能够识别用户输出的各种命令,并传递给操作系统,他的作用类似于windows中的命令行。在UNIX或者localhost中,shell即是用户交互的界面,也是控制系统的脚本语言。
shell的分类:
Bourne Shell:标识为sh,该shell是root用户默认的shell。
Bourne-Again Shell:标识为bash,大多数的localhost发行版的默认是Shell。
Korn Shell:标识为ksh。
C Shell:标识为csh。
cat /etc/shells #查看当前系统支持的shell
echo $SHELL #查看当前所支持的shell
2、shell脚本语言的书写规范
通常会给shell脚本起名为filename.sh。
第一行写声明,声明当前shell脚本是用哪个命令解释器去解释:#!/bin/bash
除了第一行其他以#开头的都为注释
vim /root/.vimrc #自动生成脚本注释
autocmd BufNewFile *.py,*.cc,*.sh,*.java exec ":call SetTitle()"
func SetTitle()
if expand("%:e") == 'sh'
call setline(1,"#!/bin/bash")
call setline(2,"##############################")
call setline(3,"#fire name:".expand("%"))
call setline(4,"#version:v1.0")
call setline(5,"#email:admin@test.com")
call setline(6,"#create time".strftime("%F %T"))
call setline(7,"#description:")
call setline(8,"##############################")
call setline(9,"")
endif
endfunc
当新建.sh文件时会自动添加:
3、shell脚本的执行方式:
(1)交互式执行:
for filename in 'ls /etc'
>...
(2)作为程序文件执行(常用)
前两个会生成子进程,后两个不会。
bash ./filename.sh #生成子进程,并不在当前进程,所以只显示执行脚本的结果,不改变当前进程的状态。使用当前的bash shell去执行
./firename.sh #生成子进程,使用脚本里面指定的shell去运行,不过需要x权限
source ./firename.sh #不生成子进程,直接在当前进程中执行命令,执行结束后可能会改变当前进程。
. ./filename.sh #与上面source ./firename.sh作用相同
验证:
echo 'userdir=`pwd`' > demo03.sh #新建shell脚本,其内容是声明一个变量,存入pw的结果。
①bash demo03.sh
echo $userdir #输出变量的值,结果为空
②source demo03.sh
echo $userdir #输出变量的值,结果非空
shell脚本的退出状态码:0为成功,非0表示不成功 (退出状态码范围为0~255)
exit 0 #等同于c语言中的return 0
二、shell的变量
1、作用以及应用范例
变量的作用就是简化代码。
如:想要增加一个用户,那么就可以新增一个变量name,每次添加一个用户只需要修改变量名,不需要重新写一遍代码。
vim demo03.sh
name=test01
useradd $name
echo 123456 | passwd --stdin $name #非交互式设置用户密码
如果想要一次性添加多个用户,就可以使用for循环进行批量新建。
2、查看变量的值
①查看单个变量的值
echo $PATH
echo ${PATH}
printf "$PATH\n" #与echo的区别是不会换行
printf "${PATH}\n"
②查看多个变量的值
set #查看所有变量和函数
declare #查看所有变量和函数
env #显示所有的全局变量
set | less #翻页查看,可以使用vim命令
#在命令行中定义变量:退出当前进程变量就会失效,其他终端上无法使用变量
var = 'bianliang'
var = "`cmd`"
var = "$(cmd)"
在文件中定义变量:永久生效(定义后要重启终端就可以正常使用变量了)
当前用户生效的文件:~/.basnrc和~/.bash_profile
所有用户生效的文件:/etc/bashrc或/etc/profile或/etc/profile.d/*.sh(这个要加x权限)
③当我们执行shell脚本时,直接使用文件名运行会找不到命令,但是当我们将这个shell脚本文件放在/usr/bin目录下就可以直接在命令行中打出文件名就可以运行脚本了。
vim demo01.sh #创建脚本文件
#!/bin/bash
echo hello world #打印hello world
但是直接输入demo01.sh就不行
cp /shell/test02/demo01.sh /usr/bin #复制文件到只当文件夹中,或者建立软连接
(查看存放命令文件的目录echo $PATH)
ln -s /shell/test02/demo01.sh /usr/bin/demo01.sh #建立软连接,注意软连接的名称可有随便起。
④$:会将后面的内容作为一个变量,并且会引用变量的值。
转义字符\:会将后面跟着的特殊字符变为普通字符。
反单引号` `:在反单引号里面的内容当成是一段命令去执行。
单引号' ':会将处于单引号中的所有内容的特殊字符失效。
双引号" ":会将处于双引号中的内容的特殊字符失效,除了$,反单引号,\在双引号里面要保留自己的特殊含义。
注意:引号是就近匹配原则,如:' ' `ls` ' ' 这个命令中的两对单引号没有任何作用。
3、变量的分类
①局部变量:只能在当前进程使用的变量,其子进程不能使用
②全局变量(环境变量):可以创建他们的shell及其派生出来的子进程中使用(su切换用户是就会读取新的环境变量)
自定义环境变量:
export var或export var ="value"
declare -x var="value" #修改为全局变量
declare +x var #修改为局部变量
③特殊变量:
变量 | 说明 |
---|---|
$# | 命令后面跟的参数个数 |
$0 | 当前脚本的名称 |
$n | 第n个参数 |
$* | 以"参数一 参数二 参数三"的格式,返回所有的参数 |
$@ | 以"参数一""参数二""参数三"的格式,返回所有的参数 |
$? |
前一个命令、函数、脚本返回的状态码 |
$$ | 返回进程即PID |
④取消变量:unset var_name
4、变量的运算
设置变量:num1=123,num2=456,str1='i love you'。
(1)数值运算(与其它的语言相同)
①★$(()) #用于整数运算的常用运算符,在(())中使用变量是可以前面的$符号
例如:echo $((num1+num2)) #其结果为579
②let #用于整数运算时,使用let命令可以执行一个或者多个算术表达式,其中的变量名不需要使用$符号。
例如:let num3=num1+num2 #计算出的结果会存放num3中
echo $num3 #展示结果为579
③expr #用于整数运算,运算符的前后都要加上空格
例如:expr num1+num2 #会直接打印后面的式子,因为expr将其视作是字符串
expr num1 + num2 #会输出正确的结果,579
expr num1 + str1 #会有非整数参数的提示,原因是str1是字符类型的数据,不能跟整数相加减,并且返回状态码非零。但并不会报错。
#用expr统计字符串中字符的个数:expr length "$str1" 输出结果为10
④bc linux下的一个计算机程序,适合整数以及小数运算
例如:seq -s '+' 1 9 | bc #管道符前面的语句是胡输出一个从一加到九的式子,通过管道符传给计算器bc,结果会被输出45。
⑤$[] #与$(())的用法是一样的。
⑥awk #后面会专门讲到
例如:awk 'BEGIN {print 2+3*4}' #结果为10
echo ''6.222 3.111'' | awk '{print ($1-$2)}' #结果为3.111
⑦declare
例如:declare -i r=2+3 #将后面表达式的计算结果放到r中
echo $r #输出r的值
实例:输入两个实数,返回加减乘除除于等结果:
read -p "please input two number:" a b
echo "##############"
echo "a+b:$[a+b]"
echo "a-b:$[a-b]"
echo "a*b:$[a*b]"
echo "a/b:$[a/b]"
echo "a%b:$[a%b]"
echo "##############"
结果为:
(2)字符串运算
表达式 | 说明 |
---|---|
${filename} | 返回变量的内容 |
${#filename} | 返回变量的长度 |
${filename:num1} | 从位置num1开始提取字串 |
${filename:num1:num2} | 从位置num1到num2提取子串 |
${filename#word} | 从头开始匹配最短子串 |
${filename##word} | 从头开始匹配最长子串 |
${filename%world}和${filename%%word} | 从尾开始匹配最长/最短子串 |
${filename/str1/str2} | 将子串中的第一个str1修改为str2 |
${filename//str1/str2} | 将子串中的全部的str1修改为str2 |
filename=test.tar.gz #定义字符串变量
echo ${filename%%.*} #提取文件的名字
echo ${firename#*.} #提取文件的扩展名
实例1:写一个脚本,计算1到n的相加之和,n值由用户输入:
#!/bin/bash
read -p "please input a number:" n
sum=`seq -s "+" $n | bc`
echo $sum
实例2:写一个脚本,输入一个文件的绝对路径,输出文件的目录,名字,还有扩展名
#!/bin/bash
read -p "please input a string:" str
str2=${str##*/}
echo "output name:${str2%%.*}"
echo "output extension:${str#*.}"
echo "output content:${str%/*}"
实验3:写一个脚本,输入一个数字,返回数的位数
#!/bin/bash
read -p "please input a number:" num
echo `expr length $num`
实验4:写一个脚本,输入一个文件夹的绝对地址,输出文件夹中的文件个数
#!/bin/bash
read -p "please input a content:" content
sum=`ls $content | wc -l`
echo $sum
三、条件测试
1、条件语句的基本用法
条件测试语法 | 说明 |
---|---|
test <测试表达式> | test语句和测试语句中间至少有一个空格 |
[ <测试表达式> ] | 该方法与test命令一样,[]边界与测试表达式之间至少有一个空格 |
[[ <测试表达式> ]] | 与上面的语句的区别是在[[]]中可以使用通配符等进行模式匹配 |
((<测试表达式>)) | 一般用于if语句中,(())两端不需要括号,测试对象必须是整数 |
2、文件的测试表达式
常用的文件测试操作符:
-a/-e 文件 #文件是否存在
-b 文件 #文件是否存在,且为块文件,是返回0
-c 文件 #文件是否存在,且为字符文件,是返回0
-L 文件 #文件存在且为来链接文件则为真
-d 文件 #文件存在且为目录则为真
-f 文件 #文件存在且为普通文件则为真
-s 文件 #文件存在且大小不为零则为真
-u 文件 #文件是否设置suid位,如果设置了suid,则结果为0
-r 文件 #文件存在且可读则为真
-w 文件 #文件存在且可写则为真
-x 文件 #文件存在且可执行则为真
f1 -nt f2,nt为newer than # 文件f1比f2新则为真,根据文件的最新修改时间为准
f1 -ot f2,ot为older than #文件f1比f2旧则为真,根据文件的修改时间计算
测试:
touch file #新建一个普通文件
#前面语句与后面语句没有输入输出关系的时候,不能用管道符,只需要用分号;隔开即可。
test -f file; echo $? #判断file文件是否为普通文件
filename=/etc/nginx #定义临时变量
[ -d $filename ];echo $? #判断filename变量中存储的是否为目录
注意:如果测试的文件路径使用变量来代替的,那么一定要加上双引号。(最好所有的变量都加上双引号,最起码不出错!)
3、字符串测试表达式
常用的字符串测试操作符 | 说明 |
---|---|
-n "字符串" | 若字符串的长度不为0则为真,n可以理解为no zero |
-z "字符串" |
若字符串的长度为0则为真 |
"字符串1"="字符串2" | 若两个字符串相同则为真,等号左右要有空格 |
"字符串1"!="字符串2" | 若两个字符串不同则为真 |
注意:参数是变量的时候,一定要给变量加双引号。
4、整数测试表达式
在[]以及test中使用的比较符号 | 在[[]]和(())中使用的比较符号 | 说明 |
---|---|---|
-eq | ==或= | 相等,全拼为equal |
-ne | != | 不相等,全拼为no equal |
-gt | > | 大于,全拼greater than |
-ge | >= | 大于等于,全拼greater equal |
-lt | < | 小于,全拼less than |
-le | <= | 小于等于,全拼less equal |
5、逻辑运算符
在[]以及test中使用的比较符号 | 在[[]]和(())中使用的比较符号 | 说明 |
---|---|---|
-a | && | and,与,两端都为真则为真 |
-o | || | or,或,一个为真,则结果为真 |
! | ! | not,非,两端相反,则结果为真 |
四、流控制之条件判断
1、if单分支结构
第一种语法:
if <条件表达式>
then
指令
fi
第二种语法:
if <条件表达式>;then
指令
fi
测试1:
free -m | grep Mem | tr -s " " | cut -d " " -f 4 #获取mem的空闲内存,单位为Mb
说明:第一个命令是查看系统中的内存情况,并且以Mb为单位
第二个命令是管道符过滤出Mem的信息
第三个命令是将过滤出的信息进行加工,字符串之间的间隙设置为一个空格
第四个命令是获取第四个字符串
(或使用awk命令:free -m | awk '/Mem:/ {print $4}')awk会将多个空格默认成一个空格,并且各个字符串就是以空格分开的。
举一反三:
echo `ls -l | awk '/-/ {print $5}'` #输出以-为开头的所有行的第五列
crontab -e #循环执行例行性检查
*/10 * * * * /test/free_mem.sh &> /dev/null #每十分钟检查一次内存是否小于100MB
判断当前的执行者是谁:
whoami
echo $USER #会返回名字
id -u
echo $UID #会返回数字,0代表是root用户,不是零为普通用户。
测试2:
vim demo02.sh #新建脚本文件
#!/bin/bash if [ "$UID" != 0 ] then echo "please switch user root" fi
chmod a+rx demo02.sh #加权限
./demo02.sh #测试结果
2、if的双分支结构
语法格式:
if <条件表达式>
then
语句1
else
语句2
fi
测试1:
判断进程是否运行:
方法一:查看进程
ps -ef |grep sshd |grep -v grep #过滤没有sshd的进程,再过滤没有grep的进程
ps -ef |grep sshd |grep -v grep |wc -l #统计符合条件的进程的数量
方法二:查看端口
ss -lntup |grep -w 22 | wc -l
netstat -lntup |grep -w 22 |wc -l #统计使用22端口的进程个数
#写一个脚本,统计某个程序的进程个数,输入一个程序的名字,输出该程序的进程个数
#!/bin/bash read -p "please input a program:" program sum=`ps -ef |grep $program |grep -v \grep |wc -l ` if [ sum = 0 ] then echo "no process" else echo "have $sum process" fi
测试:
测试2:
检测主机存活可以使用ping 命令进行检测
ping -c 2 -w 1 192.168.68.99 &> /dev/null #测试网络可达性,并且将显示的结果去除
#写一个脚本测试主机是否存活
#!/bin/bash ping -c 2 192.168.68.2 > /etc/null if [ "$?" -eq 0 ] then echo host is runing else echo host is not runing fi
3、if多分支结构
语法格式:
if 条件表达式
then
命令序列
elif 条件表达式
then
命令序列
elif 条件表达式
then
命令序列
else
命令序列
fi
测试1:
#比较两个数的大小
#!/bin/bash read -p "please input two number:" a b if [ $a -eq $b ] then echo "$a equal $b" elif [ $a -gt $b ] then echo "$a greater than $b" else echo "$a less than $b" fi
测试2:
#判断字符的种类
#!/bin/bash read -p "please input a char:" char if echo "$char"|grep "[a-zA-Z]" > /dev/null then echo "this is a letter" elif echo "$char"|grep "[0-9]" > /dev/null then echo "this is a number" else echo "this is other" fi
测试3:
cat /proc/cpuinfo |grep vendor_id |head -1 |tr -s " " |cut -d " " -f 2
cat /proc/cpuinfo |awk '/vendor_id/ {print $3}' |head -1 #切出cpu厂商的名字
#写一个脚本,判断cpu的厂商信息
#!/bin/bash vendor=`cat /proc/cpuinfo |awk '/vendor_id/ {print $3}' |head -1` if [ "$vendor" = GenuineIntel ] then echo "intel" elif [ "$vendor" = AuthenticAMD ] then echo "AMD" else echo "unknown" fi
4、多条件判断语句case
语法格式:
case 变量名 in
值1)
指令1
;;
值2)
指令2
;;
值3)
指令3
;;
*)
默认
esac
测试1:
#判断字符串的类型
#!/bin/bash read -p "please input a char:" char case "$char" in [a-zA-Z]) echo "letter" ;; [0-9]) echo "number" ;; *) echo "other" esac
五、流控制之循环
1、带列表的for循环
语法格式:
for variable in list
do
statement1
statrment2
...
done
测试1:
#循环打印IP地址:
seq -f 192.168.68.1%02g 0 50 #输出100到150所有的IP地址
%02g 解析:填充两位内容其内容是0~50,位置不够用0进行填充
#!/bin/bash
for IP in $(seq -f "192.168.68.1%02g" 0 50)
do
echo $IP
done
测试2:
#循环测试主机号为0~20网络是否可达,使用ping 命令
#!/bin/bash
for IP in 192.168.68.{0..9} $(seq -f "192.168.68.%g" 10 20)
do
ping -c 1 $IP > /etc/null
if [ "$?" -eq 0 ]
then echo "$IP" is runing
else
echo "$IP" is not runing
fi
done
测试3:
#列出当前文件夹中所有的普通文件
ls -F #列出所有文件的类型
grep -v /$ #过滤掉斜线结尾的,即过滤掉文件夹
而这种不够严谨,除了文件和文件夹类型,还有很多的文件类型,如软连接等
#!/bin/bash
for FILE in $(ls -F | grep -v /$)
do
echo $FILE
done
find ./ -type f -depth 1 #找到当前目录下文件类型为f即普通文件的所有文件,且执行深度为1,即不用找该文件夹下面所有文件夹中的文件。
#!/bin/bash
for FILE in $(find ./ -maxdepth 1 -type f)
do
echo $FILE
done
测试4:
#给定一句话,找出所有字符数小于6的字符串
#!/bin/bash
for str in rabbit is favorite to eat cabbage
do
if [ `expr length $str` -lt 6 ]
then echo $str
fi
done
测试5:
计算一百以内的奇数的个数
可以使用num%2==1来挑选,也可以用花括号的用法来选for num in {start..end..step},将step设置成2,那么就可以挑选出奇数,再通过for循环进行加合运算。
让一个变量去接收两个变量相加后的值一定要使用let命令
#!/bin/bash
sum=0
for num in {1..100..2}
do
let "sum+=num"
#或sum=$[sum+num]
done
echo "$sum"
2、不带列表的for语句
语法格式:
for variable #等同于for variable in $@或$*(即执行脚本的时候要给定参数)
do
statement1
statrment2
...
done
测试1:
#!/bin/bash
for i
do
echo $i
done
3、类C风格的for循环
语法格式:
for((expression1;expression2;expression3))
do
statement1;
statement2;
done
测试1:
#批量添加用户
then useradd test0"$i" 2> /dev/null #这段命令中的2>的作用就是如果报错,将报错信息丢弃
echo 123456 | passwd --stdin test"$i" #可以自定添加密码
#!/bin/bash
for((i=1;i<=30;i++))
do
if [ $i -lt 10 ]
then
if id -u test0"$i" &> /dev/null
then
echo test0"$i" is exist!
else
useradd test0"$i" 2> /dev/null
echo 123456 | passwd --stdin test0"$i" > /dev/null
fi
else
if id -u test"$i" &> /dev/null
then
echo test"$i" is exist!
else
useradd test"$i" 2> /dev/null
echo 123456 | passwd --stdin test"$i" > /dev/null
fi
fi
done
测试2:
#测试192.168.68.0/24这个网段的主机有多少在线
#!/bin/bash
sum=0
for IP in 192.168.68.{0..255}
do
if ping -c 1 $IP &>/dev/null
then
let sum+=1
echo "$IP is up"
else
echo "$IP id down"
fi
done
echo $sum
实验过程中问题与解决方法:
虚拟机开启 ifconfig 没有ens160网卡,无法上网,同时 图形化模式 没有有线连接选项
手动启动网卡提示:
Connection 'ens160' is not available on device ens160 because device is strictly unmanaged
有一种临时方案 :
dhclient ens160
执行后可以上网,可以远程连接,但是每次开机都无法自动启动,
最终找到原因是由于 NM托管未开启导致的
查看托管状态
nmcli n
显示 disabled 则为本文遇到的问题,如果是 enabled 则可以不用往下看了
开启 托管
nmcli n on