Linux进阶——shell脚本语言

一、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 #自动生成脚本注释

bash 复制代码
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:[email protected]")
                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的值

实例:输入两个实数,返回加减乘除除于等结果:

bash 复制代码
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值由用户输入:

bash 复制代码
#!/bin/bash
read -p "please input a number:" n
sum=`seq -s "+"  $n | bc`
echo $sum

实例2:写一个脚本,输入一个文件的绝对路径,输出文件的目录,名字,还有扩展名

bash 复制代码
#!/bin/bash
read -p "please input a string:" str
str2=${str##*/}
echo "output name:${str2%%.*}"
echo "output extension:${str#*.}"
echo "output content:${str%/*}"

实验3:写一个脚本,输入一个数字,返回数的位数

bash 复制代码
#!/bin/bash
read -p "please input a number:" num
echo `expr length $num`

实验4:写一个脚本,输入一个文件夹的绝对地址,输出文件夹中的文件个数

bash 复制代码
#!/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}'\` #输出以-为开头的所有行的第五列 > > ![](https://i-blog.csdnimg.cn/direct/d7986c2ae1bb4f8a9fe0782dc573885e.png) > > crontab -e #循环执行例行性检查 > > \*/10 \* \* \* \* /test/free_mem.sh \&\> /dev/null #每十分钟检查一次内存是否小于100MB 判断当前的执行者是谁: whoami echo $USER #会返回名字 id -u echo $UID #会返回数字,0代表是root用户,不是零为普通用户。 测试2: > vim demo02.sh #新建脚本文件 > > ```bash > #!/bin/bash > if [ "$UID" != 0 ] > then echo "please switch user root" > fi > ``` > > chmod a+rx demo02.sh #加权限 > > ./demo02.sh #测试结果 > > ![](https://i-blog.csdnimg.cn/direct/f6e60c192325476d9da58307e04f7567.png) ### 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端口的进程个数 > #写一个脚本,统计某个程序的进程个数,输入一个程序的名字,输出该程序的进程个数 > > ```bash > #!/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 > ``` > > 测试: > > ![](https://i-blog.csdnimg.cn/direct/371b8bac44094d8faeff5e4cde791ac8.png) 测试2: 检测主机存活可以使用ping 命令进行检测 ping -c 2 -w 1 192.168.68.99 \&\> /dev/null #测试网络可达性,并且将显示的结果去除 > #写一个脚本测试主机是否存活 > > ```bash > #!/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: > #比较两个数的大小 > > ```bash > #!/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 > ``` > > ![](https://i-blog.csdnimg.cn/direct/09328b3970f749608f2ec08bfc007382.png) 测试2: > #判断字符的种类 > > ```bash > #!/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 > ``` > > ![](https://i-blog.csdnimg.cn/direct/14031a592ea840419917615d9d7ca394.png) 测试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的厂商信息 > > ```bash > #!/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 > ``` > > ![](https://i-blog.csdnimg.cn/direct/2ee08db3a4974fdb933cc10541fb4725.png) 4、多条件判断语句case > 语法格式: > > case 变量名 in > > 值1) > > 指令1 > > ;; > > 值2) > > 指令2 > > ;; > > 值3) > > 指令3 > > ;; > > \*) > > 默认 > > esac 测试1: > #判断字符串的类型 > > ```bash > #!/bin/bash > read -p "please input a char:" char > case "$char" in > [a-zA-Z]) > echo "letter" > ;; > [0-9]) > echo "number" > ;; > *) > echo "other" > esac > ``` > > ![](https://i-blog.csdnimg.cn/direct/04d2fb3fb5814388adb31e8c31fbbafe.png) ## 五、流控制之循环 ### 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进行填充 ```bash #!/bin/bash for IP in $(seq -f "192.168.68.1%02g" 0 50) do echo $IP done ``` 测试2: #循环测试主机号为0\~20网络是否可达,使用ping 命令 ```bash #!/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 /$ #过滤掉斜线结尾的,即过滤掉文件夹 而这种不够严谨,除了文件和文件夹类型,还有很多的文件类型,如软连接等 ```bash #!/bin/bash for FILE in $(ls -F | grep -v /$) do echo $FILE done ``` find ./ -type f -depth 1 #找到当前目录下文件类型为f即普通文件的所有文件,且执行深度为1,即不用找该文件夹下面所有文件夹中的文件。 ```bash #!/bin/bash for FILE in $(find ./ -maxdepth 1 -type f) do echo $FILE done ``` 测试4: #给定一句话,找出所有字符数小于6的字符串 ```bash #!/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命令 ```bash #!/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: ```bash #!/bin/bash for i do echo $i done ``` ![](https://i-blog.csdnimg.cn/direct/a86568130eef45188849ae2d98a1a5ad.png) ### 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" #可以自定添加密码 ```bash #!/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这个网段的主机有多少在线 ```bash #!/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网卡,无法上网,同时 图形化模式 没有有线连接选项 手动启动网卡提示: ```bash Connection 'ens160' is not available on device ens160 because device is strictly unmanaged ``` 有一种临时方案 : ```bash dhclient ens160 ``` 执行后可以上网,可以远程连接,但是每次开机都无法自动启动, 最终找到原因是由于 NM托管未开启导致的 查看托管状态 nmcli n 显示 disabled 则为本文遇到的问题,如果是 enabled 则可以不用往下看了 开启 托管 nmcli n on

相关推荐
不爱学英文的码字机器6 分钟前
隐私计算的崛起:数据安全的未来守护者
服务器·算法
a123_z10 分钟前
.NET 创建MCP使用大模型对话二:调用远程MCP服务
运维·服务器
葟雪儿19 分钟前
Docker常用命令
linux·服务器·spring cloud·docker·微服务·容器
木盏27 分钟前
Linux终止进程(kill process)的一些玩法
linux·运维·深度学习
Fanche40427 分钟前
Linux-CentOS-7—— 安装MySQL 8
linux·运维·数据库·mysql·centos
橘子1328 分钟前
Linux信号——信号的处理(3)
linux·运维·服务器
CZIDC41 分钟前
Nginx搭建API网关服务教程-系统架构优化 API统一管理
运维·nginx·系统架构
Algorithm15761 小时前
linux如何查看当前系统的资源占用情况
linux·运维·服务器
java资料站2 小时前
分盘,内网
linux·服务器
嘵奇3 小时前
Spring Boot内嵌服务器全解析:Tomcat vs Jetty vs Undertow 选型指南
服务器·spring boot·tomcat