【shell script】

发布于:2024-07-04 ⋅ 阅读:(14) ⋅ 点赞:(0)


一、基础shell script

在“shell”部分,那是在命令行界面下让我们与系统沟通的一个工具接口。script是“脚本”的意思。整句话是说,shell script是针对shell所写的“脚本”。

操作系统的shell程序,介于用户和操作系统内核(Kernel)之间,负责将用户的命令解释成操作系统可以接受的指令,然后由操作系统来执行这些指令,并将操作系统执行的结果以用户可以了解的方式反馈给用户。

shell程序与C语言等高级语言程序不同,shell程序是通过shell命令解释器解释执行的,不生成二进制的可执行代码。由于bash是Linux下默认提供的shell解释器,并且bash也是使用最广泛、与其他shell兼容性最好的解释器,因此下面介绍的shell程序的知识都是基于bash解释器的。

sh01.sh:打印Hello World!

[root@RHEL7-2 scripts]# vim  sh01.sh
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
echo -e “Hello World! \n“   #-e 激活转义字符
exit 0

sh02.sh:使用read命令撰写一个script。让用户输入first name与last name后,在屏幕上显示“Your full name is: ”的内容。

[root@RHEL7-2 scripts]# vim  sh02.sh
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
 
read -p "Please input your first name: " firstname   	# 提示使用者输入
read -p "Please input your last name:  " lastname   	# 提示使用者输入
echo -e "\nYour full name is: $firstname $lastname"  	# 结果由屏幕输出
[root@RHEL7-2 scripts]# sh  sh02.sh

sh03.sh:利用date创建用日期命名的文件。
创建3个空的文件(通过touch),文件名开头由用户输入决定,假设用户输入“filename”,而今天的日期是2024/06/30,若想要以前天、昨天、今天的日期来创建这些文件,即filename20240628,filename20240629,filename20240630。
分析:
(1)判断文件名是否存在

1.${变量var:-value}:如果指定的变量var存在,则返回var的值,否则返回value。
2.${变量var:=value}:如果指定的变量var存在,则返回var的值,否则先将value赋给var,然后再返回value。
3.${变量var:+value}:如果指定的变量var存在,则返回value,否则返回空值。
4.${变量var:?value}:如果指定的变量var存在,则返回该var的值,否则将错误提示消息value送到标准错误输出并退出shell程序。
5.${变量var:offset[:length]}:offset和length是整数,中括号表示可选部分。表示返回从变量var的第offset+1个字符开始长度为length的子串。如果中括号部分省略,则表示返回变量var第offset+1个字符后面的子串。

(2)获取日期date
在这里插入图片描述
(3)$符号

$()命令替换
${}指定变量边界
$(())数学运算
$[]数学运算

[root@RHEL7-2 scripts]# vim  sh03.sh
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
 
#  让使用者输入文件名称,并取得fileuser这个变量
echo -e "I will use 'touch' command to create 3 files." 	# 纯粹显示信息
read -p "Please input your filename: "  fileuser         	# 提示用户输入
 
#  为了避免用户随意按“Enter”键,利用变量功能分析文件名是否设置?
filename=${fileuser:-"filename"}         	# 开始判断是否设置了文件名
#  开始利用date命令来取得所需要的文件名
date1=$(date --date='2 days ago'  +%Y%m%d)	# 前两天的日期,注意+号前面有个空格
date2=$(date --date='1 days ago'  +%Y%m%d)	# 前一天的日期,注意+号前面有个空格
date3=$(date +%Y%m%d)                      	# 今天的日期
file1=${filename}${date1}                  	# 这三行设置文件名
file2=${filename}${date2}
file3=${filename}${date3}
 
#  创建文件
touch "$file1"                             
touch "$file2"
touch "$file3"
[root@RHEL7-2 scripts]# sh  sh04.sh
[root@RHEL7-2 scripts]# ll 

sh04.sh:简单的加减乘除

