初识Linux(九)------ 学习Shell Scripts

1. 介绍

  基本上, shell script 有点像是早期的批处理文件,亦即是将一些指令汇整起来一次执行,但是 Shell script 拥有更强大的功能,那就是他可以进行类似程序 (program) 的编写,并且不需要经过编译 (compile) 就能够执行, 真的很方便。加上我们可通过 shell script 来简化我们日常的工作管理, 而且,整个 Linux 环境中,一些服务 (services) 的启动都是通过 shell script 的。

  shell script 是利用 shell 的功能所写的一个“程序 (program)”,这个程序是使用纯文本文件,将一些 shell 的语法与指令(含外部指令)写在里面, 搭配正则表达式、管道命令与数据流重导向等功能,以达到我们所想要的处理目的。

为什么要学习使用shell script ?

  • 自动化管理

  查询登录文件、追踪流量、监控使用者使用主机状态、主机各项硬件设备状态、 主机软件更新查询等等日常性事务。

  • 追踪与管理系统的重要工作

  在CentOS 6.x 以前的版本中,系统的服务 (services) 启动的接口是在 /etc/init.d/ 这个目录下,目录下的所有文件都是 scripts ; 另外,包括开机 (booting) 过程也都是利用 shell script 来帮忙查找系统的相关设置数据, 然后再代入各个服务的设置参数。举例来说,如果我们想要重新启动系统登录文件, 可以使用:“/etc/init.d/rsyslogd restart”,那个 rsyslogd 文件就是 script 。

  • 简单入侵监测功能

  当我们的系统有异状时,大多会将这些异状记录在系统记录器,也就是我们常提到的“系统登录文件”, 那么我们可以在固定的几分钟内主动的去分析系统登录文件,若察觉有问题,就立刻通报管理员, 或者是立刻加强防火墙的设置规则,

  • 连续指令单一化
  • 简单的数据处理
  • 跨平台支持

2. script 的编写与执行

  shell script 其实就是纯文本文件,我们可以编辑这个文件,然后让这个文件来帮我们一次执行多个指令, 或者是利用一些运算与逻辑判断来帮我们达成某些功能。所以,要编辑这个文件的内容时,当然就需要具备有 对bash 指令下达的相关认识,这个部分在之前的章节提到过。在 shell script 的撰写中还需要用到下面的注意事项:

  1. 指令的执行是从上而下、从左而右的分析与执行;
  2. 指令的下达就如同bash内提到的: 指令、选项与参数间的多个空白都会被忽略掉;
  3. 空白行也将被忽略掉,并且 [tab] 按键所推开的空白同样视为空白键;
  4. 如果读取到一个 Enter 符号 (CR) ,就尝试开始执行该行 (或该串) 命令;
  5. 至于如果一行的内容太多,则可以使用“ \[Enter] ”来延伸至下一行;
  6. “ # ”可做为注解!任何加在 # 后面的数据将全部被视为注解文字而被忽略。

  假设你写的这个程序文件名是 /home/dmtsai/shell.sh ,那如何执行这个文件?很简单,可以有下面几个方法:

  1. 直接指令下达: shell.sh 文件必须要具备可读与可执行 (rx) 的权限,然后:
    • 绝对路径:使用 /home/dmtsai/shell.sh 来下达指令;
    • 相对路径:假设工作目录在 /home/dmtsai/ ,则使用 ./shell.sh 来执行
    • 变量“PATH”功能:将 shell.sh 放在 PATH 指定的目录内,例如: ~/bin/
  2. 以 bash 程序来执行:通过“ bash shell.sh ”或“ sh shell.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 的权限即可被执行喔!而我们也可以利用 sh 的参数,如 -n 及 -x 来检查与追踪 shell.sh 的语法是否正确。


范例:编写第一个 script

