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: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的值

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

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}'` #输出以-为开头的所有行的第五列

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 #测试结果

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

测试:

测试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

测试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

测试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

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

五、流控制之循环

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

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
相关推荐
YH行业报告分析17 分钟前
2026电缆铺设设备市场:自动化敷设与电网升级驱动增长
运维·自动化
ajassi20001 小时前
AI语音智能体开发日记(十八)智能体服务器xiaozhi-esp32-server源码部署指南
运维·服务器·人工智能·ai·ai编程
‎ദ്ദിᵔ.˛.ᵔ₎1 小时前
linux 进程信号(下)
linux·运维·服务器
skywalk81632 小时前
在FreeBSD系统的linux仿真环境下安装和使用kiro 这个亚马逊的AI agent
linux·人工智能·freebsd·kiro
Mikko72 小时前
Linux 磁盘满了但 du 找不到?LVM 扩容 df 不变?fstab 写错开不了机?三台真机实测磁盘排查
linux·运维·centos
蓝胖的四次元口袋2 小时前
Jenkins知识梳理(2)
运维·jenkins
yumgpkpm2 小时前
Acceldata ODP 3.3.6.4 vs CDP Private Cloud Base 7.3.2 对比
大数据·运维·服务器·hadoop·华为·zookeeper·hbase
Madison-No72 小时前
多语言聊天大模型--测试报告
linux·git·python·selenium·jmeter·自动化·postman
Liuqy-052 小时前
特殊进程——孤儿、僵尸、守护进程
linux·运维·服务器
流浪0012 小时前
Linux系统篇47——线程(十二) POSIX 信号量与环形队列,把判空判满提前到访问之前
linux·操作系统·线程·信号量·环形队列