[root@RHEL7-2 scripts]# vim  sh04.sh
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
echo -e "You SHOULD input 2 numbers, I will cross them! \n“
read -p "first number:  " firstnu
read -p "second number: " secnu
total=$(($firstnu*$secnu))
echo -e "\nThe result of $firstnu× $secnu is ==> $total"

二、脚本运行方式的差异

在上一篇文章中介绍了三种运行脚本的方式,都会使用一个新的bash环境来运行脚本内的命令。使用这种执行方式时,其实脚本是在子程序的bash内运行的,并且当子程序完成后,在子程序内的各项变量或动作将会结束而不会传回到父程序中。

[root@RHEL7-2 scripts]# echo  $firstname  $lastname <==首先确认变量并不存在
[root@RHEL7-2 scripts]# sh   sh02.sh
Please input your first name: Bobby                <==这个名字是读者自己输入的
Please input your last name: Yang

Your full name is: Bobby Yang      <==看吧!在脚本运行中,这两个变量会生效
[root@RHEL7-2 scripts]# echo   $firstname  $lastname
    <==事实上,这两个变量在父程序的bash中还是不存在

另一种运行方式,利用source运行脚本,在父程序中运行。

[root@RHEL7-2 scripts]# source  sh02.sh
Please input your first name: Bobby <==这个名字是读者自己输入的
Please input your last name: Yang

Your full name is: Bobby Yang      <==在script运行中,这两个变量会生效
 [root@RHEL7-2 scripts]# echo  $firstname  $lastname
Bobby Yang  						<==有数据产生

三、判断式

1.利用test命令

3.1.1 判断文件名及“文件类型”

测试标志 含义
-e “文件名”是否存在
-f “文件名”是否存在且为文件file
-d “文件名”是否存在且为目录
-b “文件名”是否存在且为block device设备
-c “文件名”是否存在且为character device设备
-S “文件名”是否存在且为Socket文件
-p “文件名”是否存在且为FIFO(pipe)文件
-L “文件名”是否存在且为连结文档

3.1.2 判断文件的权限

测试标志 含义
-r 文件名是否存在且具有“可读”权限
-w 文件名是否存在且具有“可写”权限
-x 文件名是否存在且具有“可执行”权限
-u 文件名是否存在且具有“SUID”权限
-g 文件名是否存在且具有“SGID”权限
-k 文件名是否存在且具有“Sticky bit”权限
-s 文件名是否存在且为非空白文件

3.1.3 两个文件之间的比较
test file1 -nt file2

测试标志 含义
-nt newer than 判断file1是否比file2新
-ot older than 判断file1是否比file2旧
-ef 判断file1与file2是否为同一文件,可以用于判断硬链接

3.1.4 两个整数之间的比较
test n1 -eq n2

测试标志 含义
-eq 两个数值相等 equal
-ne 两个数值不相等 not equal
-gt n1大于n2 great than
-lt n1小于n2 less than
-ge n1大于等于n2 great than or equal
-le n1小于等于n2 less than or equal

3.1.5 判断字符串数据

测试标志 含义
test -z string 判定字符串是否为0?若string为空字符串,则为true
test -n string 判定字符串是否非0?若string为空字符串,则为false(-n可省略)
test str1=str2 判定str1是否等于str2,若相等,则回传true
test str1!=str2 判定str1是否不等于str2,若相等,则回传false

3.1.6 多重条件判断
test -r filename -a -x filename

测试标志 含义
-a (and)两状况同时成立。例如test –r file –a –x file,则file同时具有r与x权限时,才回传true
-o (or)两状况任何一个成立。例如test –r file –o –x file,则file同时具有r或x权限时,就可回传true
! 反相状态,如test !-x file,当file不具有x时,回传true

sh05.sh:让读者输入一个文件名,然后作如下判断。文件是否存在,若不存在则给出“Filename does not exist”的信息,并中断程序。若这个文件存在,则判断其是文件还是目录,结果输出“Filename is regular file”或“Filename is directory”。判断一下,执行者的身份对这个文件或目录所拥有的权限,并输出权限数据。

[root@RHEL7-2 scripts]# vim  sh05.sh
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