[dmtsai@study ~]$ mkdir bin; cd bin
[dmtsai@study 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
  1. 第一行 #!/bin/bash 在宣告这个 script 使用的 shell 名称:
    因为我们使用的是 bash ,所以,必须要以“ #!/bin/bash ”来宣告这个文件内的语法使用 bash 的语法!那么当这个程序被执行时,他就能够载入 bash 的相关环境配置文件 (一般来说就是 non-login shell 的 ~/.bashrc), 并且执行 bash 来使我们下面的指令能够执行!这很重要的!(在很多情况中,如果没有设置好这一行, 那么该程序很可能会无法执行,因为系统可能无法判断该程序需要使用什么 shell 来执行!)
  2. 程序内容的说明:
    整个 script 当中,除了第一行的“ #! ”是用来宣告 shell 的之外,其他的 # 都是“注解”用途! 所以上面的程序当中,第二行以下就是用来说明整个程序的基本数据。一般来说, 建议你一定要养成说明该 script 的:1. 内容与功能; 2. 版本信息; 3. 作者与联络方式; 4. 创建日期;5. 历史纪录 等等。这将有助于未来程序的改写与 debug !
  3. 主要环境变量的宣告:
    建议务必要将一些重要的环境变量设置好,PATH 与 LANG (如果有使用到输出相关的信息时) 是当中最重要的,如此一来,则可让我们这支程序在进行时,可以直接下达一些外部指令,而不必写绝对路径,比较方便。
  4. 主要程序部分
    在这个例子当中,就是 echo 那一行。
  5. 运行结果告知 (定义返回值)
    之前的章节说过,一个指令的执行成功与否,可以使用 $? 这个变量来观察~ 那么我们也可以利用 exit 这个指令来让程序中断,并且返回一个数值给系统。 在我们这个例子当中,使用 exit 0 ,这代表离开 script 并且返回一个 0 给系统, 所以我执行完这个 script 后,若接着下达 echo $? 则可得到 0 的值。 利用这个 exit n (n 是数字) 的功能,我们还可以自定义错误信息, 让这支程序变得更加的 smart 。

范例运行结果:

[dmtsai@study bin]$ sh hello.sh
Hello World !

2.1 简单范例

范例一:对话式脚本,变量内容由使用者决定

  用 read 指令,编写一个 script ,他可以让使用者输入:1. first name 与 2. last name, 最后并且在屏幕上显示:“Your full name is: ”的内容:

[dmtsai@study bin]$ 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 进行文件的创建

  创建三个空的文件 (通过 touch) ,文件名最开头由使用者输入决定,假设使用者输入 filename 好了,那今天的日期是 2015/07/16 , 我想要以前天、昨天、今天的日期来创建这些文件,亦即 filename_20150714, filename_20150715, filename_20150716 。

[dmtsai@study bin]$ 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 ,利用变量功能分析文件名是否有设置?
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}"

范例三:数值运算:简单的加减乘除

  使用者输入两个变量, 然后将两个变量的内容相乘,最后输出相乘的结果。

[dmtsai@study bin]$ 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} ”


 范例四:数值运算:通过 bc 计算 pi

   计算 pi 时,小数点以下位数可以无限制的延伸下去!而 bc 有提供一个运算 pi 的函数,只是想要使用该函数必须要使用 bc -l 来调用才行。 也因为这个小数点以下位数可以无限延伸运算的特性存在,所以我们可以通过下面的脚本来让使用者输入一个“保留小数点几位数”, 以让 pi 能够更准确.

[dmtsai@study bin]$ 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

 2.2  script 的执行方式差异 (source, sh script, ./script)

   不同的 script 执行方式会造成不一样的结果,尤其影响 bash 的环境。脚本的执行方式除了前面小节谈到的方式之外,还可以利用 source 或小数点 (.) 来执行,那么这种执行方式有何不同呢?

 利用直接执行的方式来执行 script

  当使用前一小节提到的直接指令下达 (无论是绝对路径/相对路径还是 ${PATH} 内),或者是利用 bash (或 sh) 来下达脚本时, 该 script 都会使用一个新的 bash 环境来执行脚本内的指令。也就是说,使用这种执行方式时, 其实 script 是在子程序的 bash 内执行的!我们在之前的章节 BASH 内谈到 export 的功能时,曾经就父程序/子程序谈过一些概念性的问题, 重点在于:“当子程序完成后,在子程序内的各项变量或动作将会结束而不会传回到父程序中”! 这是什么意思呢?

  我们举刚刚提到过的 showname.sh 这个脚本来说明好了,这个脚本可以让使用者自行设置两个变量,分别是 firstname 与 lastname,想一想,如果你直接执行该指令时,该指令帮你设置的 firstname 会不会生效?看一下下面的执行结果:

[dmtsai@study bin]$ echo ${firstname} ${lastname}
    <==确认了,这两个变量并不存在喔!
