龙之介大人

shell的进阶下
12.1 条件判断式只要讲到『程序』的话,那么条件判断式,亦即是『 if then 』这种判别式肯定一定要学习的!...
扫描右侧二维码阅读全文
30
2019/08

shell的进阶下

12.1 条件判断式

只要讲到『程序』的话,那么条件判断式,亦即是『 if then 』这种判别式肯定一定要学习的! 因为 很多时候,我们都必须要依据某些数据来判断程序该如何进行。举例来说,我们在上头的 ans_yn.sh 讨 论输入响应的范例中不是有练习当使用者输入 Y/N 时,必须要执行不同的讯息输出吗?简单的方式 可以利用 && 与 || ,但如果我还想要执行一堆指令呢?那真的得要 if then 来帮忙!

12.1.1 if .... then的使用

这个 if .... then 是最常见的条件判断式了~简单的说,就是当符合某个条件判断的时候, 就予以进 行某项工作就是了。这个 if ... then 的判断还有多层次的情况!我们分别介绍如下:

  • 单层、简单条件判断式

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

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

至于条件判断式的判断方法,与前一小节的介绍相同啊!
较特别的是,如果我有多个条件要判别时, 除了 ans_yn.sh 那个案例所写的,也就是『将多个条件写入一个中括号内的情况』之外, 我还可以有 多个中括号来隔开喔!而括号与括号之间,则以 && 或 || 来隔开,他们的意义是:

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

所以,在使用中括号的判断式中, && 及 || 就与指令下达的状态不同了。举例来说, ans_yn.sh 里 面的判断式可以这样修改:

[ "${yn}" == "Y" -o "${yn}" == "y"]
#可以写成
[ "${yn}" == "Y" ] || [ "${yn}" == "y"]

之所以这样改,很多人是习惯问题!很多人则是喜欢一个中括号仅有一个判别式的原因。好了,现在我们来将 ans_yn.sh 这个脚本修改成为 if ... then 的样式来看看:

[xiaoqi@study ~]$ 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,interrupt!"
    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

不过,由这个例子看起来,似乎也没有什么了不起吧?原本的 ans_yn.sh 还比较简单呢~ 但是如果 以逻辑概念来看,其实上面的范例中,我们使用了两个条件判断呢!明明仅有一个 ${yn} 的变量, 为何需要进行两次比对呢? 此时,多重条件判断就能够来测试测试

12.1.2 if .... else的使用

在同一个数据的判断中,如果该数据需要进行多种不同的判断时,应该怎么作?举例来说,上面的 ans_yn.sh 脚本中,我们只要进行一次 ${yn} 的判断就好 (仅进行一次 if ),不想要作多次 if 的判断。 此时你就得要知道底下的语法了:

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

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

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

你得要注意的是, elif 也是个判断式,因此出现 elif 后面都要接 then 来处理!但是 else 已经是最 后的没有成立的结果了, 所以 else 后面并没有 then

我们来将 ans_yn-2.sh 改写成这样:

[xiaoqi@study ~]$ 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,interrupt!"
elif [ "${yn}" == "N" ] || [ "${yn}" == "n" ];then
    echo "Oh,interropt!"
else
    echo "I don't know what your choice is"
fi

是否程序变得很简单,而且依序判断,可以避免掉重复判断的状况,这样真的很容易设计程序!

好了,让我们再来进行另外一个案例的设计。一般来说,如果你不希望用户由键盘输入额外的 数据时, 可以使用上一节提到的参数功能 ($1)!让用户在下达指令时就将参数带进去! 现在我们 想让用户输入『 hello 』这个关键词时,利用参数的方法可以这样依序设计:

  1. 判断 $1 是否为 hello,如果是的话,就显示 "Hello, how are you ?";
  2. 如果没有加任何参数,就提示使用者必须要使用的参数下达法;
  3. 而如果加入的参数不是 hello ,就提醒使用者仅能使用 hello 为参数。

实现代码:

[xiaoqi@study ~]$ 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, ex> {${0} someword}"
else 
    echo "The only parameter is 'hello', ex> {${0} hello}"
fi

然后你可以执行这支程序,分别在 $1 的位置输入 hello, 没有输入与随意输入, 就可以看到不同的 输出!

我们在第十章已经学会了 grep 这个好用的玩意儿,那么多学一个叫做 netstat 的指令,这个指令可以 查询到目前主机有开启的网络服务端口口 (service ports), 相关的功能我们会在服务器架设篇继续介 绍,这里你只要知道,我可以利用『 netstat -tuln 』来取得目前主机有启动的服务, 而且取得的信息 有点像这样:

[xiaoqi@study ~]$ netstat -lunt
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
tcp        0      0 192.168.122.1:53        0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.1:631           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:631                 :::*                    LISTEN     
tcp6       0      0 ::1:25                  :::*                    LISTEN     
udp        0      0 0.0.0.0:5996            0.0.0.0:*                          
udp        0      0 0.0.0.0:55150           0.0.0.0:*                          
udp        0      0 192.168.122.1:53        0.0.0.0:*                          
udp        0      0 0.0.0.0:67              0.0.0.0:*                          
udp        0      0 0.0.0.0:68              0.0.0.0:*                          
udp        0      0 0.0.0.0:48345           0.0.0.0:*                          
udp        0      0 0.0.0.0:5353            0.0.0.0:*                          
udp        0      0 127.0.0.1:323           0.0.0.0:*                          
udp6       0      0 :::5996                 :::*                               
udp6       0      0 :::48280                :::*                               
udp6       0      0 ::1:323                 :::*                               
udp6       0      0 fe80::20c:29ff:fe61:546 :::*     
#封包格式               本地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(打印服务功能)

假设我的主机有兴趣要侦测的是比较常见的 port 21, 22, 25 及 80 时,那我如何透过 netstat 去侦测 我的主机是否有开启这四个主要的网络服务端口口呢?由于每个服务的关键词都是接在冒号『 : 』 后面, 所以可以藉由撷取类似『 :80 』来侦测的!那我就可以简单的这样去写这个程序喔:

[xiaoqi@study ~]$ vim netstat.sh 
#!/bin/bash

PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin 
export PATH

echo "Now, I will detect your Linux server's services!"
echo -e "The www, ftp, ssh, and mail(smtp) will be detect! \n"

testfile=/dev/shm/netstat_checking.txt
netstat -tuln > ${testfile} # 先转存数据到内存当中!不用一直执行 netstat

testing=$(grep ":80 " ${testfile}) # 侦测看 port 80 在否?
if [ "${testing}" != ""  ]; then
    echo "WW is running in your system."
fi

testing=$(grep ":22 " ${testing}) # 侦测看 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

实际执行这个程序你就可以看到你的主机有没有启动这些服务!
/dev/shm/是linux下内存文件夹,不再硬盘中创建数据而是在内存里

12.1.3 case .... esac的使用

上个小节提到的『 if .... then .... fi 』对于变量的判断是以『比对』的方式来分辨的, 如果符合状态 就进行某些行为,并且透过较多层次 (就是 elif ...) 的方式来进行多个变量的程序代码编写!

如 hello-2.sh 那个小程序,就是用这样的方式来撰写的啰。 好,那么万一我有多个既定的变量内容, 例如 hello-2.sh 当中,我所需要的变量就是 "hello"及空字符串两个, 那么我只要针对这两个变量来设定状况就好了,那么可以使用什么方式来设计呢?就用 case ... in .... esac !

语法如下:

case $变量名称 in        <--键词为 case ,还有变数前有钱字号
    "第一个变量内容") <--每个变量内容建议用双引号括起来,关键词则为小括号 )
        程序段
    ;;                <--每个类别结尾使用两个连续的分号来处理!
    "第二个变量内容")
        程序段
    ;;
    *)                <--最后一个变量内容都会用 * 来代表所有其他值
        不包含第一个变量内容与第二个变量内容的其他程序执行段
        exit 1
    ;;
esac                <--最终的 case 结尾!『反过来写』思考一下!

要注意的是,这个语法以 case (实际案例之意) 为开头,结尾自然就是将 case 的英文反过来写!就 成为 esac !另外,每一个变量内容的程序段最后都需要两个分号 (;;) 来代表该 程序段落的结束,这挺重要的喔!至于为何需要有 * 这个变量内容在最后呢?这是因为,如果用户 不是输入变量内容一或二时, 我们可以告知用户相关的信息!

我们拿 hello-2.sh 的案 例来修改一下,他应该会变成这样喔:

[xiaoqi@study ~]$ cat hell-3.sh 
#!/bin/bash

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}"
        ;;
  *)
          echo "Usage ${0} {hello}"
        ;;
esac