#  让使用者输入文件名,并且判断使用者是否输入了字符串
echo -e "Please input a filename, I will check the filename's type and permission. \n\n"
read -p "Input a filename : " filename
test -z $filename && echo "You MUST input a filename." && exit 0

#  判断文件是否存在,若不存在则显示信息并结束脚本
test ! -e $filename && echo "The filename '$filename' DO NOT exist" && exit 0
# 开始判断文件类型与属性
test -f $filename && filetype="regulare file"
test -d $filename && filetype="directory"
test -r $filename && perm="readable"
test -w $filename && perm="$perm writable"
test -x $filename && perm="$perm executable“

# 开始输出信息
echo "The filename: $filename is a $filetype"
echo "And the permissions are : $perm"

2.利用判断符号[]

[□"$HOME"□==□"$MAIL"□]

  • 在中括号 [] 内的每个组件都需要有空格键来分隔。
  • 在中括号内的变量,最好都以双引号括起来。 [□“$name” □==□“yang”□]
  • 在中括号内的常数,最好都以单或双引号括起来。

sh06.sh:当运行一个程序的时候,这个程序会让用户选择Y或N。
如果用户输入Y或y时,就显示“OK, continue”。
如果用户输入n或N时,就显示“Oh, interrupt!”
如果不是Y/y/N/n之内的其他字符,就显示“I don’t know what your choice is”。
分析:需要利用中括号、&&与 ||。

[root@RHEL7-2 scripts]# vim  sh06.sh
read -p "Please input (Y/N): " yn
[ "$yn" == "Y" -o "$yn" == "y" ] && echo "OK, continue" && exit 0
[ "$yn" == "N" -o "$yn" == "n" ] && echo "Oh, interrupt!" && exit 0
echo "I don't know what your choice is" && exit 0

四、if,case语句

1.if…then

单层、简单条件判断式
if [条件判断式]; then
  当条件判断式成立时,可以进行的命令工作内容;
fi <==将if反过来写,就成为fi了,结束if之意

多重、复杂条件判断式
if [条件判断式]; then
  当条件判断式成立时,进行的命令工作内容;
else
  当条件判断式不成立时,进行的命令工作内容;
fi

多个条件判断多个条件判断 (if…elif…elif… else)
分多种不同情况运行
if [条件判断式一]; then
  当条件判断式一成立时,进行的命令工作内容;
elif [条件判断式二]; then
  当条件判断式二成立时,进行的命令工作内容;
else
   当条件判断式一与二均不成立时,进行的命令工作内容;
fi

sh07.sh:编写一个shell脚本,以一个普通文件作为参数。其功能是:判断文件的大小是否为0,如果是则删除它;否则将该文件的内容输出到标准输出。

#!/bin/bash

read -p "input a filename:" filename
if [ -f "$filename" ];then
        if [ -s "$filename" ];then
                cat $filename
        else
                rm -f $filename
        fi
else
        echo "$filename is not a file."
fi

sh08.sh:如果用户输入Y或y时,就显示“OK, continue”。
如果用户输入n或N时,就显示“Oh, interrupt!”
如果不是Y/y/N/n之内的其他字符,就显示“I don’t know what your choice is”

#!/bin/bash
read -p "Please input (Y/N): " yn
if [ "$yn" == "Y" ] || [ "$yn" == "y" ]; then
  echo "OK, continue"
elif [ "$yn" == "N" ] || [ "$yn" == "n" ]; then
      echo "Oh, interrupt!"
else
      echo "I don't know what your choice is"
fi

sh09.sh:判断$1是否为hello,如果是的话,就显示“Hello, how are you ?”。
如果没有加任何参数,就提示用户必须要使用的参数。
而如果加入的参数不是hello,就提醒用户仅能使用hello为参数。

[root@RHEL7-2 scripts]# vim  sh09.sh
if [ "$1" == "hello" ]; then
      echo "Hello, how are you ?"
elif [ "$1" == "" ]; then
      echo "You MUST input parameters, ex> {$0 someword}"