[dmtsai@study bin]$ sh showname.sh
Please input your first name: VBird <==这个名字是鸟哥自己输入的
Please input your last name:  Tsai 

Your full name is: VBird Tsai      <==看吧!在 script 运行中,这两个变量有生效
[dmtsai@study bin]$ echo ${firstname} ${lastname}
    <==事实上,这两个变量在父程序的 bash 中还是不存在的!

  我们以下图来说明。当你使用直接执行的方法来处理时,系统会给予一支新的 bash 让我们来执行 showname.sh 里面的指令,因此你的 firstname, lastname 等变量其实是在下图中的子程序 bash 内执行的。 当 showname.sh 执行完毕后,子程序 bash 内的所有数据便被移除。

showname.sh 在子程序当中运行的示意图


利用 source 来执行脚本:在父程序中执行

  如果你使用 source 来执行指令那就不一样了,同样的脚本我们来执行:

[dmtsai@study bin]$ source showname.sh
Please input your first name: VBird
Please input your last name:  Tsai

Your full name is: VBird Tsai
[dmtsai@study bin]$ echo ${firstname} ${lastname}
VBird Tsai  <==嘿嘿!有数据产生喔!

  source 对 script 的执行方式可以使用下面的图示来说明。 showname.sh 会在父程序中执行的,因此各项动作都会在原本的 bash 内生效!这也是为啥你不登出系统而要让某些写入 ~/.bashrc 的设置生效时,需要使用“ source ~/.bashrc ”而不能使用“ bash ~/.bashrc ”是一样的。

3. 善用判断式

  之前,我们提到过 $? 这个变量所代表的意义, 此外,也通过 && 及 || 来作为前一个指令执行回传值对于后一个指令是否要进行的依据。在前面章节的讨论中,如果想要判断一个目录是否存在, 当时我们使用的是 ls 这个指令搭配数据流重导向,最后配合 $? 来决定后续的指令进行与否。 但是否有更简单的方式可以来进行“条件判断”呢?有的~那就是“ test ”这个指令。

3.1 利用 test 指令的测试功能

  举例来说,要检查 /dmtsai 是否存在时,使用:

[dmtsai@study ~]$ test -e /dmtsai

  执行结果并不会显示任何讯息,但最后可以通过 $? 或 && 及 || 来展现整个结果,例如将上面的例子改写成这样:

[dmtsai@study ~]$ test -e /dmtsai && echo "exist" || echo "Not exist"
Not exist

 

测试的标志:

3.2 利用判断符号 [ ]

  除了 test 之外,其实,还可以利用判断符号“ [ ] ”(就是中括号) 来进行数据的判断。举例来说,如果我想要知道 ${HOME} 这个变量是否为空的,可以这样做:

[dmtsai@study ~]$ [ -z "${HOME}" ] ; echo $?

  使用中括号必须要特别注意,因为中括号用在很多地方,包括万用字符与正则表达式等等,所以如果要在 bash 的语法当中使用中括号作为 shell 的判断式时,必须要注意中括号的两端需要有空格字符来分隔。假设空格键使用“□”符号来表示,那么,在这些地方你都需要有空格键:

[  "$HOME"  ==  "$MAIL"  ]
[□"$HOME"□==□"$MAIL"□]
 ↑       ↑  ↑       ↑
 #在上面的判断式当中使用了两个等号“ == ”。其实在 bash 当中使用一个等号与两个等号的结果是一样的! 
 #不过在一般程序的写法中,一个等号代表“变量的设置”,两个等号则是代表“逻辑判断” 

最好要注意:

  • 在中括号 [] 内的每个元素都需要有空格键来分隔;
  • 在中括号内的变量,最好都以双引号括号起来;
  • 在中括号内的常数,最好都以单或双引号括号起来。

 为什么要这么麻烦?举例来说,假如我设置了 name="VBird Tsai" ,然后这样判定:

[dmtsai@study ~]$ name="VBird Tsai"
[dmtsai@study ~]$ [ ${name} == "VBird" ]
bash: [: too many arguments

   发生错误,错误是由于“太多参数 (arguments)”所致!这是因为 ${name} 如果没有使用双引号括起来,那么上面的判定式会变成:

 [ VBird Tsai == "VBird" ]

   因为一个判断式仅能有两个数据的比对,上面 VBird 与 Tsai 还有 "VBird" 就有三个数据了。

  另外,中括号的使用方法与 test 几乎一模一样~ 只是中括号比较常用在条件判断式 if ..... then ..... fi 的情况中。

范例:

  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 ”。
[dmtsai@study bin]$ 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

 3.3 Shell script 的默认变量($0, $1...)

  指令可以带有选项与参数,例如 ls -la 可以查看包含隐藏文件的所有属性与权限。那么 shell script 能不能在脚本文件名后面带有参数呢?举例来说,如果你想要重新启动系统的网络,可以这样做:

[dmtsai@study ~]$ file /etc/init.d/network
/etc/init.d/network: Bourne-Again shell script, ASCII text executable
# 使用 file 来查询后,系统告知这个文件是个 bash 的可执行 script 

[dmtsai@study ~]$ /etc/init.d/network restart

  本章一开始是使用 read 的功能,但 read 功能的问题是你得要手动由键盘输入一些判断式。如果通过指令后面接参数, 那么一个指令就能够处理完毕而不需要手动再次输入一些变量行为。script 是怎么达成这个功能的呢?其实 script 针对参数已经有设置好一些变量名称了,对应如下:

/path/to/scriptname  opt1  opt2  opt3  opt4 
       $0             $1    $2    $3    $4

  执行的脚本文件名为 $0 这个变量,第一个接的参数就是 $1,除了这些数字的变量之外, 还有一些较为特殊的变量可以在 script 内使用来调用这些参数。

  • $# :代表后接的参数“个数”,以上表为例这里显示为“ 4 ”;
  • $@ :代表“ "$1" "$2" "$3" "$4" ”之意,每个变量是独立的(用双引号括起来);
  • $* :代表“ "$1c$2c$3c$4" ”,其中 c 为分隔字符,默认为空白键, 所以本例中代表“ "$1 $2 $3 $4" ”之意。

  一般使用情况下可以直接记忆 $@ 即可。

范例:

假设要执行一个可以携带参数的 script ,执行该脚本后屏幕会显示如下的数据:

  • 程序的文件名为何?
  • 共有几个参数?
  • 若参数的个数小于 2 则告知使用者参数数量太少
  • 全部的参数内容为何?
  • 第一个参数为何?
  • 第二个参数为何
[dmtsai@study bin]$ vim how_paras.sh
#!/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 "The 1st parameter         ==> ${1}"
echo "The 2nd parameter         ==> ${2}"

执行结果如下:

[dmtsai@study bin]$ sh how_paras.sh theone haha quot
The script name is        ==> how_paras.sh       <==文件名
Total parameter number is ==> 3                  <==果然有三个参数
Your whole parameter is   ==> 'theone haha quot' <==参数的内容全部
The 1st parameter         ==> theone             <==第一个参数
The 2nd parameter         ==> haha               <==第二个参数

shift:造成参数变量号码偏移

  除此之外,脚本后面所接的变量是否能够进行偏移 (shift) 呢?什么是偏移?我们直接以下面的范例来说明。我们将 how_paras.sh 的内容稍作变化一下,用来显示每次偏移后参数的变化情况:


[dmtsai@study bin]$ vim shift_paras.sh
#!/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   ==> '$@'"

  这玩意的执行成果如下:

[dmtsai@study bin]$ sh shift_paras.sh one two three four five six <==给予六个参数
Total parameter number is ==> 6   <==最原始的参数变量情况
Your whole parameter is   ==> 'one two three four five six'
Total parameter number is ==> 5   <==第一次偏移,看下面发现第一个 one 不见了
Your whole parameter is   ==> 'two three four five six'
Total parameter number is ==> 2   <==第二次偏移掉三个,two three four 不见了
Your whole parameter is   ==> 'five six'

  光看结果你就可以知道啦,那个 shift 会移动变量,而且 shift 后面可以接数字,代表拿掉最前面的几个参数的意思。

3.4 条件判断式

3.4.1 利用 if .... then

单层、简单条件判断式

  如果你只有一个判断式要进行,那么可以简单的这样看:

if [ 条件判断式 ]; then
	当条件判断式成立时,可以进行的指令工作内容;
fi   <==将 if 反过来写,就成为 fi 啦!结束 if 之意!

  至于条件判断式的判断方法,除了将多个条件写入一个中括号内的情况之外, 还可以用多个中括号来隔开。而括号与括号之间,则以 && 或 || 来隔开,他们的意义是:

  • && 代表 AND ;
  • || 代表 or ;

举例来说:

[ "${yn}" == "Y" -o "${yn}" == "y" ]
上式可替换为
[ "${yn}" == "Y" ] || [ "${yn}" == "y" ]


多重、复杂条件判断式

# 一个条件判断,分成功进行与失败进行 (else)
if [ 条件判断式 ]; then
	当条件判断式成立时,可以进行的指令工作内容;
else
	当条件判断式不成立时,可以进行的指令工作内容;
fi

  如果考虑更复杂的情况,则可以使用这个语法:

# 多个条件判断 (if ... elif ... elif ... else) 分多种不同情况执行
if [ 条件判断式一 ]; then
	当条件判断式一成立时,可以进行的指令工作内容;
elif [ 条件判断式二 ]; then
	当条件判断式二成立时,可以进行的指令工作内容;
else
	当条件判断式一与二均不成立时,可以进行的指令工作内容;
fi

  要注意的是, elif 也是个判断式,因此出现 elif 后面都要接 then 来处理。

范例:

使用者输入“ hello ”这个关键字时,利用参数的方法可以这样依序设计:

  • 判断 $1 是否为 hello,如果是的话,就显示 "Hello, how are you ?";
  • 如果没有加任何参数,就提示使用者必须要使用参数;
  • 而如果加入的参数不是 hello ,就提醒使用者仅能使用 hello 为参数。
[dmtsai@study bin]$ 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),可以利用“ netstat -tuln ”来取得目前主机有启动的服务, 而且取得的信息有点像这样:

[dmtsai@study ~]$ netstat -tuln
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN
tcp        0      0 127.0.0.1:25            0.0.0.0:*               LISTEN
tcp6       0      0 :::22                   :::*                    LISTEN
tcp6       0      0 ::1:25                  :::*                    LISTEN
udp        0      0 0.0.0.0:123             0.0.0.0:*
udp        0      0 0.0.0.0:5353            0.0.0.0:*
udp        0      0 0.0.0.0:44326           0.0.0.0:*
udp        0      0 127.0.0.1:323           0.0.0.0:*
udp6       0      0 :::123                  :::*
udp6       0      0 ::1:323                 :::*
#封包格式           本地IP:端口             远端IP:端口             是否监听

   上面的重点是“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(打印服务功能)

   假设要用 netstat 去监测主机是否有打开这四个主要的网络服务端口,可以这样做:

[dmtsai@study bin]$ 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

 3.4.2  利用 case ..in... esac 判断

case  $变量名称 in   <==关键字为 case ,还有变量前有“$”字符
  "第一个变量内容")   <==每个变量内容建议用双引号括起来,关键字则为小括号 )
	程序段
	;;            <==每个类别结尾使用两个连续的分号来处理!
  "第二个变量内容")
	程序段
	;;
  *)                  <==最后一个变量内容都会用 * 来代表所有其他值
	不包含第一个变量内容与第二个变量内容的其他程序执行段
	exit 1
	;;
esac                  <==最终的 case 结尾!“反过来写”思考一下!
[dmtsai@study bin]$ 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

 3.4.3 利用 function 功能

 function 的语法是这样的:

function fname() {
	程序段
}

  fname 就是我们的自定义的执行指令名称~而程序段就是我们要他执行的内容了。 要注意的是,因为 shell script 的执行方式是由上而下,由左而右, 因此在 shell script 当中的 function 的设置一定要在程序的最前面, 这样才能够在执行时被找到可用的程序段。

范例:

[dmtsai@study bin]$ 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 是不同的。以上面 show123-2.sh 来说,假如我下达:“ sh show123-2.sh one ” 这表示在 shell script 内的 $1 为 "one" 这个字串。但是在 printit() 内的 $1 则与这个 one 无关。

将上面的例子再次的改写一下:

[dmtsai@study bin]$ 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

  在上面的例子当中,如果你输入“ sh show123-3.sh one ”就会出现“ Your choice is 1 ”。

4. 循环 (loop)

  除了 if...then...fi 这种条件判断式之外,循环可能是程序当中最重要的一环了~ 循环可以不断的执行某个程序段落,直到使用者设置的条件达成为止。

4.1 while do done, until do done (不定循环)