在上面这个 hello-3.sh 的案例当中,如果你输入『 sh hello-3.sh test 』来执行, 那么屏幕上就会出 现『Usage hello-3.sh {hello}』的字样,告知执行者仅能够使用 hello !
这样的方式对于需要某些 固定字符串来执行的变量内容就显的更加的方便!这种方式真的要熟悉掌握!
这是因为早期系统 的很多服务的启动 scripts 都是使用这种写法的 (CentOS 6.x 以前)。 虽然 CentOS 7 已经使用 systemd,不过仍有数个服务是放在 /etc/init.d/ 目录下喔!例如有个名为 netconsole 的服务在该目录 下, 那么你想要重新启动该服务,是可以这样做的 (请注意,要成功执行,还是得要具有 root身份 才行!一般账号能执行,但不会成功!):

/etc/init.d/netconsole restart

重点是那个 restart 啦!如果你使用『 less /etc/init.d/netconsole 』去查阅一下,就会看到他使用的是 case 语法, 并且会规定某些既定的变量内容,你可以直接下达 /etc/init.d/netconsole , 该 script 就 会告知你有哪些后续接的变量可以使用!

一般来说,使用『 case $变量 in 』这个语法中,当中的那个『 $变量 』大致有两种取得的方式:

  • 直接下达式:例如上面提到的,利用『 script.sh variable 』 的方式来直接给予 $1 这个变量的内容,这也 是在 /etc/init.d 目录下大多数程序的设计方式。
  • 交互式:透过 read 这个指令来让用户输入变量的内容。
[xiaoqi@study ~]$ vim show123.sh
#!/bin/bash

PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin 
export PATH

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

此时,你可以使用『 sh show123.sh two 』的方式来下达指令,就可以收到相对应的响应了。 上面使用的是直接下达的方式,

12.2 function的使用

什么是『函数 (function)』功能啊?简单的说,其实, 函数可以在 shell script 当中做出一个类似自 定义执行指令的东西,最大的功能是,可以简化我们很多的程序代码~举例来说,上面的 show123.sh 当中,每个输入结果 one, two, three 其实输出的内容都一样啊~那么我就可以使用 function 来简化 了! function 的语法是这样的:

function fname(){
    程序段
}

那个 fname 就是我们的自定义的执行指令名称~而程序段就是我们要他执行的内容了。要注意的是, 因为 shell script 的执行方式是由上而下,由左而右, 因此在 shell script 当中的 function 的设定一 定要在程序的最前面, 这样才能够在执行时被找到可用的程序段 (这一点与传统程序语言差异相当大!)

我们将 show123.sh 改写一下,自定义一个名为 printit 的函 数来使用喔:

[xiaoqi@study ~]$ 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 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

以上面的例子来说,写了一个函数名称为 printit ,所以,在后续的程序段里面,只要执行 printit 的话,就表示我的 shell script 要去执行『 function printit .... 』里面的那几个程序段

另外, 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 无关。我们将上面的例子再次的改写一下:

[xiaoqi@study ~]$ cat show123-3.sh 
#!/bin/bash

PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