else
      echo "The only parameter is 'hello', ex> {$0 hello}"
fi

sh10.sh:假设需要检测的是比较常见的port 21, 22, 25及80,那么如何去检测当前主机是否开启了这4个主要的网络服务端口?

[root@RHEL7-2 ~]# netstat  -tuln # 获取主机取得的服务
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address     Foreign Address   State
tcp        0      0 0.0.0.0:111       0.0.0.0:*         LISTEN
tcp        0      0 127.0.0.1:631  	   0.0.0.0:*         LISTEN
tcp        0      0 127.0.0.1:25   	   0.0.0.0:*         LISTEN
tcp        0      0 :::22           	   :::*               LISTEN
udp        0      0 0.0.0.0:111   	   0.0.0.0:*
udp        0      0 0.0.0.0:631   	   0.0.0.0:*
#封包格式           本地IP:端口     	 	   远程IP:端口       是否监听
[root@RHEL7-2 scripts]# vim  sh10.sh
echo "Now, I will detect your Linux server's services!"
echo -e "The www, ftp, ssh, and mail will be detect! \n"
#  开始进行一些测试的工作,并且也输出一些信息
testing=$(netstat -tuln | grep ":80 ")   # 检测port 80存在否
if [ "$testing" != "" ]; then
      echo "WWW is running in your system."
fi
testing=$(netstat -tuln | grep ":22 ")   # 检测port  22存在否
if [ "$testing" != "" ]; then
 echo "SSH is running in your system."
fi
testing=$(netstat -tuln | grep ":21 ")   # 检测port 21存在否
if [ "$testing" != "" ]; then
      echo "FTP is running in your system."
fi
testing=$(netstat -tuln | grep ":25 ")   # 检测port 25存在否
if [ "$testing" != "" ]; then
      echo "Mail is running in your system."
fi

sh11.sh:有个军人想要计算自己还有多长时间会退伍,让用户输入他的退伍日期,从而帮他计算还有多少天会退伍?
分析:由于日期是要用相减的方式来处置,通过使用date显示日期与时间,将其转为由1970-01-01累积而来的秒数,通过秒数相减来取得剩余的秒数后,再换算为天数即可。

#!/bin/bash
#  告诉使用者这个程序的用途,并且告诉应该如何输入日期格式
echo "This program will try to calculate :"
echo "How many days before your demobilization date..."
read -p "Please input your demobilization date (YYYYMMDD ex>20120401): " date2

#  利用正则表达式测试一下这个输入的内容是否正确,
date_d=$(echo $date2 |grep '[0-9]\{8\}')   # 看看是否有8个数字
if [ "$date_d" == "" ]; then
      echo "You input the wrong date format...."
   exit 1
fi
# 开始计算日期
#declare -i date_dem=`date --date="$date2"  +%s`     # 退伍日期秒数,注意+前面的空格
declare -i date_dem=$(date -d "$date2" +%s)

#declare -i date_now=`date  +%s`                     # 现在日期秒数,注意+前面的空格
declare -i date_now=$(date +%s)

declare -i date_total_s=$(($date_dem-$date_now))        # 剩余秒数统计
declare -i date_d=$(($date_total_s/60/60/24)) #转为日数,用除法(一天=24*60*60(秒))
if [ "$date_total_s" -lt "0" ]; then                     # 判断是否已退伍
      echo "You had been demobilization before: " $((-1*$date_d)) " ago"
else
      declare -i date_h=$(($(($date_total_s-$date_d*60*60*24))/60/60))
      echo "You will demobilize after $date_d days and $date_h hours."
fi

2.case…esac

假如有多个既定的变量内容,那么只要针对这几个变量来设置情况就可以了,使用case…in…esac最为方便。

case $变量名称 in #关键字为case,变量前有 $ 符
  “第一个变量内容”) #每个变量内容建议用双引号括起来,关键字则为小括号 )
  程序段
  ;; #每个类别结尾使用两个连续的分号来处理
  “第二个变量内容”)
  程序段
  ;;
 *) #最后一个变量内容都会用 * 来代表所有其他值
  不包含第一个变量内容与第二个变量内容的其他程序运行段
  exit 1
  ;;