while [ condition ]  <==中括号内的状态就是判断式
do            <==do 是循环的开始!
	程序段落
done          <==done 是循环的结束

  当 condition 条件成立时,就进行循环,直到 condition 的条件不成立才停止的意思。还有另外一种不定循环的方式:

until [ condition ]
do
	程序段落
done

  这种方式恰恰与 while 相反,它说的是当 condition 条件成立时,就终止循环, 否则就持续进行循环的程序段。

范例:

假设我要让使用者输入 yes 或者是 YES 才结束程序的执行,否则就一直进行告知使用者输入字串。

[dmtsai@study bin]$ 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."

  那如果使用 until 呢?

[dmtsai@study bin]$ 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."

4.2 for...do...done (固定循环)

for var in con1 con2 con3 ...
do
	程序段
done

以上面的例子来说,这个 $var 的变量内容在循环工作时:

  1. 第一次循环时, $var 的内容为 con1 ;
  2. 第二次循环时, $var 的内容为 con2 ;
  3. 第三次循环时, $var 的内容为 con3 ;
  4. ....

  我们可以做个简单的练习。假设我有三种动物,分别是 dog, cat, elephant 三种, 我想每一行都输出这样:“There are dogs...”之类的字样,则可以:

[dmtsai@study bin]$ 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 分别检查使用者的识别码与特殊参数呢?由于不同的 Linux 系统上面的帐号都不一样。此时实际去捉 /etc/passwd 并使用循环处理,就是一个可行的方案了。程序可以如下:

[dmtsai@study bin]$ 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,由于有 100 台主机, 总不会要我在 for 后面输入 1 到 100 吧?此时你可以这样做:

[dmtsai@study bin]$ 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

     那个 seq 是连续 (sequence) 的缩写之意!代表后面接的两个数值是一直连续的。

  除了使用 $(seq 1 100) 之外,你也可以直接使用 bash 的内置机制来处理。可以使用 {1..100} 来取代 $(seq 1 100) 。那个大括号内的前面/后面用两个字符,中间以两个小数点来代表连续出现的意思。例如要持续输出 a, b, c...g 的话, 就可以使用“ echo {a..g} ”这样的表示方式。

 范例:

  使用者输入某个目录文件名, 然后找出某目录内的文件名的权限。

[dmtsai@study bin]$ 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

4.3 for...do...done 的数值处理

  除了上述的方法之外,for 循环还有另外一种写法,语法如下:

for (( 初始值; 限制值; 执行步数 ))
do
	程序段
done

 范例:


[dmtsai@study bin]$ 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}"

4.4 随机数与数组

 范例:

  随机餐厅

[dmtsai@study bin]$ 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}]}"

 5. shell script 的追踪与 debug

   scripts 在执行之前,最怕的就是出现语法错误的问题了。那么我们如何 debug 呢?有没有办法不需要通过直接执行该 scripts 就可以来判断是否有问题呢?当然是有的,我们就直接以 bash 的相关参数来进行判断。

[dmtsai@study ~]$ sh [-nvx] scripts.sh
选项与参数:
-n  :不要执行 script,仅查询语法的问题;
-v  :再执行 sccript 前,先将 scripts 的内容输出到屏幕上;
-x  :将使用到的 script 内容显示到屏幕上,这是很有用的参数!

 

范例一:测试 dir_perm.sh 有无语法的问题?
[dmtsai@study ~]$ sh -n dir_perm.sh 
# 若语法没有问题,则不会显示任何信息!

范例二:将 show_animal.sh 的执行过程全部列出来~
[dmtsai@study ~]$ sh -x show_animal.sh 
+ PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:/root/bin
+ export PATH
+ for animal in dog cat elephant
+ echo 'There are dogs.... '
There are dogs....
+ for animal in dog cat elephant
+ echo 'There are cats.... '
There are cats....
+ for animal in dog cat elephant
+ echo 'There are elephants.... '
There are elephants....

  在输出的信息中,在加号后面的数据其实都是指令串,由于 sh -x 的方式来将指令执行过程也显示出来, 如此使用者可以判断程序码执行到哪一段时会出现相关的信息。通过显示完整的指令串, 你就能够依据输出的错误信息来修改你的脚本了。

 

posted @ 2022-11-15 11:47  莫莫君不恋爱  阅读(245)  评论(0编辑  收藏  举报