目录
不定循环 while do done 与 until do done
1.简介
可以将shell脚本看成是一个程序,但它始终是一个由命令构成的纯文本文件,在系统管理上是一个非常好用的工具,但是用在大量数值运算上就有点力不从心了
在shell脚本的编写中需要注意的是
- 命令是从上往下、从左往右分析与执行
- 命令选项与参数之间的多个空格会被忽略
- 空白行会被忽略,tab产生的空白视为空格键
- 读到一个enter符就会尝试执行该行命令
- 可使用\enter来扩展到下一行
- #为注释符,不会执行该行后面的命令
执行脚本文件的几个方法
- 直接命令执行:文件需要有rx权限
- 绝对路径执行
- 相对路径执行
- 放入PATH变量
- 以bash程序执行
编写的第一个脚本
#!/bin/bash
#Program:
# This program shows 'hello world' in your screen.
#History:
#2022/8/23 zjw First realease
PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:~/bin
export PATH
echo -e "Hello World! \a \n"
exit 0
PATH变量可手动敲上去,也可以利用下面的方法快速写入
:! echo $PATH >> hello.sh
1.第一行 #!/bin/bash 声明该脚本使用的shell名称
此行被称为shebang行,告诉了系统该脚本使用什么shell来执行
2.程序内容说明
除了第一行#外其他的#都为注释,强烈建议要养成写脚本说明的习惯包括:
内容与功能;版本信息;作者与联系方式;建立日期;历史记录
3.主要环境变量的声明
务必设置好相关环境变量如PATH与LANG
4.写好主程序
5.定义返回值
一个良好的编写习惯
在每个脚本的文件头处记录好
脚本功能;脚本版本信息;作者与联系方式;脚本的版权声明;历史记录;脚本内特殊的命令;脚本运行时需要的环境变量预先声明与设置
脚本编写尽量美观
2.shell脚本练习
简单范例
交互式脚本
让用户自己输入一些内容从而让脚本顺利运行
#!/bin/bash
#Program:
# This program shows your name.
#History:
#2022/8/23 zjw First release
PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:~/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}"
利用 read -p 来提示用户输入
随日期变化
自动创建随日期变化的文件
#!/bin/bash
#Program:
# 按照不同的日期创建三个文件
#History:
#2022/8/23 zjw First release
PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:~/bin
export PATH
read -p "Please input your filename:" fileuser
filename=${fileuser:-"filename"} #如果不输入filename直接回车,那文件名前缀就是filename
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}" #创建文件命令
数值运算
需要用declare自定义变量类型
#!/bin/bash
#Program:
# 实现数值的加减乘除计算
# 除法为整型除法
#History:
#2022/8/24 zjw First release
PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:~/bin
export PATH
echo -e "Input two numbers."
read -p "First number:" firstnu
read -p "Second number:" secondnu
add=$((${firstnu}+${secondnu}))
sub=$((${firstnu}-${secondnu}))
mul=$((${firstnu}*${secondnu}))
div=$((${firstnu}/${secondnu}))
echo -e "${firstnu}+${secondnu}=${add}\n${firstnu}-${secondnu}=${sub}\n${firstnu}*${secondnu}=${mul}\n${firstnu}/${secondnu}=${div}"
数值运算还可以替换成下方写法,不过上方写法更容易记住
declare -i add=${firstnu}+${secondnu}
利用 bc 命令(bc为计算器命令,可进入命令行也可利用管道,第四章有说明)可以实现含有小数的运算
#!/bin/bash
#Program:
# 用户输入小数位数用以显示pi
#History:
#2022/8/24 zjw First release
PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:~/bin
export PATH
read -p "The scale number(10-10000):" checking
num=${checking:-"10"} #判断是否有输入数值,若直接回车,则默认10
echo -e "Starting calcuate pi values.Be patient..."
time echo "scale=${num}; 4*a(1)" | bc -lq #time显示用时,4*a(1)是bc提供的计算pi的函数
脚本执行方式差异
直接执行脚本 sh filename.sh 其实是通过子进程执行,子进程产生的变量不会传递到父进程
利用 source filename.sh 会直接在父进程中执行,脚本结束后变量仍然存在
3.判断式
test命令
如果想要检查某个文件是否存在,可以利用下面的命令查看,返回值需要通过echo $?来查看
test -e filename
test的所有参数在P396
判断符号[ ]
[zjw@zjw ~]$ [ -z $HOME ] ; echo $? 用于判断变量是否为空,非空则返回1
中括号内的每个组件都需要有空格来分隔
中括号内的变量最好都以双引号括起来
中括号内的常数最好都以单或双引号括起来
[zjw@zjw ~]$ name="zjw zjw" [zjw@zjw ~]$ [ $name="abc" ] bash: [: zjw: unary operator expected $name不用双引号括起来,就会默认为 [ zjw zjw="abc" ]
#!/bin/bash #Program: # This program shows the user's choice. #History: #2022/8/26 zjw First release PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:~/bin export PATH read -p "Please input (Y/N):" yn [ "${yn}"=="Y" -o "${yn}"=="y" ] && echo "Continue" && exit 0 #-o(或)用于连接两个判断 [ "${yn}"=="N" -o "${yn}"=="n" ] && echo "Interrupt" && exit 0 echo "I don't know what your choice is" && exit 0
shell脚本的默认变量 $0 $1 ...
利用变量可以实现在脚本文件名后面携带参数
/path/to/scriptname opt1 opt2 ...
$0 $1 $2
还有一些特殊变量可以在脚本内使用来调用上述参数
$# 后接参数个数 $@ 代表 "$1" "$2" "$3",即每个独立的变量 $* 代表 "$1c$2c$3",c为分隔符,默认空格 #!/bin/bash #Program: # 程序显示文件名、参数个数... #History: #2022/8/26 zjw First release PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:~/bin export PATH echo "The script name is:${0}" echo "Total parameter number is:$#" #显示了所有参数个数 [ "$#" -lt 2 ] && echo "The number of parameter is less than 2." && exit 0 echo "Your whole parameter is:$@" #列出每个独立变量 echo "The 1st parameter:${1}" echo "The 2nd parameter:${2}"
变量的偏移
#!/bin/bash
#Program:
# 变量的偏移。
#History:
#2022/8/26 zjw First release
PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:~/bin
export PATH
echo "Total parameter number is:$#"
echo "Whole parameter is:$@"
shift #第一次偏移
echo "Total parameter number is:$#"
echo "Whole parameter is:$@"
shift 3 #第二次偏移3个
echo "Total parameter number is:$#"
echo "Whole parameter is:$@"
[zjw@zjw bin]$ sh shift_paras.sh 1 2 3 4 5
Total parameter number is:5
Whole parameter is:1 2 3 4 5
Total parameter number is:4
Whole parameter is:2 3 4 5
Total parameter number is:1
Whole parameter is:5
条件判断式
单层条件判断式
#!/bin/bash #Program: # This program shows user's choice. #History: #2022/8/29 zjw First release PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:~/bin export PATH read -p "Please input(Y/N):" yn if [ "${yn}" == "Y" ] || [ "${yn}" == "y" ];then echo "Continue" exit 0 fi if [ "${yn}" == "N" ] || [ "${yn}" == "n" ];then #注意中括号两边及等号两端的空格,否则会报错 echo "Interrupt" exit 0 fi echo "I don't know" && exit 0
多重条件判断式
#!/bin/bash #Program: # This program shows user's choice. #History: #2022/8/29 zjw First release PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:~/bin export PATH read -p "Please input(Y/N):" yn if [ "${yn}" == "Y" ] || [ "${yn}" == "y" ];then echo "Continue" elif [ "${yn}" == "N" ] || [ "${yn}" == "n" ];then echo "Interrupt" else echo "I don't know" fi #!/bin/bash #Program: # 判断$1是否为hello #History: #2022/8/29 zjw First release PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:~/bin export PATH if [ "${1}" == "hello" ];then echo "yes" elif [ "${1}" == "" ];then echo "You must input parameters,ex> [${0} parameters]" else echo "no" fi
利用case...esac判断
case后的变量有两种获取方式,直接执行方式与交互式
直接执行获得变量
#!/bin/bash
#Program:
# Show "hello" from $1... by using case...esac
#History:
#2022/8/31 zjw First release
PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:~/bin
export PATH
case ${1} in
"hello")
echo "Hello, how are you ?"
;;
"")
echo "You MUST input parameters, ex> {${0} someword}"
;;
*)
echo "Usage ${0} {hello}"
;;
esac
交互式获得变量
#!/bin/bash
#Program:
# This script only accepts the flowing parameter: one, two, three.
#History:
#2022/8/31 zjw First release
PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:~/bin
export PATH
read -p "Input your choice:" choice
case ${choice} 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功能
shell脚本的执行方式是从上而下从左到右,所以函数的定义一定要在程序前面
函数内部的${1}与shell脚本内的${1}是不一样的
#!/bin/bash
#Program:
# This script only accepts the flowing parameter: one, two, three.
#History:
#2022/8/31 zjw First release
PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:~/bin
export PATH
function printit()
{
echo -n "Your choice is "
}
echo "This prigram 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
4.循环 loop
不定循环 while do done 与 until do done
#!/bin/bash
#Program:
# Repeat question until user input correct answer.
#History:
#2022/8/31 zjw First release
PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:~/bin
export PATH
read -p "Please input yes or no:" ans
while [ "${ans}" != "yes" -a "${ans}" != "YES" ]
do
read -p "Please input yes/no to stop this program:" ans
done
echo "OK, correct answer"
#!/bin/bash
#Program:
# Repeat question until user input correct answer.
#History:
#2022/8/31 zjw First release
PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:~/bin
export PATH
read -p "Please input yes or no:" ans
until [ "${ans}" == "yes" -a "${ans}" == "YES" ] #while与until的区别在于条件判断的不同
do
read -p "Please input yes/no to stop this program:" ans
done
echo "OK, you input the correct answer"
#!/bin/bash
#Program:
# Use loop to calculate 1+2+...+input
#History:
#2022/8/31 zjw First release
PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:~/bin
export PATH
sum=0
i=0
read -p "Please input a number:" num
while [ "${i}" != "${num}" ] #这里的双引号一定要写对,且 <= 无法识别
do
i=$((${i}+1))
sum=$((${sum}+${i}))
done
echo "The result is: ${sum}"
for...do...done 固定循环
for var in con1 con2 con3...
do
程序
done
第一次循环,变量var的内容为con1,第二次循环变量var的内容为con2...
#!/bin/bash
#Program:
# Using loop to print three animals.
#History:
#2022/9/1 zjw First release
PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:~/bin
export PATH
for animals in dogs cats elephants
do
echo "There are ${animals}"
done
若需要连续输出,则需要用到命令 seq
#!/bin/bash
#Program:
# Using ping command to check the network's PC state.
#History:
#2022/9/1 zjw First release
PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:~/bin
export PATH
network="192.168.1"
for sitenu in $(seq 1 100) # $(seq 1 100) 即从1到100,也可以用{1..100}来替换
do
ping -c 1 -w 1 ${network}.${sitenu} &> /dev/null && result=0 || result=1
if [ "${result}"==0 ]; then
echo "Server ${network}.${sitenu} is UP."
else
echo "Server ${network}.${sitenu} is DOWN."
fi
done
判断加上循环
#!/bin/bash
#Program:
# User input dir name, I find the permission of files.
#History:
#2022/9/1 zjw First release
PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:~/bin
export PATH
read -p "Please input a directory:" dir
if [ "${dir}" == "" -o ! -d "${dir}" ]; then #条件判断,如果是空目录或者不是目录 -o 是或者的意思;! -d "${dir}" 即判断是否为目录,不是目录则返回真
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 "This file ${dir}/$${filename}'s permission is ${perm}"
done
for...do...done 的数值处理
for (( 初始值; 限制值; 复制运算))
do
程序段
done
适合用于数值运算的循环
- 初始值:变量的初始值
- 限制值:变量的值在这个限制值内变化,则继续循环
- 复制运算:每做一次循环,变量也做相应的变化
#!/bin/bash
#Program:
# Calculate 1+2+...+num
#History:
#2022/8/26 zjw First release
PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:~/bin
export PATH
sum=0
read -p "Please input a number:" num
for (( i=0; i<=${num}; i=i+1 ))
do
declare -i sum=${sum}+${i}
done
echo "The sum is ${sum}"
shell不支持向c中的i++等操作,所以只能i=i+1
随机数与数组实验
只输出一组数据
#!/bin/bash
#Program:
# Try to tell you what you may eat.
#History:
#2022/9/1 zjw First release
PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:~/bin
export PATH
eat[1]="馋嘴鱼"
eat[2]="烤鸭饭"
eat[3]="麻辣烫(晚)"
eat[4]="大碗面"
eat[5]="酥肉小锅"
eat[6]="炒饭"
eat[7]="香锅(晚)"
eatnum=7
check=$(( ${RANDOM} * ${eatnum} / 32767 + 1 )) #重点在 ${RANDOM},变量产生的随机数范围在0-32676
echo "You may eat ${eat[${check}]}"
同时输出多组不重复的数据
#!/bin/bash
#Program:
# Try to tell you what you may eat.
#History:
#2022/9/1 zjw First release
PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:~/bin
export PATH
eat[1]="馋嘴鱼"
eat[2]="烤鸭饭"
eat[3]="麻辣烫(晚)"
eat[4]="大碗面"
eat[5]="酥肉小锅"
eat[6]="炒饭"
eat[7]="香锅(晚)"
eatnum=7
eated=0
while [ "${eated}" -lt 2 ]; do
check=$((${RANDOM}*${eatnum}/32676+1))
mycheck=0
if [ "${eated}" -ge 1 ]; then
for i in $(seq 1 ${eated})
do
if [ ${eatedcon[$i]} == $check ]; then
mycheck=1
fi
done
fi
if [ ${mycheck} == 0 ]; then
echo "You may eat ${eat[${check}]}"
eated=$((${eated}+1))
eatedcon[${eated}]=${check}
fi
done
5.shell脚本的跟踪与调试
每次跟着鸟哥写完脚本一运行,总是会出错,进去看了两三遍才发现变量写错了,但是通过bash的相关参数,可以不用执行脚本就可以检测出错误
sh [-nvx] scripts.sh
-n 不需要执行脚本,仅查询语法问题
-v 执行脚本前,先将脚本文件内容显示在屏幕上
-x 将使用到的脚本内容显示到屏幕上
如果没有语法问题,则不会显示信息
[zjw@zjw bin]$ sh -x mybirth.sh
+ PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:/home/zjw/bin
+ export PATH
+ read -p 'Please input your birthday(YYYYMMDD ex>20220831):' date1
Please input your birthday(YYYYMMDD ex>20220831):20220224
++ echo 20220224
++ grep '[0-9]\{8\}'
+ date_check=20220224
+ '[' 20220224 == '' ']'
++ date --date=20220224 +%s
+ declare -i date1_s=1645632000
++ date +%s
+ declare -i date_now=1662036001
+ declare -i date_total_s=-16404001
+ declare -i date_total=-189
+ '[' -16404001 -lt 0 ']'
+ echo 'You have had your birthday' 189 'days ago'
You have had your birthday 189 days ago
这是个很有用的参数,可以显示出脚本内容