esac #最终的case结尾!

sh12.sh:让用户能够输入one、two、three,并且将用户的变量显示到屏幕上,如果不是one、two、three,就告诉用户仅有这3种选择。

[root@RHEL7-2 scripts]# vim  sh12.sh
echo "This program will print your selection !"
case $1 in                              		
  "one")
      echo "Your choice is ONE"
      ;;
  "two")
      echo "Your choice is TWO"
      ;;
  "three")
      echo "Your choice is THREE"
      ;;
  *)
      echo "Usage $0 {one|two|three}"
      ;;
esac

五、函数function

function fname() {
  程序段
}
先定义,后使用。

function printit(){
      echo -n "Your choice is "     # 加上 -n可以不断行继续在同一行显示
}
echo "This program will print your selection !"
case $1 in
  "one")
      printit; echo $1 | tr 'a-z' 'A-Z'  # 将参数做大小写转换
      ;;
  "two")

printit; echo $1 | tr 'a-z' 'A-Z'
      ;;
  "three")
      printit; echo $1 | tr 'a-z' 'A-Z'
      ;;
  *)
      echo "Usage $0 {one|two|three}"
      ;;
esac

注意函数中的传参:

将上面的例子再次改写一下:
function printit(){
      echo "Your choice is $1"   # 这个 $1必须参考下面命令的执行
}
echo "This program will print your selection !"
case $1 in
  "one")
      printit 1  			# 请注意,printit命令后面还有参数
      ;;
  "two")
      printit 2
      ;;
  "three")
      printit 3
      ;;
  *)
      echo "Usage $0 {one|two|three}"
      ;;
esac
#执行
[root@rhel7-2 scripts]# sh sh12-2.sh two
This program will print your selection !
Your choice is 2     					//转换大小写

例:编写两个函数分别为sum()(相加函数)和multi()(相乘函数),输入两个数,分别调用两个函数输出结果。

function sum(){
        sum=$(($1+$2))
        return $sum
}
function multi(){
        multi=$(($1*$2))
        echo "The result of $p1 * $p2 is $multi"
}
read -p "input your operation:ex {+|*}" op
read -p "input your first value:" p1
read -p "input your second value:" p2

if [ "$op" == "+" ];then
        sum $p1 $p2
        sum=$?
        echo "The result of $p1 + $p2 is $sum"
elif [ "$op" == "*" ];then
        multi $p1 $p2
else
        echo "you should input + or *."
fi

六、循环

1.while和until循环

while:当条件满足时执行循环体

while [ condition ] #中括号内的状态就是判断式
do #do是循环的开始!
  程序段落
done #done是循环的结束

until:当条件满足时,退出循环体

until [ condition ]
do
  程序段落
done

sh13.sh:假设要让用户输入yes或者是YES才结束程序的运行,否则就一直运行并告诉用户输入字符。

[root@RHEL7-2 scripts]# vim  sh13.sh
while [ "$yn" != "yes" -a "$yn" != "YES" ]
do
      read -p "Please input yes/YES to stop this program: " yn
done
echo "OK! you input the correct answer."

或者

[root@RHEL7-2 scripts]# vim  sh13-2.sh
until [ "$yn" == "yes" -o "$yn" == "YES" ]
do
      read -p "Please input yes/YES to stop this program: " yn
done
echo "OK! you input the correct answer."

sh14.sh:计算1+2+…+100的值

[root@RHEL7-2 scripts]# vim  sh14.sh
s=0  					# 这是累加的数值变量
i=0  					# 这是累计的数值,即1, 2, 3...
while [ "$i" != "100" ]
do
      i=$(($i+1))   		# 每次i都会添加1 
      s=$(($s+$i))  		# 每次都会累加一次
done
echo "The result of '1+2+3+...+100' is ==> $s"

2.for循环

(1)for … do … done固定循环

