Linux进阶——shell脚本语言

发布于:2025-04-08 ⋅ 阅读:(13) ⋅ 点赞:(0)

一、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