Shell Scripts
shell script 有点像是早期的批处理文件,亦即是将一些指令汇整起来一次执行,但是 Shell script 拥有更强大的功能,那就是他可以进行类似程序 (program) 的撰写,并且不需 要经过编译 (compile) 就能够执行, 真的很方便。
如果写了一个程序文件/home/dmtsai/shell.sh,如何执行这个文件呢?有下面几个方法。
#第一支script
mkdir bin; cd bin
vim hello.sh
#然后编辑这个文件输入以下的内容(第一行必须要设置好)
#!/bin/bash
#Program:
# This program shows "Hello world" in your screen.
#History:
#2021/12/27 VBird First release
PATH=/bin:/sbin:/usr/bin:usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
echo -e "Hello world! \a \n"
exit 0
#然后保存一下,执行该文件看看结果
sh hello.sh
#另外,下面两个也可以执行这个文件
chmod a+x hello.sh
./hello.sh
简单范例
#对谈式脚本:变量内容由使用者决定
#撰写一个脚本,使用者输入first name和last name,在屏幕上显示"Your full name is XXX"
vim showname.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}"
./showname.sh
#随日期变化:利用date进行文件的创建
#假设我想要创建三个空文件(通过touch),文件名开头由使用者输入决定,如果想要用前天,昨天,今天的日期来命名这些文件,该如何是好?
vim create_3_filename.sh
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
#1\.让使用者输入文件名称,并取得fileuser这个变量
#纯粹显示一行提示信息
echo -e "I will use 'touch' command to create 3 files."
#提示使用者输入
read -p "Please input your filename: " fileuser
#2\.为了避免使用者随意按Enter,利用[变量功能](../Text/index.HTML#variable_other_re)分析文件名
filename=${filuser:-"filename"}#也就是fileuser的默认值是filename
#3\.开始利用date指令取得所需要的文件名
#其中,$(command) 有取得讯息、变量的设置功能、变量的累加以及touch指令辅助。
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}
#4\.将文件名创建
touch "${file1}"
touch "${file2}"
touch "${file3}"
#数值计算:简单的加减乘除
vim multiplying.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; Program will cross these 2 numbers."
read -p "first number: " firstnu
read -p "second number: " secnu
#法1
total=$((${firstnu}*${secnu}))
#法2
declare -i total=${firstnu}*${secnu}
echo -e "\nThe result of ${firstnu} x ${secnu} is ${total}"
下面这种方式计算还是挺方便的
echo $((13%3))
如果想计算含有小数点的数据时,可以通过bc这个指令的协助
echo "123.123*55.9" | bc
#数值计算:通过bc计算pi
#计算pi时,小数点以下的位数可以无限制的延伸下去,bc提供了一个运算pi的函数,通过bc -l来调用,接下来编写一个脚本让使用者输入小数点位数,得到对应的Pi值
vim cal_pi.sh
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
echo -e "This program will calculate pi value. \n"
echo -e "You should input a float number to calculate pi value.\n"
read -p "The scale number (10-10000) ? " checking
num=${checking:-"10"}#默认是10
echo -e "Starting calculate pi value. Be patient."
time echo "scale=${num}; 4*a(1)" | bc -lq
script的执行方式差异
1、利用直接执行的方式来执行script
bash xx.sh
sh xx.sh
相对路径./xx.sh
绝对路径
这种情况下执行会重开一个bash,script实际上是在子程序的bash内执行的。当子程序完成后,在子程序内的各项动作或变量会结束而不会传回到父程序中。
2、利用source来执行脚本:在父程序中执行
source xx.sh
此时在程序中设置的变量在当前窗口都是可以echo到的
判断式
利用test指令的测试功能
#检查/dmtsai是否存在
test -e /dmtsai#这样直接执行不会显示任何讯息,可以结合&&||等展现结果
test -e /dmtsai && echo 'exist' || echo "Not exist"
除了测试是否存在之外,还可以进行以下测试
接下来举几个test的简单例子
首先,判断一下,让使用者输入一个文件名,我们判断:
1、 这个文件是否存在,若不存在则给予一个“Filename does not exist”的讯息,并中断程序;
2、 若这个文件存在,则判断他是个文件或目录,结果输出“Filename is regular file”或 “Filename is directory”
3、 判断一下,执行者的身份对这个文件或目录所拥有的权限,并输出权限数据!
vim file_perm.sh
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
#1\.让使用者输入文件名,并判断使用者是否真的有输入字串
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
#2\.判断文件是否存在,若不存在则显示讯息并结束脚本
test ! -e ${filename} && echo "The filename '${filename}' do not exist" && exit 0
#3\.开始判断文件类型与属性
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
#4\.开始输出信息
echo "The filename: ${filename} is a ${filetype}"
echo "And the permissions for you are : ${perm}"
利用判断符号[ ]
如果想知道${HOME}这个变量是否为空,可以用下面的命令
[ -z “${HOME}” ] ; echo $?
需要注意的是,因为中括号用在很多地方,包括万用字符与正则表达式等,所以如果要在bash中的语法当中使用中括号作为shell的判断式时,需要加空白键,需要注意以下几点:
1、在中括号 [] 内的每个元件都需要有空白键来分隔;
2、在中括号内的变量,最好都以双引号括号起来;
3、在中括号内的常数,最好都以单或双引号括号起来
中括号的使用方法与test几乎一模一样,只是中括号比较常用在条件判断式if…then的情况中。
接下来做一个小案例
1、 当执行一个程序的时候,这个程序会让使用者选择 Y 或 N ,
2、如果使用者输入 Y 或 y 时,就显示“ OK, continue ”
3、 如果使用者输入 n 或 N 时,就显示“ Oh, interrupt !”
4、 如果不是 Y/y/N/n 之内的其他字符,就显示“ I don’t know what your choice is ”
vim ans_yn.sh
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
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
Shell script的默认变量($0,$1)
如果想重新启动系统的网络,可以使用下面的命令
file /etc/init.d/network#首先查询一下发现它是一个bash的可执行脚本
/etc/init.d/network restart#这个可以重新启动
/etc/init.d/network stop#这个可以关闭网络服务
之前,如果要依据程序的执行给予一些变量不同的任务时,使用的是read功能,但read的问题是需要手动从键盘输入一些判断式。如果通过指令后面接参数, 那么一个指令就能够处理完毕而不需要手动再次输入一些变量行为!这样 下达指令会比较简单方便啦!
其实 script 针对参数已经有设置好一些变量名称了!对应如 下:
?执行的脚本文件名为 $0 这个变量,第一个接的参数就是 $1 ,接着就是$2$3这些
然后做一个小案例,假设我要执行一个可以携带参数的 script ,执行该脚本后屏幕会显示如下的数据:
程序的文件名为何?
共有几个参数?
若参数的个数小于 2 则告知使用者参数数量太少
全部的参数内容为何?
第一个参数为何?
第二个参数为何?
vim how_paras.sh
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
echo "The script name is ${0}"
echo "Total parameter number is $#"
[ "$#" < 2 ] && echo "The number of parameter is less than 2\. Stop here." && exit 0
echo "Your whole parameter is $@"
echo "The 1st parmeter : ${1}"
echo "The 2nd parmeter : ${2}"
#调用上面的脚本
sh how_paras.sh theone haha quot
shift:造成参数变量号码偏移
把上面的内容稍微改一下,用来显示每次偏移后参数的变化
vim shift_paras.sh
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
echo "Total parameter number is $#"
echo "Your whole parameter is $@"
shift #进行第一次一个变量的偏移
echo "Total parameter number is $#"
echo "Your whole parameter is $@"
shift 3 #进行第二次三个变量的偏移
echo "Total parameter number is $#"
echo "Your whole parameter is $@"
#调用上面的脚本执行
sh shift_paras.sh one two three four five six
条件判断式
利用if…then
单层、简单条件判断式
if [条件判断式]; then
当条件判断式成立时,可以进行的指令工作内容
fi #结束if
条件判断式可以多个括号写然后彼此之间用&&或||隔开,分别表示AND和OR,所以,此时&&和||与指令下达时不同
[ “${yn}” == “Y” -o “${yn}” == “y” ]可替换为
[ “${yn}” == “Y” ] || [ “${yn}” == “y” ]
#使用if...then的一个样例
cp ans_yn.sh ans_yn-2.sh
vim ans_yn-2.sh
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
read -p "Please input (Y/N) : " yn
if [ "${yn}" == "Y" ] || [ "${yn}" == "y" ]; then
echo "OK,continue"
exit 0
fi
if [ "${yn}" == "N" ] || [ "${yn}" == "n" ]; then
echo "Oh,interrupt!"
exit 0
fi
echo "I don't know what your choice is" && exit 0
多重、复杂条件判断式
if [条件表达式]; then
当条件判断式成立时,可以进行的指令工作内容
else
当条件判断式不成立时,可以进行的指令工作内容
fi
若是有更复杂的情况,多个分支
if [条件表达式1]; then
当条件判断式1成立时,可以进行的指令工作内容
elif [条件表达式2]; then
当条件判断式1成立时,可以进行的指令工作内容
else
当条件判断式1和2均不成立时,可以进行的指令工作内容
fi
cp ans_yn-2.sh ans_yn-3.sh
vim ans_yn-3.sh
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
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
再来一个例子,使用上面的if elif 啥的
vim hello-2.sh
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
if [ "${1}" == "hello" ]; then
echo "Hello, how are you ? "
elif [ "${1}" == "" ]; then
echo "you must input parameters"
else
echo "The only parmeter is 'hello'"
fi
再来一个例子:输入退伍日期,计算还有几天退伍
由于日期是要用相减的方式来处置,所以我们可以通过使用 date 显示日期与时间,将他转为 由 1970-01-01 累积而来的秒数, 通过秒数相减来取得剩余的秒数后,再换算为日数即可。 整个脚本的制作流程有点像这样:
1、 先让使用者输入他们的退伍日期;
2.、再由现在日期比对退伍日期;
3、 由两个日期的比较来显示“还需要几天”才能够退伍的字样。
vim cal_retired.sh
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
#1\.告知使用者这支程序的用途,并且告知应该如何输入日期格式
echo "This program will try to calculate:"
echo "How mny days before your demobilization date..."
read -p "Please input your demobilization date (YYYYMMDD):" date2
#2\.测试一下输入的内容是否正确
date_d=$(echo ${date2} | grep '[0-9]\{8\}')
if [ "${date_d}" == ""]; then
echo "You input the wrong date format...."
exit 1
fi
#3\.开始计算日期
declare -i date_dem=$(date --date="${date2}" +%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))#转为日数
if [ "${date_d}" < "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."
利用case…esac判断
case $变量名称 in
“第一个变量内容”)
程序段
;;
“第二个变量内容”)
程序段
;;
*)#*代表所有其他值
不包含第一个和第二个变量内容的其他程序执行段
exit 1
;;
esac
一般来说,使用case $变量 in 这个语法时,当中的那个变量有两种取得的方式:
直接下达式:这个就是在命令中作为参数传进去
互动式:通过read这个指令来让使用者输入变量的内容
来一个例子
#让使用者能够输入 one, two, three , 并且将使用者的变量显示到屏幕上,如果不是 one, two, three 时,就告知使用者仅 有这三种选择
vim show123.sh
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
echo "This program will input your selection!"
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
上面是自己输入参数,也可以在下命令的时候输入参数,case后面改成${1}就可以了
利用function功能
function fname(){
程序段
}
要注意的是,因为shell script的执行方式是由上而下,由左而右,因此里面函数的设置一定要在程序的最前面
#将上面改一下,加一个函数
vim show123-2.sh
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
function printit(){
echo -n "Your choice is "#加上-n可以不断行继续在同一行显示
}
echo "This program will input your selection!"
#read -p "Input your choice:" choice
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也是拥有内置变量的,他的内置变量与shell script类似,函数名称为$0,后续接的变量为$1,$2,…,两者并不等同,要注意不能搞混了,再次改写一下上面的例子
vim show123-3.sh
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
function printit(){
echo -n "Your choice is ${1}"#加上-n可以不断行继续在同一行显示
}
echo "This program will input your selection!"
#read -p "Input your choice:" choice
case ${1} in
"one")
**printit 1**;
;;
"two")
**printit 2**
;;
"three")
**printit 3**
;;
*)
echo "Usage ${0} {one|two|three}"
;;
esac
#调用上面的脚本
sh show123-3.sh one#此时出现的结果就是1了
循环
while do done, until do done(不定循环)
#满足条件就一直循环
while [condition]
do
程序段落
done
#满足条件就终止循环
until [condition]
do
程序段落
done
一个小练习:使用者输入yes或者是YES才结束程序的执行,否则就一直告知使用者输入字串
#用while
vim yes_to_stop.sh
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
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."
#用until
vim yes_to_stop-2.sh
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
until [ "${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."
再来一个小例子:计算1+2+3+…+100
vim cal_1_100.sh
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
i=0
sum=0
while i<=100
do
i=$((${i}+1))
sum=$((${sum}+${i}))
done
echo "The result of '1+2+3+...+100' is ${sum}"
#执行一下上面这个脚本
sh cal_1_100.sh
for…do…done(固定循环)
for var in con1 con2 con3 …
do
程序段
done
以上面的例子来说,这个 $var 的变量内容在循环工作时:
1、 第一次循环时, $var 的内容为 con1 ;
2、 第二次循环时, $var 的内容为 con2 ;
3、 第三次循环时, $var 的内容为 con3 ;
然后做个小练习
vim show_animal.sh
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
for animal in dog cat element
do
echo "There are ${animal}s...."
#用id检查使用者的信息
vim userid.sh
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
users=$(cut -d ":" -f 1 /etc/passwd)
for username in ${users}
do
id ${username}
done
一连串的数字循环的情况
vim pingip.sh
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
network="192.168.1"
for sitenu in $(seq 1 100)
do
#取得ping的回传值是正确的还是失败的
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
上面 ( s e q 1100 ) 表 示 连 续 出 现 的 意 思 , 此 外 , 也 可 以 使 用 1..100 来 取 代 (seq 1 100)表示连续出现的意思,此外,也可以使用{1..100}来取代 (seq1100)表示连续出现的意思,此外,也可以使用1..100来取代(seq 1 100),中间两个小点表示连续出现,{a…g}就表示从a到g的所有字母
再来一个例子:让使用者输入某个目录文件名。然后找出某目录内的文件名的权限。
vim dir_perm.sh
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
#1先看看这个目录是否存在
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
#2测试文件
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
for…do…done的数值处理
除了上述的方法外,for循环还有另外一种写法
for ((初始值;限制值;执行步阶))
do
程序段
done
#从1加到用户输入的数
vim cal_1_100-2.sh
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
read -p "Please input a number, I will count for 1+2+...+your_input: " nu
s=0
for ((i=0; i<=nu; i=i+1))
do
s=$(${s}+${i})
done
echo "The result of '1+2+3+...+${nu}' is ${s}"
搭配乱数与阵列的实验
vim what_to_eat.sh
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
eat[1]="haha"
eat[2]='heihei'
eat[3]='houhou'
eat[4]='xixi'
eat[5]='wawa'
eatnum=5
check=$((${RANDOM}*${eatnum}/32767 + 1))
echo "your may eat ${eat[${check}]}"
上面每次出来一个店家,如果想要每次出来3个呢
vim what_to_eat-2.sh
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
eat[1]="haha"
eat[2]='heihei'
eat[3]='houhou'
eat[4]='xixi'
eat[5]='wawa'
eatnum=5
eated=0
while [ "${eated}" < "3" ]
do
check=$((${RANDOM}*${eatnum}/32767 + 1))
mycheck=0
if [ "${eated}" >= 1]; then
for i in $(seq 1 ${eated})
do
if [ "${eatedcon[$i]}" == $check ]; then
mycheck=1
fi
done
fi
if [ ${mycheck} == 0 ]; then
echo "your may eat ${eat[${check}]}"
eated=$((${eated}+1))
eatedcon[${eated}]=${check}
fi
done
echo "your may eat ${eat[${check}]}"
shell script的追踪与debug
sh [-nvx] scripts.sh
选项与参数:
-n :不要执行 script,仅查询语法的问题;
-v :再执行 sccript 前,先将 scripts 的内容输出到屏幕上;
-x :将使用到的 script 内容显示到屏幕上,这是很有用的参数!
#范例1:测试dir_perm.sh有无语法问题
sh -n dir_perm.sh#若语法没有问题不会显示任何信息
#范例2:将show_animal.sh的执行过程全部列出来
sh -x show_animal.sh