for var in con1 con2 con3 …
do
  程序段
done

sh15.sh:假设有三种动物,分别是dog、cat、elephant,编写程序,使每一行都按“There are dogs…”之类的样式输出。

[root@RHEL7-2 scripts]# vim  sh15.sh
for animal in dog cat elephant
do
  echo "There are ${animal}s.... "
done

sh16.sh:查找系统中所有的账号,并用id命令显示每个用户的信息。

[root@RHEL7-2 scripts]# vim  sh16.sh
users=$(cut -d ':' -f1 /etc/passwd)  	# 获取账号名称
for username in $users               		# 开始循环
do
        id $username
done

sh17.sh:让用户输入某个目录名,然后找出某目录内的文件的权限。

[root@RHEL7-2 scripts]# vim  sh18.sh
#  先判断这个目录是否存在
read -p "Please input a directory: " dir
if [ "$dir" == ""  -o  ! -d  "$dir" ]; then
      echo "The $dir is NOT exist in your system."
      exit 1
fi

#  开始测试文件
filelist=$(ls $dir)   			     # 列出所有在该目录下的文件名称
for filename in $filelist
do
      perm=""
      test -r "$dir/$filename" && perm="$perm readable"
      test -w "$dir/$filename" && perm="$perm writable"
      test -x "$dir/$filename" && perm="$perm executable"
      echo "The file $dir/$filename's permission is $perm "
done

(2)seq对数字循环
seq 用于产生从某个数到另外一个数之间的所有整数

seq … 尾数
seq … 首数 尾数
seq … 首数 增量 尾数

sh18.sh:利用ping这个可以判断网络状态的命令来进行网络状态的实际检测,要侦测的域是本机所在的192.168.10.1~192.168.10.100。

[root@RHEL7-2 scripts]# vim  sh17.sh
network="192.168.10"              	# 先定义一个网络号(网络ID)
for sitenu in $(seq 1 100)       	# seq为sequence(连续) 的缩写之意
do
      # 下面的语句取得ping的回传值是正确的还是失败的
    ping -c 1 -w 1 ${network}.${sitenu} &> /dev/null && result=0  ||  result=1
                 # 开始显示结果是正确的启动(UP)还是错误的没有连通(DOWN)
    if [ "$result" == 0 ]; then
            echo "Server ${network}.${sitenu} is UP."
    else
            echo "Server ${network}.${sitenu} is DOWN."
    fi
done

(3)(())对数字循环

for (( 初始值; 限制值; 执行步长 ))
do
  程序段
done

sh19.sh:输入一个数值nu,计算1+2+3+…+nu的值。

[root@RHEL7-2 scripts]# vim  sh19.sh
read -p "Please input a number, I will count for 1+2+...+your_input: " nu
s=0
for (( i=1; i<=$nu; i=i+1 ))
do
  s=$(($s+$i))
done
echo "The result of '1+2+3+...+$nu' is ==> $s"

sh20.sh:设计一个shell程序,在/root目录下建立10个目录,即user1~user10,如果存在,则输出“the directory exists”,并设置每个目录的权限,其中其他用户的权限为读;文件所有者的权限为读写执行;文件所有者所在组的权限为读执行。

i=1
while [ "$i" -le 10 ]
do
        if [ -d "user$i" ];then
                echo "the user$i directory exists."
        else
                mkdir user$i
        fi
        chmod 754 user$i
        i=$(($i + 1))
done

sh21.sh:打印9*9乘法表

for ((i=1;i<=9;i=i+1))
do
        for ((j=1;j<=i;j++))
        do
                result=$(($i*$j))
                echo -n "$i*$j=$result "
        done
        echo ""
done

(4)break和continue语句
break和continue命令用于中断循环体的执行;
break命令将控制转移到done后面的命令,循环提前结束;
continue命令将控制转移到done,接着再次计算条件的值,以决定是否继续循环。

break[n]
n:表示要跳出几层循环,默认值是1
continue[n]
从continue语句的最内层循环向外跳出第n层循环,默认值为1