12.2 简单的 shell script 练习

在第一支 shell script 撰写完毕之后,相信你应该具有基本的撰写功力了。接下来,在开始更深入的程序概念之前,我们先来玩一些简单的小范例好了。 下面的范例中,达成结果的方式相当的多,建议你先自行撰写看看,写完之后再与鸟哥写的内容比对, 这样才能更加深概念喔!好!不啰唆,我们就一个一个来玩吧!

12.2.1 简单范例

下面的范例在很多的脚本程序中都会用到,而下面的范例又都很简单!值得参考看看喔!

  • 对谈式脚本:变量内容由使用者决定

很多时候我们需要使用者输入一些内容,好让程序可以顺利运行。 简单的来说,大家应该都有安装过软件的经验,安装的时候,他不是会问你“要安装到那个目录去”吗? 那个让使用者输入数据的动作,就是让使用者输入变量内容啦。

你应该还记得在 第十章、认识与学习BASH 的时候,我们有学到一个 read 指令吧?现在,请你以 read 指令的用途,撰写一个 script ,他可以让使用者输入:1. first name 与 2. last name, 最后并且在屏幕上显示:“Your full name is: ”的内容:

[[email protected] 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}" # 结果由屏幕输出

将上面这个 showname.sh 执行一下,你就能够发现使用者自己输入的变量可以让程序所取用,并且将他显示到屏幕上! 接下来,如果想要制作一个每次执行都会依据不同的日期而变化结果的脚本呢?

  • 随日期变化:利用 date 进行文件的创建

想像一个状况,假设我的服务器内有数据库,数据库每天的数据都不太一样,因此当我备份时,希望将每天的数据都备份成不同的文件名, 这样才能够让旧的数据也能够保存下来不被覆盖。哇!不同文件名呢!这真困扰啊?难道要我每天去修改 script ?

不需要啊!考虑每天的“日期”并不相同,所以我可以将文件名取成类似: backup.2015-07-16.data , 不就可以每天一个不同文件名了吗?呵呵!确实如此。那个 2015-07-16 怎么来的?那就是重点啦!接下来出个相关的例子: 假设我想要创建三个空的文件 (通过 touch) ,文件名最开头由使用者输入决定,假设使用者输入 filename 好了,那今天的日期是 2015/07/16 , 我想要以前天、昨天、今天的日期来创建这些文件,亦即 filename_20150714, filename_20150715, filename_20150716 ,该如何是好?

[[email protected] 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 ,利用[变量功能](../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}"

上面的范例鸟哥使用了很多在 第十章、认识与学习BASH 介绍过的概念: 包括小指令 $(command) 的取得讯息、变量的设置功能、变量的累加以及利用 touch 指令辅助! 如果你开始执行这个 create_3_filename.sh 之后,你可以进行两次执行:一次直接按 [Enter] 来查阅文件名是啥? 一次可以输入一些字符,这样可以判断你的脚本是否设计正确喔!

  • 数值运算:简单的加减乘除

各位看官应该还记得,我们可以使用 declare 来定义变量的类型吧? 当变量定义成为整数后才能够进行加减运算啊!此外,我们也可以利用 $((计算式)) 来进行数值运算的。 可惜的是, bash shell 里头默认仅支持到整数的数据而已。OK!那我们来玩玩看,如果我们要使用者输入两个变量, 然后将两个变量的内容相乘,最后输出相乘的结果,那可以怎么做?

[[email protected] 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} 也可以使用上面的方式来进行!基本上,鸟哥比较建议使用这样的方式来进行运算:var=$((运算内容))

不但容易记忆,而且也比较方便的多,因为两个小括号内可以加上空白字符喔! 未来你可以使用这种方式来计算的呀!至于数值运算上的处理,则有:+, -, *, /, % 等等。 那个 % 是取余数啦~举例来说, 13 对 3 取余数,结果是 13=4* 3+1,所以余数是 1 啊!就是:

[[email protected] bin]$ echo $(( 13 % 3 ))
1

这样了解了吧?另外,如果你想要计算含有小数点的数据时,其实可以通过 bc 这个指令的协助喔! 例如可以这样做:

[[email protected] bin]$ echo "123.123*55.9" | bc
6882.575

了解了 bc 的妙用之后,来让我们测试一下如何计算 pi 这个东西呢?

  • 数值运算:通过 bc 计算 pi

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

[[email protected] 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

上述数据中,那个 4*a(1) 是 bc 主动提供的一个计算 pi 的函数,至于 scale 就是要 bc 计算几个小数点下位数的意思。当 scale 的数值越大, 代表 pi 要被计算的越精确,当然用掉的时间就会越多!因此,你可以尝试输入不同的数值看看!不过,最好是不要超过 5000 啦!因为会算很久! 如果你要让你的 CPU 随时保持在高负载,这个程序算下去你就会知道有多操 CPU 啰! ^_^

Tips 鸟哥的实验室中,为了要确认虚拟机的效率问题,所以很多时候需要保持虚拟机在忙碌的状态~鸟哥的学生就是丢这只程序进去系统跑! 但是将 scale 调高一些,那计算就得要花比较多时间~用以达到我们需要 CPU 忙碌的状态喔!

12.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 会不会生效?看一下下面的执行结果:

[[email protected] bin]$ echo ${firstname} ${lastname}
    <==确认了,这两个变量并不存在喔!
[[email protected] bin]$ sh showname.sh
Please input your first name: VBird <==这个名字是鸟哥自己输入的
Please input your last name:  Tsai 
Your full name is: VBird Tsai      <==看吧!在 script 运行中,这两个变量有生效
[[email protected] bin]$ echo ${firstname} ${lastname}
    <==事实上,这两个变量在父程序的 bash 中还是不存在的!

上面的结果你应该会觉得很奇怪,怎么我已经利用 showname.sh 设置好的变量竟然在 bash 环境下面无效!怎么回事呢? 如果将程序相关性绘制成图的话,我们以下图来说明。当你使用直接执行的方法来处理时,系统会给予一支新的 bash 让我们来执行 showname.sh 里面的指令,因此你的 firstname, lastname 等变量其实是在下图中的子程序 bash 内执行的。 当 showname.sh 执行完毕后,子程序 bash 内的所有数据便被移除,因此上表的练习中,在父程序下面 echo ${firstname} 时, 就看不到任何东西了!这样可以理解吗?

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

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

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

[[email protected] bin]$ source showname.sh
Please input your first name: VBird
Please input your last name:  Tsai
Your full name is: VBird Tsai
[[email protected] bin]$ echo ${firstname} ${lastname}
VBird Tsai  <==嘿嘿!有数据产生喔!

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

图12.2.2、showname.sh 在父程序当中运行的示意图

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