文章目录
- 一、基础shell script
- 二、脚本运行方式的差异
- 三、判断式
- 1.利用test命令
- 2.利用判断符号[]
- 四、if,case语句
- 1.if...then
- 2.case...esac
- 五、函数function
- 六、循环
- 1.while和until循环
- 2.for循环
一、基础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