function printit(){
    echo "You 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 』的字样~ 为 什么是 1 呢?因为在程序段落当中,我们是写了『 printit 1 』那个 1 就会成为 function 当中的 $1 ~

12.3循环的使用

除了 if...then...fi 这种条件判断式之外,循环可能是程序当中最重要的一环了~ 循环可以不断的执行 某个程序段落,直到用户设定的条件达成为止。 所以,重点是那个『条件的达成』是什么。除了这种依据判断式达成与否的不定循环之外, 还有另外一种已经固定要跑多少次的循环形态,可称为固 定循环的形态呢!底下我们就来谈一谈:

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

一般来说,不定循环最常见的就是底下这两种状态了:

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

while 的中文是『当....时』,所以,这种方式说的是『当 condition 条件成立时,就进行循环,直到 condition 的条件不成立才停止』的意思。

还有另外一种不定循环的方式:

until [ condition ]
do
    程序段落
done

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

我们以 while 来做个简单的练习好了。 假设我要让使用者输入 yes 或者是 YES 才结束程序的执行,否则就一直进行告知用户输入字符串。

[xiaoqi@study ~]$ 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."

上面这个例题的说明是『当 ${yn} 这个变数不是 "yes" 且 ${yn} 也不是 "YES" 时,才进行循环内 的程序。』 而如果 ${yn} 是 "yes" 或 "YES" 时,就会离开循环.

那如果使用 until 呢?他的条件会变成这样:

[xiaoqi@study ~]$ cat 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" -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 这个数据呢? 利 用循环啊~他是这样的:

[xiaoqi@study ~]$ vim cal_1_100.sh
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

s=0 #加数
i=0 #累加数

while [ "${i}" != "100" ]
do
    i=$(($i+1)) 
    s=$(($s+$i)) 
done
echo " The result of '1=2=3=...+100' is --> $s"

当你执行了『 sh cal_1_100.sh 』之后,就可以得到 5050 这个数据

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

相对于 while, until 的循环方式是必须要『符合某个条件』的状态, for 这种语法,则是『 已经知道 要进行几次循环』的状态!他的语法是:

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

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

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

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

[xiaoqi@study ~]$ vim userid.sh 
#!/bin/bash

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 这个可以判断网络状态的指令,来进行网络状态的实际侦测时,我想要侦测的网域是本机所在的 10.10.1.150~10.10.1.255,由于有 105 台主机,总不会要我在 for 后面输入 1 到 100 吧?此时 你可以这样做喔!

[xiaoqi@study ~]$ cat pingip.sh 
#!/bin/bash

PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

network="10.10.1"     # 先定义一个网域的前面部分!
for sitenu in $(seq 150 255)    # 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

上面这一串指令执行之后就可以显示出 10.10.1.150~10.10.1.255 共 105 部主机目前是否能与你的机器连通!其实这个范例的重点在 $(seq ..) 那个位置!那个 seq 是连续 (sequence) 的缩写之意!代表后面接的两个数值是一直连续的! 如此一来,就能够轻松的将连续数字带入程序中!

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

最后,让我们来玩判断式加上循环的功能!我想要让用户输入某个目录文件名, 然后我找出某目录 内的文件名的权限,该如何是好?

[root@study xiaoqi]# 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 adirectory: " dir
if [ "${dir}" == "" -o ! -d "${dir}" ]; then
    echo "Then ${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 "Then file ${dir}/${filename}'s permission is ${perm} "
done

利用这种方式,可以很轻易的来处理一些文件的特性。

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

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

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

这种语法适合于数值方式的运算当中,在 for 后面的括号内的三串内容意义为:

  • 初始值:某个变量在循环当中的起始值,直接以类似 i=1 设定好;
  • 限制值:当变量的值在这个限制值的范围内,就继续进行循环。例如 i<=100;
  • 执行步阶:每作一次循环时,变量的变化量。例如 i=i+1。

值得注意的是,在『执行步阶』的设定上,如果每次增加 1 ,则可以使用类似『i++』的方式,亦即是 i 每次循环都会增加一的意思。好,我们以这种方式来进行 1 累加到使用者输入的循环吧

[root@study xiaoqi]# 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 anumber, I will count for 1+2+...+your_input: " nu

s=0
for (( i=1; i<=${nu}; i++ ))
do
    s=$((${s}+${i}))
done

echo "The result pf '1+2+3..+${nu}' is --> ${s}"

搭配随机数与数组的实验

让我们来做个小实验!假设你们公司的团队中,经 常为了今天中午要吃啥搞到头很昏! 每次都用猜拳的~好烦喔~有没有办法写支脚本,用脚本搭配随机数来告诉我们,今天中午吃啥好?执行这只脚本后, 直接跟你说要吃啥~

要达成这个任务,首先你得要将全部的菜品输入到一组数组当中,再透过随机数的处理,去取得可能的数值,再将搭配到该数值的菜品输出出来即可!

[root@study xiaoqi]# 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]="酸辣土豆丝"
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 个菜呢? 而且这个店家不能重复喔!重复当然就没啥意义了!所以,你可以这样作!

[root@study xiaoqi]# 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]="酸辣土豆丝"
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
                myckeck=1
            fi
        done
    fi
    if [ ${mycheck} == 0 ];then
        echo "your may cat ${eat[${check}]}"
        eated=$((${eated} + 1))
        eatedcon[${eated}]=${check}
    fi

done

12.4 shell script 的追踪与 debug

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

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


#范例一:测试 dir_perm.sh 有无语法的问题?
[root@study xiaoqi]# sh -n dir_perm.sh 
#若语法没有问题,则不会显示任何信息!
 
范例二:将 show_animal.sh 的执行过程全部列出来~
[root@study xiaoqi]# 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 的方式来将指令 执行过程也显示出来, 如此用户可以判断程序代码执行到哪一段时会出现相关的信息!

最后修改:2020 年 01 月 21 日 04 : 32 PM

发表评论