重头戏终于来了!
shell script 是利用 shell 的功能所写的一个『程序 (program)』,这个程序是使用纯文本文件,将一些 shell 的语法与指令(含外部指令)写在里面, 搭配正规表示法、管线命令与数据流重导向等功能,以达到我们所想要的处理目的。
#!/bin/bash 在宣告这个 script 使用的 shell 名称。
用vi test.sh创建一个shell脚本:
#!/bin/bash
echo "Hello World !"
执行方式:
(1)bash test.sh
(2)chmod a+x test.sh;./test.sh
在每个 script 的文件头处记录好:
1、请你以 read 指令的用途,撰写一个 script ,他可以让使用者输入: 1. first name 与 2. last name, 最后并且在屏幕上显示:
『Your full name is: 』的内容。
2、随日期变化:利用 date 进行文件的建立
假设我想要建立三个空的文件 (透过 touch) ,档名最开头由使用者输入决定,假设使用者输入 filename 好了,那今天的日期是 2015/07/16 , 我想要以前天、昨天、今天的日期来建立这些文件,亦即 filename_20150714, filename_20150715, filename_20150716 ,该如何是好?
#!/bin/bash
# Program:
# Program creates three files, which named by user's input and date command.
# History:
# 2018/06/13 yue First release
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 ,利用变量功能分析档名是否有设定?
filename=${fileuser:-"filename"} # 开始判断有否配置文件名
# 3. 开始利用 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}
# 4. 将档名建立吧! touch命令用于修改文件或者目录的时间属性,包括存取时间和更改时间。若文件不存在,系统会建立一个新的文件。
touch "${file1}" # 底下三行在建立文件
touch "${file2}"
touch "${file3}"
3、数值运算:简单的加减乘除
用户输入两个变量, 然后将两个变量的内容相乘,最后输出相乘的结果。
#!/bin/bash
# Program:
# User inputs 2 integer numbers; program will cross these two numbers.
# History:
#2018/06/13 yue First Release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
echo -e "Your should inout 2 numbers.\n"
read -p "first number: " firstnu
read -p "second number: " secondnu
total=$((${firstnu}*${secondnu}))
echo -e "\nThe result is: ==>${total}"
4、数值运算:透过 bc 计算 pi
#!/bin/bash
# Program:
# User input a scale number to calculate pi number.
# History:
#2018/06/13 yue First release
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"}
echo -e "Starting calculate pi value."
time echo "scale=${num}; 4*a(1)" | bc -lq
4*a(1) 是 bc 主动提供的一个计算 pi 的函数,至于 scale 就是要 bc 计算几个小数点下位数的意思。当 scale 的数值越大, 代表 pi 要被计算的越精确。
不同的 script 执行方式会造成不一样的结果! 脚本的执行方式除了前面小节谈到的方式之外,还可以利用 source 或小数点 (.) 来执行。
1、利用直接执行的方式来执行 script——使用bash执行脚本时,echo script中的变量是无法显示内容的。
2、利用 source 来执行脚本:在父程序中执行——用source执行script时,可以用echo显示变量的内容。
例1——
首先,判断一下,让使用者输入一个档名,我们判断:
#!/bin/bash
# Program:
# User input a filename, program will check the flowing:
# 1.) exist? 2.) file/directory? 3.) file permissions
# History:
#2018/06/18 yue First release
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"
read -p "Input a filename : " filename
test -z ${filename} && echo "You must input a filename." && exit 0
# 2. 判断文件是否存在?若不存在则显示讯息并结束脚本
test ! -e ${filename} && echo "The filename '${filenmae}' 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}"
2、利用判断符号 [ ]
(1)想要知道 ${HOME} 这个变量是否为空——[ -z "${HOME}" ] ; echo $?
注:使用中括号必须要特别注意,因为中括号用在很多地方,包括通配符与正规表示法等等,所以如果要在 bash 的语法当中使用中括号作为 shell 的判断式时,必须要注意中括号的两端需要有空格符来分隔。
所以说,最好要注意:
中括号比较常用在条件判断式 if ..... then ..... fi 的情况中。
例2——ans_yn.sh
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 』
#!/bin/bash
# Program:
# This program shows the user's choice
# History:
#2018/06/13 yue First release
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
(3)Shell script 的默认参数($0, $1...)
指令可以带有选项与参数,例如 ls -la 可以察看包含隐藏文件的所有属性与权限。那么 shell script 能不能在脚本文件名后面带有参数呢?
举例来说,如果你想要重新启动系统的网络,可以这样做:
上面的指令可以『重新启动 /etc/init.d/network 这支程序』的意思
如果要依据程序的执行给予一些变量去进行不同的任务时,一开始使用的是 read 的功能!但 read 功能的问题是你得要手动由键盘输入一些判断式。如果透过指令后面接参数, 那么一个指令就能够处理完毕而不需要手动再次输入一些变量行为。
script 针对参数已经有设定好一些变量名称:
执行的脚本文件名为 $0 这个变量,第一个接的参数就是 $1 啊~ 所以,只要在 script 里面善用 $1 的话,就可以很简单的立即下达某些指令功能了。除了这些数字的变量之外,还有一些较为特殊的变量可以在 script 内使用来呼叫这些参数:
例3——假设我要执行一个可以携带参数的 script ,执行该脚本后屏幕会显示如下的数据:
#!/bin/bash
# Program:
# Program shows the script name, parameters...
# History:
# 2015/07/16 yue First release
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 ==> $#"
[ "$#" -lt 2 ] && echo "The number of parameter is less than 2. Stop here." && exit 0
echo "Your whole parameter is ==> '$@'"
echo "The 1st parameter ==> ${1}"
echo "The 2nd parameter ==> ${2}"
(4)shift:造成参数变量号码偏移
#!/bin/bash
# Program:
# Program shows the effect of shift function.
# History:
# 2018/06/13 yue First release
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 # 进行第一次『一个变量的 shift 』
echo "Total parameter number is ==> $#"
echo "Your whole parameter is ==> '$@'"
shift 3 # 进行第二次『三个变量的 shift 』
echo "Total parameter number is ==> $#"
echo "Your whole parameter is ==> '$@'"
1、利用 if .... then
(1)单层、简单条件判断式[ "${yn}" == "Y" -o "${yn}" == "y" ] 可替换为 [ "${yn}" == "Y" ] || [ "${yn}" == "y" ]
例1:将 ans_yn.sh 这个脚本修改成为 if ... then 的样式
#!/bin/bash
# Program:
# This program shows the user's choice
# History:
#2018/06/13 yue First release
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
(2)多重、复杂条件判断式
要注意的是, elif 也是个判断式,因此出现 elif 后面都要接 then 来处理!但是 else 已经是最后的没有成立的结果了, 所以 else 后面并没有 then!再次重写ans_yn.sh
#!/bin/bash
# Program:
# This program shows the user's choice
# History:
# 2018/06/13 yue First release
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
例2——
一般来说,如果不希望用户由键盘输入额外的数据时, 可以使用上一节提到的参数功能 ($1)!让用户在下达指令时就将参数带进去!
让用户输入『 hello 』这个关键词时,利用参数的方法可以这样依序设计:
1. 判断 $1 是否为 hello,如果是的话,就显示 "Hello, how are you ?";
2. 如果没有加任何参数,就提示使用者必须要使用的参数下达法;
3. 而如果加入的参数不是 hello ,就提醒使用者仅能使用 hello 为参数。
#!/bin/bash
# Program:
# Check $1 is equal to "hello"
# History:
# 2018/06/13 yue First release
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, ex> {${0} someword}"
else
echo "The only parameter is 'hello', ex> {${0} hello}"
fi
例3——
netstat 这个指令可以查询到目前主机有开启的网络服务端端口 (service ports)
上面的重点是『Local Address (本地主机的 IP 与端口口对应)』那个字段,他代表的是本机所启动的网络服务! IP 的部分说明的是该服务位于那个接口上,若为 127.0.0.1 则是仅针对本机开放,若是0.0.0.0 或 ::: 则代表对整个 Internet 开放 (更多信息请参考服务器架设篇的介绍)。 每个埠口 (port)都有其特定的网络服务,几个常见的 port 与相关网络服务的关系是:
假设主机有兴趣要侦测的是比较常见的 port 21, 22, 25 及 80 时,那如何透过 netstat 去侦测主机是否有开启这四个主要的网络服务端口口呢?由于每个服务的关键词都是接在冒号『 : 』后面, 所以可以藉由撷取类似『 :80 』来侦测!那就可以简单的这样写这个程序:
#!/bin/bash
# Program:
# Using netstat and grep to detect WWW,SSH,FTP and Mail services.
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
# 1. 先作一些告知的动作而已~
echo "Now, I will detect your Linux server's services!"
echo -e "The www, ftp, ssh, and mail(smtp) will be detect! \n"
# 2. 开始进行一些测试的工作,并且也输出一些信息啰!
testfile=/dev/shm/netstat_checking.txt
netstat -tuln > ${testfile} # 先转存数据到内存当中!不用一直执行 netstat
testing=$(grep ":80 " ${testfile}) # 侦测看 port 80 在否?
if [ "${testing}" != "" ]; then
echo "WWW is running in your system."
fi
testing=$(grep ":22 " ${testfile}) # 侦测看 port 22 在否?
if [ "${testing}" != "" ]; then
echo "SSH is running in your system."
fi
testing=$(grep ":21 " ${testfile}) # 侦测看 port 21 在否?
if [ "${testing}" != "" ]; then
echo "FTP is running in your system."
fi
testing=$(grep ":25 " ${testfile}) # 侦测看 port 25 在否?
if [ "${testing}" != "" ]; then
echo "Mail is running in your system."
fi
例4——让用户输入他的退伍日期,让你去帮他计算还有几天才退伍?
由于日期是要用相减的方式来处置,所以我们可以透过使用 date 显示日期与时间,将他转为由1970-01-01 累积而来的秒数, 透过秒数相减来取得剩余的秒数后,再换算为日数即可。整个脚本的制作流程有点像这样:
1. 先让使用者输入他们的退伍日期;
2. 再由现在日期比对退伍日期;
3. 由两个日期的比较来显示『还需要几天』才能够退伍的字样。
利用『 date --date="YYYYMMDD" +%s 』转成秒数后
#!/bin/bash
# Program:
# You input your demobilization date, I calculate how many days before you demobilize.
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 many days before your demobilization date..."
read -p "Please input your demobilization date (YYYYMMDD ex>20150716): " 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_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 判断
3、利用 function 功能
因为 shell script 的执行方式是由上而下,由左而右, 因此在 shell script 当中的 function 的设定一定要在程序的最前面。
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
1、while do done, until do done (不定循环)
(1)while——当 condition 条件成立时,就进行循环,直到condition 的条件不成立才停止
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."
(2)until——与 while 相反,当 condition 条件成立时,就终止循环, 否则就持续进行循环的程序段。
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."
例1——计算 1+2+3+....+100
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
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...do...done (固定循环)
例2——
假设有三种动物,分别是 dog, cat, elephant 三种, 我想每一行都输出这样:『There are dogs...』之类的字样
for animal in dog cat elephant
do
echo "There are ${animal}s.... "
done
例3——透过管线命令的 cut 捉出单纯的账号名称后,以 id 分别检查使用者的标识符与特殊参数
users=$(cut -d ':' -f1 /etc/passwd) # 撷取账号名称
for username in ${users} # 开始循环进行!
do
id ${username}
done
例4——利用 ping这个可以判断网络状态的指令, 来进行网络状态的实际侦测时,我想要侦测的网域是本机所在的
192.168.1.1~192.168.1.100,由于有 100 台主机, 总不会要我在 for 后面输入 1 到 100 吧?此时可以这样做:
network="192.168.1" # 先定义一个网域的前面部分!
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
例5——让用户输入某个目录文件名, 然后我找出某目录内的文件名的权限。
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
3、for...do...done 的数值处理
这种语法适合于数值方式的运算当中,在 for 后面的括号内的三串内容意义为:
初始值:某个变量在循环当中的起始值,直接以类似 i=1 设定好;
限制值:当变量的值在这个限制值的范围内,就继续进行循环。例如 i<=100;
执行步阶:每作一次循环时,变量的变化量。例如 i=i+1。
值得注意的是,在『执行步阶』的设定上,如果每次增加 1 ,则可以使用类似『i++』的方式,亦即是 i 每次循环都会增加一的意思。好,我们以这种方式来进行 1 累加到使用者输入的循环吧!
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}"