20220824 12. 学习 Shell Scripts
12.1 什么是 Shell scripts
shell script (程序化脚本)
shell script 是利用 shell 的功能所写的一个“程序 (program)”,这个程 序是使用纯文本文件,将一些 shell 的语法与指令(含外部指令)写在里面, 搭配正则表达 式、管线命令与数据流重导向等功能,以达到我们所想要的处理目的。
shell script 可以简单的被看成是批处理文件
12.1.1 干嘛学习 shell scripts
虽然 shell script 号称是程序 (program) ,但实际上, shell script 处理数据的速度上 是不太够的。因为 shell script 用的是外部的指令与 bash shell 的一些默认工具,所以,他常 常会去调用外部的函数库
shell script 用在系统管理上面是很好的一项工具,但是用在处理大量数值运算上, 就不够好了, 因为 Shell scripts 的速度较慢,且使用的 CPU 资源较多,造成主机资源的分配不良。
12.1.2 第一支 script 的撰写与执行
在 shell script 的撰写中还需要用到下面的注意事项:
-
指令的执行是从上而下、从左而右的分析与执行;
-
指令的下达就如同第四章内提到的: 指令、选项与参数间的多个空白都会被忽略掉;
-
空白行也将被忽略掉,并且 [tab] 按键所推开的空白同样视为空白键;
-
如果读取到一个 Enter 符号 (CR) ,就尝试开始执行该行 (或该串) 命令;
-
至于如果一行的内容太多,则可以使用
\[Enter]
来延伸至下一行; -
“ # ”可做为注解!任何加在 # 后面的数据将全部被视为注解文字而被忽略!
如何执行 /home/dmtsai/shell.sh
-
直接指令下达: shell.sh 文件必须要具备可读与可执行 (rx) 的权限
-
绝对路径:使用 /home/dmtsai/shell.sh 来下达指令;
-
相对路径:假设工作目录在 /home/dmtsai/ ,则使用 ./shell.sh 来执行
-
变量“PATH”功能:将 shell.sh 放在 PATH 指定的目录内,例如: ~/bin/
-
-
以 bash 程序来执行:通过“ bash shell.sh ”或“ sh shell.sh ”来执行
由于 CentOS 默认使用者主文件夹下的 ~/bin 目录会被设置到 ${PATH} 内,所 以你也可以将 shell.sh 创建在 /home/dmtsai/bin/ 下面 ( ~/bin 目录需要自行设置) 。此时, 若 shell.sh 在 ~/bin 内且具有 rx 的权限,那就直接输入 shell.sh 即可执行该脚本程序!
为何“ sh shell.sh ”也可以执行呢?这是因为 /bin/sh 其实就是 /bin/bash (链接文件),使 用 sh shell.sh 亦即告诉系统,我想要直接以 bash 的功能来执行 shell.sh 这个文件内的相关 指令的意思,所以此时你的 shell.sh 只要有 r 的权限即可被执行
撰写第一支 script
mkdir bin; cd bin
vim hello.sh
#!/bin/bash
# Program:
# This program shows "Hello World!" in your screen.
# History:
# 2015/07/16 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
-
#!/bin/bash
宣告这个文件内的语法使用 bash 的语法!那么当这个程 序被执行时,他就能够载入 bash 的相关环境配置文件 (一般来说就是 non-login shell 的 ~/.bashrc) -
整个 script 当中,除了第一行的“ #! ”是用来宣告 shell 的之外,其他的 # 都是“注解”用途!。一般来说, 建议你一定要养成说明该 script 的:
-
内容与功能;
-
版本信息;
-
作者与联络方式;
-
创建日期;
-
历史纪录
-
-
主要环境变量的宣告: 建议务必要将一些重要的环境变量设置好,鸟哥个人认为, PATH 与 LANG (如果有使用到输出相关的信息时) 是当中最重要的! 如此一来,则可 让我们这支程序在进行时,可以直接下达一些外部指令,而不必写绝对路径
-
主要程序部分 就将主要的程序写好即可!在这个例子当中,就是 echo 那一行
-
执行成果告知 (定义回传值),可以使用
$?
这个变量来观察
# 执行
sh hello.sh
也可以利用:chmod a+x hello.sh; ./hello.sh
来执行这个 script
12.1.3 撰写 shell script 的良好习惯创建
在每个 script 的文件开始处记录好:
-
script 的功能;
-
script 的版本信息;
-
script 的作者与联络方式;
-
script 的版权宣告方式;
-
script 的 History (历史纪录);
-
script 内较特殊的指令,使用“绝对路径”的方式来下达;
-
script 运行时需要的环境变量预先宣告与设置。
在较为特殊的程序码部分,个人建议务必要加上注解说明
程序码的撰写最好使用巢状方式,在包覆的内部程序码最好能以 [tab] 按键的空格向后推
使用撰写 script 的工具最好使用 vim 而不是 vi ,因为 vim 会有额外的 语法检验机制
12.2 简单的 shell script 练习
12.2.1 简单范例
对谈式脚本:变量内容由使用者决定
vim showname.sh
#!/bin/bash
# Program:
# User inputs his first name and last name. Program shows his full name.
# History:
# 2015/07/16 VBird First release
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}" # 结果由屏幕输出
随日期变化:利用 date 进行文件的创建
假设使用者输入 filename 好了,那今天的日期是 2015/07/16 , 我想要以前天、昨天、今天的日期来创建这些文件,亦即 filename_20150714, filename_20150715, filename_20150716
vim create_3_filename.sh
#!/bin/bash
# Program:
# Program creates three files, which named by user's input and date command.
# History:
# 2015/07/16 VBird 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 ,利用[变量功能](../Text/index.html#variable_other_re)分析文件名是否有设置?
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 "${file1}" # 下面三行在创建文件
touch "${file2}"
touch "${file3}"
数值运算:简单的加减乘除
可以使用 declare 来定义变量的类型吧? 当变量定义成为整数后 才能够进行加减运算啊!此外,我们也可以利用“ $((计算式))
”来进行数值运算的。 可 惜的是, bash shell 里头默认仅支持到整数的数据而已
输入两个变量, 然后将两个变量的内容相乘,最后输出相乘的结果
vim multiplying.sh
#!/bin/bash
# Program:
# User inputs 2 integer numbers; program will cross these two numbers.
# History:
# 2015/07/16 VBird First release
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 multiplying them! \n"
read -p "first number: " firstnu
read -p "second number: " secnu
total=$((${firstnu}*${secnu}))
echo -e "\nThe result of ${firstnu} x ${secnu} is ==> ${total}"
在数值的运算上,可以使用 declare -i total=${firstnu}*${secnu}
或 ecnu total=(({firstnu
如果你想要计算含有小数点的数据
echo "123.123*55.9" | bc
数值运算:通过 bc 计算 pi
bc 有提供一个运算 pi 的函数, 只是想要使用该函数必须要使用 bc -l
来调用才行
vim cal_pi.sh
#!/bin/bash
# Program:
# User input a scale number to calculate pi number.
# History:
# 2015/07/16 VBird 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 calcuate pi value. Be patient."
time echo "scale=${num}; 4*a(1)" | bc -lq
4*a(1)
是 bc 主动提供的一个计算 pi 的函数,至于 scale 就是要 bc 计 算几个小数点下位数的意思
为了要确认虚拟机的效率问题,所以很多时候需要保持虚拟机在忙碌 的状态~将 scale 调高一些,用以达到我们需要 CPU 忙碌的状态喔!
12.2.2 script 的执行方式差异 (source, sh script, ./script)
利用直接执行的方式来执行 script
直接指令下达 (不论是绝对路径/相对路径还是 ${PATH} 内),或者是 利用 bash (或 sh) 来下达脚本时, 该 script 都会使用一个新的 bash 环境来执行脚本内的 指令!也就是说,使用这种执行方式时, 其实 script 是在子程序的 bash 内执行的
当子程序完成后,在子程序内的各项变量或动作将会结束而不会传回到父程序中
echo ${firstname} ${lastname} # 不存在
sh showname.sh # 在 script 运行中,这两个变量有生效
echo ${firstname} ${lastname} # 父程序的bash中还是不存在
利用 source 来执行脚本:在父程序中执行
showname.sh 会在父程序中执行的,因此各项动作都会在原本的 bash 内生效!这也是为啥 你不登出系统而要让某些写入 ~/.bashrc 的设置生效时,需要使用“ source ~/.bashrc ”而不能 使用“ bash ~/.bashrc ”是一样的啊!
echo ${firstname} ${lastname} # 不存在
source showname.sh # 在 script 运行中,这两个变量有生效
echo ${firstname} ${lastname} # 父程序的bash中存在
12.3 善用判断式
12.3.1 利用 test 指令的测试功能
test -e /dmtsai
执行结果并不会显示任何讯息,但最后我们可以通过 $? 或 && 及 || 来展现整个结果
# 判断目录是否存在,并输出信息
test -e /dmtsai && echo "exist" || echo "Not exist"
1. 关于某个文件名的“文件类型”判断
测试的标志 | 代表意义 |
---|---|
-e | 该“文件名”是否存在?(常用) |
-f | 该“文件名”是否存在且为文件(file)?(常用) |
-d | 该“文件名”是否存在且为目录(directory)?(常 用) |
-b | 该“文件名”是否存在且为一个 block device 设备? |
-c | 该“文件名”是否存在且为一个 character device 设 备? |
-S | 该“文件名”是否存在且为一个 Socket 文件? |
-p | 该“文件名”是否存在且为一个 FIFO (pipe) 文件? |
-L | 该“文件名”是否存在且为一个链接文件? |
2. 关于文件的权限侦测
测试的标志 | 代表意义 |
---|---|
-r | 侦测该文件名是否存在且具有“可读”的权限? |
-w | 侦测该文件名是否存在且具有“可写”的权限? |
-x | 侦测该文件名是否存在且具有“可执行”的权限? |
-u | 侦测该文件名是否存在且具有“SUID”的属性? |
-g | 侦测该文件名是否存在且具有“SGID”的属性? |
-k | 侦测该文件名是否存在且具有“Sticky bit”的属性? |
-s | 侦测该文件名是否存在且为“非空白文件”? |
3. 两个文件之间的比较
测试的标志 | 代表意义 |
---|---|
-nt | (newer than)判断 file1 是否比 file2 新 |
-ot | (older than)判断 file1 是否比 file2 旧 |
-ef | 判断 file1 与 file2 是否为同一文件,可用在判断 hard link 的判定上。 主要意义在判定,两个文件是 否均指向同一个 inode 哩! |
4. 关于两个整数之间的判定
测试的标志 | 代表意义 |
---|---|
-eq | 两数值相等 (equal) |
-ne | 两数值不等 (not equal) |
-gt | n1 大于 n2 (greater than) |
-lt | n1 小于 n2 (less than) |
-ge | n1 大于等于 n2 (greater than or equal) |
-le | n1 小于等于 n2 (less than or equal) |
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 |
6. 多重条件判定
测试的标志 | 代表意义 |
---|---|
-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 |
示例
-
这个文件是否存在,若不存在则给予一个“Filename does not exist”的讯息,并中断程 序;
-
-
若这个文件存在,则判断他是个文件或目录,结果输出“Filename is regular file”或 “Filename is directory”
-
- 判断一下,执行者的身份对这个文件或目录所拥有的权限,并输出权限数据!
-
vim file_perm.sh
#!/bin/bash
# Program:
# User input a filename, program will check the flowing:
# 1.) exist?
# 2.) file/directory?
# 3.) file permissions
# History:
# 2015/07/16 VBird 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\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="regular 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}"
由于 root 在很多权限的限制上面都 是无效的,所以使用 root 执行这个脚本时, 常常会发现与 ls -l 观察到的结果并不相同
12.3.2 利用判断符号 [ ]
注意:
-
在中括号 [] 内的每个元件都需要有空白键来分隔;
-
在中括号内的变量,最好都以双引号括号起来;
-
在中括号内的常数,最好都以单或双引号括号起来
中括号的使用方法与 test 几乎一模一样啊~ 只是中括号比较常用 在条件判断式 if ..... then ..... fi 的情况中就是了
案例设置如下:
-
当执行一个程序的时候,这个程序会让使用者选择 Y 或 N ,
-
如果使用者输入 Y 或 y 时,就显示“ OK, continue ”
-
如果使用者输入 n 或 N 时,就显示“ Oh, interrupt !”
-
如果不是 Y/y/N/n 之内的其他字符,就显示“ I don't know what your choice is ”
vim ans_yn.sh
#!/bin/bash
# Program:
# This program shows the user's choice # History:
# 2015/07/16 VBird 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
12.3.3 Shell script 的默认变量($0
, $1
...)
script 针对参数已经有设置好一些变量名称了
/path/to/scriptname opt1 opt2 opt3 opt4
$0 $1 $2 $3 $4
还有一些较为特殊的变量可以在 script 内使用来调用
-
$#
:代表后接的参数“个数”,以上表为例这里显示为“ 4 ”; -
$@
:代表“ "$1
" "$2
" "$3
" "$4
" ”之意,每个变量是独立的(用双引号括起来); -
$*
:代表“ "$1<u>c</u>$2<u>c</u>$3<u>c</u>$4
" ”,其中<u>c</u>
为分隔字符,默 认为空白键, 所以本例中代表“"$1 $2 $3 $4"
”之意
假设我要执行一个可以携带参数的 script ,执行该脚本后屏幕会显示如下的数据:
-
程序的文件名为何?
-
共有几个参数?
-
若参数的个数小于 2 则告知使用者参数数量太少
-
全部的参数内容为何?
-
第一个参数为何?
-
第二个参数为何
vim how_paras.sh
sh how_paras.sh opt1 opt2 opt3 opt4
#!/bin/bash
# Program:
# Program shows the script name, parameters...
# History: # 2015/07/16 VBird 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 "Your whole parameter is \$* ==> $*"
echo "The 1st parameter \${1}==> ${1}"
echo "The 2nd parameter \${2}==> ${2}"
shift:造成参数变量号码偏移
shift 会移动变量,而且 shift 后面可以接数字,代表拿掉最前 面的几个参数的意思
vim shift_paras.sh
sh shift_paras.sh one two three four five six
#!/bin/bash
# Program:
# Program shows the effect of shift function.
# History:
# 2009/02/17 VBird 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 \$@ ==> '$@'"
执行成果如下:
Total parameter number is $# ==> 6
Your whole parameter is $@ ==> one two three four five six
Total parameter number is $# ==> 5
Your whole parameter is $@ ==> 'two three four five six'
Total parameter number is $# ==> 2
Your whole parameter is $@ ==> 'five six'
12.4 条件判断式
12.4.1 利用 if .... then
单层、简单条件判断式
if [ 条件判断式 ]; then
当条件判断式成立时,可以进行的指令工作内容;
fi <==将 if 反过来写,就成为 fi 啦!结束 if 之意!
除了将多个条件写入一个中括号内的情况”之 外, 我还可以有多个中括号来隔开喔!而括号与括号之间,则以 &&
或 ||
来隔开
-
&&
代表 AND ; -
||
代表 or ;
[ "${yn}" == "Y" -o "${yn}" == "y" ]
可替换为 [ "${yn}" == "Y" ] || [ "${yn}" == "y" ]
将 ans_yn.sh 这个脚本修改成为 if ... then 的样式:
vim ans_yn-2.sh
#!/bin/bash
# Program:
# This program shows the user's choice
# History:
# 2015/07/16 VBird 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
多重、复杂条件判断式
# 一个条件判断,分成功进行与失败进行 (else)
if [ 条件判断式 ]; then
当条件判断式成立时,可以进行的指令工作内容;
else
当条件判断式不成立时,可以进行的指令工作内容;
fi
# 多个条件判断 (if ... elif ... elif ... else) 分多种不同情况执行
if [ 条件判断式一 ]; then
当条件判断式一成立时,可以进行的指令工作内容;
elif [ 条件判断式二 ]; then
当条件判断式二成立时,可以进行的指令工作内容;
else
当条件判断式一与二均不成立时,可以进行的指令工作内容;
fi
改写之前的例子:
vim ans_yn-3.sh
#!/bin/bash
# Program:
# This program shows the user's choice
# History:
# 2015/07/16 VBird 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
现在我们想让使用者输入“ hello ”这个关键字时,利用参数的方法可以这样依序设计:
-
判断 $1 是否为 hello,如果是的话,就显示 "Hello, how are you ?";
-
如果没有加任何参数,就提示使用者必须要使用的参数下达法;
-
而如果加入的参数不是 hello ,就提醒使用者仅能使用 hello 为参数。
vim hello-2.sh
#!/bin/bash
# Program:
# Check $1 is equal to "hello"
# History:
# 2015/07/16 VBird 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
netstat 指令可以查询到目前主机有打开的网络服务端口 (service ports)
重点是“Local Address (本地主机的IP与端口对应)”那个字段,他代表的是本机所启 动的网络服务! IP的部分说明的是该服务位于那个接口上,若为 127.0.0.1 则是仅针对本机 开放,若是 0.0.0.0 或 ::: 则代表对整个 Internet 开放
每个端口 (port) 都有其特定的网络服务,几个常见的 port 与相关网络服务的关系是:
-
80: WWW
-
22: ssh
-
21: ftp
-
25: mail
-
111: RPC(远端程序调用)
-
631: CUPS(打印服务功能)
假设我的主机有兴趣要侦测的是比较常见的 port 21, 22, 25及 80 时,那我如何通过 netstat 去侦测我的主机是否有打开这四个主要的网络服务端口呢?
vim netstat.sh
#!/bin/bash
# Program:
# Using netstat and grep to detect WWW,SSH,FTP and Mail services.
# History:
# 2015/07/16 VBird First release
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
testing=$(grep ":8080 " ${testfile}) # 侦测看 port 8080 在否?
if [ "${testing}" != "" ]; then
echo "Tomcat is running in your system."
fi
让使用者输入他的退伍日期,让你去帮他计算还有几天才退伍?
由于日期是要用相减的方式来处置,所以我们可以通过使用 date 显示日期与时间,将他转为由 1970-01-01 累积而来的秒数,通过秒数相减来取得剩余的秒数后,再换算为日数即可。
整个脚本的制作流程有点像这样:
-
先让使用者输入他们的退伍日期;
-
再由现在日期比对退伍日期;
-
由两个日期的比较来显示“还需要几天”才能够退伍的字样。
vim cal_retired.sh
#!/bin/bash
# Program:
# You input your demobilization date, I calculate how many days before you demobilize.
# History:
# 2015/07/16 VBird First release
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
12.4.2 利用 case ..... esac 判断
case $变量名称 in <==关键字为 case ,还有变量前有钱字号
"第一个变量内容") <==每个变量内容建议用双引号括起来,关键字则为小括号 )
程序段
;; <==每个类别结尾使用两个连续的分号来处理!
"第二个变量内容")
程序段
;;
*) <==最后一个变量内容都会用 * 来代表所有其他值,
不包含第一个变量内容与第二个变量内容的其他程序执行段
exit 1
;;
esac <==最终的 case 结尾!“反过来写”
vim hello-3.sh
#!/bin/bash
# Program:
# Show "Hello" from $1.... by using case .... esac
# History:
# 2015/07/16 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
case ${1} in
"hello")
echo "Hello, how are you ?"
;;
"")
echo "You MUST input parameters, ex> {${0} someword}"
;;
*) # 其实就相当于万用字符,0~无穷多个任意字符之意!
echo "Usage ${0} {hello}"
;;
esac
一般来说,使用“ case $变量 in
”这个语法中,当中的那个“ $变量
”大致有两种取得的方式:
-
直接下达式:例如上面提到的,利用“ script.sh variable ” 的方式来直接给予 $1 这个变量的内容,这也是在 /etc/init.d 目录下大多数程序的设计方式。
-
互动式:通过 read 这个指令来让使用者输入变量的内容。
vim show123.sh
#!/bin/bash
# Program:
# This script only accepts the flowing parameter: one, two or three.
# History:
# 2015/07/17 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
echo "This program will print your selection !"
# read -p "Input your choice: " choice # 暂时取消,可以替换!
# case ${choice} in # 暂时取消,可以替换!
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
现在是直接下达式,通过将注释放开,可以改成互动式
12.4.3 利用 function 功能
“函数 (function)可以在 shell script 当中做出一个 类似自订执行指令的东西,最大的功能是, 可以简化我们很多的程序码
function fname() {
程序段
}
vim show123-2.sh
#!/bin/bash
# Program:
# Use function to repeat information.
# History:
# 2015/07/17 VBird First release
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 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 也是拥有内置变量的~他的内置变量与 shell script 很类似, 函数名称代表示 $0
,而后续接的变量也是以 $1
, $2
... 来取代的~ 这里很容易搞错喔~因为“ function fname() { 程序段 } ”内的 $0
, $1
... 等等与 shell script 的 $0
是不同的
vim show123-3.sh
#!/bin/bash
# Program:
# Use function to repeat information.
# History:
# 2015/07/17 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
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
12.5 循环 (loop)
12.5.1 while do done, until do done (不定循环)
不定循环最常见的就是下面这两种状态了:
while [ condition ] <==中括号内的状态就是判断式
do <==do 是循环的开始!
程序段落
done <==done 是循环的结束
until [ condition ]
do
程序段落
done
假设我要让使 用者输入 yes 或者是 YES 才结束程序的执行,否则就一直进行告知使用者输入字串
vim yes_to_stop.sh
#!/bin/bash
# Program:
# Repeat question until user input correct answer.
# History:
# 2015/07/17 VBird First release
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."
vim yes_to_stop-2.sh
#!/bin/bash
# Program:
# Repeat question until user input correct answer.
# History:
# 2015/07/17 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
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+2+3+....+100 这个数据 呢?
vim cal_1_100.sh
#!/bin/bash
# Program:
# Use loop to calculate "1+2+3+...+100" result.
# History:
# 2015/07/17 VBird First release
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"
12.5.2 for...do...done (固定循环)
for var in con1 con2 con3 ...
do
程序段
done
假设我有三种动物,分别是 dog, cat, elephant 三种, 我想每一行 都输出这样:“There are dogs...”之类的字样
vim show_animal.sh
#!/bin/bash
# Program:
# Using for .... loop to print 3 animals
# History:
# 2015/07/17 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
for animal in dog cat elephant
do
echo "There are ${animal}s.... "
done
由于系统上面 的各种帐号都是写在 /etc/passwd 内的第一个字段,你能不能通过管线命令的 cut 捉出单纯的 帐号名称后,以 id 分别检查使用者的识别码与特殊参数呢?
vim userid.sh
#!/bin/bash
# Program
# Use id, finger command to check system account's information.
# History
# 2015/07/17 VBird first release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
users=$(cut -d ':' -f1 /etc/passwd) # 撷取帐号名称
for username in ${users} # 开始循环进行!
do
id ${username}
done
我想要利用 ping 这个可以判断网络状态的指令, 来进行网络状态的实际侦测时,我想要侦测 的网域是本机所在的 192.168.1.1~192.168.1.100
vim pingip.sh
#!/bin/bash
# Program
# Use ping command to check the network's PC state.
# History
# 2015/07/17 VBird first release
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) # 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
除了使用 sitenu in $
之外,可以使 用 {1..100}
来取代
大括号内的前面/后面用两个字符,中间以两个小 数点来代表连续出现的意思!例如要持续输出 a, b, c...g 的话, 就可以使用“ echo {a..g}
”这 样的表示方式!
我想要让使用者输入某个目录文件名, 然后我找 出某目录内的文件名的权限
vim dir_perm.sh
#!/bin/bash
# Program:
# User input dir name, I find the permission of files.
# History:
# 2015/07/17 VBird First release
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
12.5.3 for...do...done 的数值处理
for (( 初始值; 限制值; 执行步阶 ))
do
程序段
done
在 for 后面的括号内的三串内容意义为:
-
初始值:某个变量在循环当中的起始值,直接以类似 i=1 设置好;
-
限制值:当变量的值在这个限制值的范围内,就继续进行循环。例如 i<=100;
-
执行步阶:每作一次循环时,变量的变化量。例如 i=i+1。
vim cal_1_100-2.sh
#!/bin/bash
# Program:
# Try do calculate 1+2+....+${your_input}
# History:
# 2015/07/17 VBird First release
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=1; i<=${nu}; i=i+1 ))
do
s=$((${s}+${i}))
done
echo "The result of '1+2+3+...+${nu}' is ==> ${s}"
12.5.4 搭配乱数与阵列的实验
用脚本搭配乱数来告诉我们,今天中午吃啥好?
vim what_to_eat.sh
#!/bin/bash
# Program:
# Try do tell you what you may eat.
# History:
# 2015/07/17 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
eat[1]="卖当当漢堡包" # 写下你所收集到的店家!
eat[2]="肯爷爷炸鸡"
eat[3]="彩虹日式便当"
eat[4]="越油越好吃大雅"
eat[5]="想不出吃啥学餐"
eat[6]="太师父便当"
eat[7]="池上便当"
eat[8]="怀念火车便当"
eat[9]="一起吃方便面"
eatnum=9 # 需要输入有几个可用的餐厅数!
check=$(( ${RANDOM} * ${eatnum} / 32767 + 1 ))
echo "your may eat ${eat[${check}]}"
如果想要每次都秀出 3 个店家呢? 而且这个店家不能重复
vim what_to_eat-2.sh
#!/bin/bash
# Program:
# Try do tell you what you may eat.
# History:
# 2015/07/17 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
eat[1]="卖当当漢堡包"
eat[2]="肯爷爷炸鸡"
eat[3]="彩虹日式便当"
eat[4]="越油越好吃大雅"
eat[5]="想不出吃啥学餐"
eat[6]="太师父便当"
eat[7]="池上便当"
eat[8]="怀念火车便当"
eat[9]="一起吃方便面"
eatnum=9
eated=0
while [ "${eated}" -lt 3 ]; do
check=$(( ${RANDOM} * ${eatnum} / 32767 + 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 "your may eat ${eat[${check}]}"
eated=$(( ${eated} + 1 ))
eatedcon[${eated}]=${check}
fi
done
12.6 shell script 的追踪与 debug
sh [-nvx] scripts.sh
选项与参数:
-n :不要执行 script,仅查询语法的问题;
-v :再执行 sccript 前,先将 scripts 的内容输出到屏幕上;
-x :将使用到的 script 内容显示到屏幕上,这是很有用的参数!
# 范例一:测试 dir_perm.sh 有无语法的问题?
sh -n dir_perm.sh
# 范例二:将 show_animal.sh 的执行过程全部列出来~
sh -x show_animal.sh