本章内容
- 构建基本脚本
- 使用多个命令
- 创建shell脚本文件
现在我们已经介绍了Linux系统和命令行的基础知识,可以开始编程了。本章讨论编写shell脚本的基础知识。在开始编写自己的shell脚本大作前,你必须知道这些基本概念。
10.1 使用多个命令
如果要两个命令一起运行,可以同一提示行输入它们,用分号分隔开:
[root@localhost ~]# date ; who 2018年 06月 11日 星期一 19:42:16 CST root pts/0 2018-06-11 17:29 (192.168.1.64) root pts/1 2018-06-11 19:42 (192.168.1.64)
恭喜你,你刚刚已经写了一个脚本了。这个简单的脚本只用到了两个bash shell命令。date命令先运行,显示了当月前日期和时间;后面紧跟着who命令的输入,显示了当月前是谁登录到了系统上。使用这种方法,你就能将任意多个命令串连在一起使用,只要不超过最大命令行字符数255就行。
然而这种技术公适用于小的脚本,它有一个致使的缺陷,既次运行之彰都必须在命令提示符下输入整个命令。但不需要手动将这些命令都输入命令行中,你可以将命令合并成一个简单的文本文件。在需要运行这些命令时,你可以简单地在运行这个文本文件。
10.2 创建shell脚本文件
要将shell命令放到一个文本文件中,首先需要用一个文本编辑器(参见第9章)来创建一个文件,然后将命令输入到文件中。
在创建shell脚本文件时,必须在文件的第一行指定要使得的shell。其格式为:
#!/bin/bash
在通过的shell脚本的行里,井号(#)用作注释行。shell脚本中的注释行是不被shell执行的。然而,shell脚本文件的第一行是个特例,井号后接叹号告诉shell用哪个shell来运行脚本(是的,佝要以用bash shell来运行你的脚本 程序,也可以用其他shelll)。
在指定了shell之后,可在文件的每行输入命令,后加一个回国符。之前提到过,注释可用井号添加。例如:
#!/bin/bash #This script displays the date and who's loged on date who
这就是所有脚本的内容了。如有需要,可用分号来在一行输入你要用的两个命令。但在shell脚本中,你可以在不同行来列出命令。shell会按根据命令在文件中出现的顺序来处理命令。
还有,要注意另有一行马以井号(#)开头,并添加了一个注释。以井号开头的行都不会被shell处理(除了以#!开头的第一行)。在脚本 中留下注释来说明脚本做了什么,这种方法非常好,所以两年后回过来再这个脚本时,你还可以很容易记起来你做了什么。
将这个脚本保存在名为test1的文件中,就基本好了。在运行新脚本前,还需要做其他一些事。一在运行脚本,如下 重复寄生可能会叫你的点失望:
[root@localhost ch10]# test1 -bash: test1: 未找到命令
你要跨过的第一个障碍是让bash shell能找到脚本文件。如第5章所述,shell会通过PATH环境变量来查找命令。
两种解决办法:
- 将shell脚本文件所处的目录添加到PATH环境变量中;
- 的提示符中有绝对或相对文件路径来引用shee脚本文件。
在这个例子中,我们将用第二种方式来告诉shell脚本文件所处的确切位置。记住要引用当前目录下的文件,你要在shell中使用单点操作符:
[root@localhost ch10]# ./test1 -bash: ./test1: 权限不够通过chmod命令赋予文件属主执行文件的权限。
[root@localhost ch10]# chmod u+x test1 [root@localhost ch10]# ls -l 总用量 4 -rwxr--r--. 1 root root 71 6月 11 20:25 test1 [root@localhost ch10]# ./test1 2018年 06月 11日 星期一 20:42:45 CST root pts/0 2018-06-11 17:29 (192.168.1.64) root pts/1 2018-06-11 19:42 (192.168.1.64)
成功了!现在万事俱备,能够执行新的shell脚本文件了。
10.3 显示消息
许多shell命令会产生自己的输出,这些输出会显示在脚本所运行的控制台显示器上。然而许多情况下,你可能想要添加自己的文本消息来告诉脚本用户脚本 正在做什么。你可以通过echo命令来做这个。echo命令能显示一个简单的文字字符串,如果你通过如下命令添加了字符串:
[root@localhost ch10]# echo This is a test This is a test
注意,默认情况下,你不需要使用引号将显示的文本字符串圈起来。但有时在字符串中出现引号的话可能就比较麻烦。
[root@localhost ch10]# echo Let's see if this'll work Lets see if thisll work
echo命令可用单引号或双引号来将文本字符串圈起来。如果你在字符串中用到了它们,你需要在文本中使用其中一种引号,而另外一种来将字符串圈起来:
[root@localhost ch10]# echo "This ia a test to see if you're paying attention" This ia a test to see if you're paying attention
[root@localhost ch10]# echo 'Rich says "scripting is easy".' Rich says "scripting is easy".
现在所有的引号都正确的输出中显示了。
你可以将echo语句添加到shell脚本中任何需要显示额外信息的地方:
[root@localhost ch10]# cat test1 #!/bin/bash #This script displays the date and who's loged on echo The time and date are: date echo "Let's see who's logged into the system:" who
当运行这个脚本时,它会产生如下输出:
[root@localhost ch10]# ./test1 The time and date are: 2018年 06月 11日 星期一 22:53:26 CST Let's see who's logged into the system: root pts/0 2018-06-11 17:29 (192.168.1.64) root pts/1 2018-06-11 19:42 (192.168.1.64)
很好,但如果你想在同一行显示一个文本字符串为命令输出,应该怎么办呢?你可以用echo 语句 -n参数。只要将第一个echo语句改成这样就行:
echo -n "The time and date are: "
你需要在字符串的两侧使用引号来保证在显示垢字符串尾部有一个空格。命令输出将会紧接着字符串结束的地方开始。现在输出会是这个样子:
[root@localhost ch10]# ./test1 The time and date are: 2018年 06月 11日 星期一 22:59:27 CST Let's see who's logged into the system: root pts/0 2018-06-11 17:29 (192.168.1.64) root pts/1 2018-06-11 19:42 (192.168.1.64)
完美! echo命令是shell脚本中同用户交互的重要工具。你会在很多情况下用到它,尤其是当你要显示脚本中变量的值时。我们下面继续孢子解这个。
10.4 使用变量
运行shell脚本中的单个命令很有用,但它有自身的限制。通常你可能会要用shell命令中的其他数据来处理信息。这点可以通过变量来完成。变量允许你临时性地将信息存储在shell脚本中,以便和脚本中的其他命令一起使用。本节将介绍如何在shell脚本中使用变量。
你可以在环境变量名称之前加个美元符($)来在脚本中使用这些环境变量。下面的脚本中交访问范畴 演示:
#!/bin/bash #display user information from the system. echo "User info for userid:$USER" echo UID: $UID echo HOME: $HOME
$USER、$UID和$HOME环境变量用业显示已登录用户的有关信息。输出看起来应该是这样了的:
[root@localhost ch10]# ./test2 User info for userid:root UID: 0 HOME: /root [root@localhost ch10]#
注意,echo命令中的环境变量在脚本运行时替换成当月前值。还有第一个字符串中我们可以将$USER系统变量放置到双引号中,而shell依然能够知道我们的意图。但采用这种方法也一个问题。看看这个例子会怎么样:
[root@localhost ch10]# echo "The cost of the item is $15" The cost of the item is 5
显然这不是我们想要的。只要脚本在引号中看到美元符,它就会以为你在引用一个变量。在这个例子中,脚本会尝试显示变量$1(但并未定义),再显示数字5。要显示美元符,你必须在它前面放置一个反斜线:
[root@localhost ch10]# echo "The cost of the item is \$15" The cost of the item is $15
看起来好多了。反斜线允许shell脚本将美元符解谜成为实际的美元符,而不是变量。下一节将会介绍如何在脚本中创建自己的变量。
说明 你可能还见过通过${varriable}形式引用的变量。变量名两侧额外的花括号通过用来帮助识别美元符后的变量名。
10.4.2 用户变量
除了环境变量,shell脚本还允许在脚本 中定义和使用自己的变量。定义变量允许临时存储数据并在整个脚本中使用,从而便shell脚本看起来更像计算机程序。
用户变量可以是任何不超过20个字母、数字或下划线的文本字符。用户变量区分大小写,所以变量Var1和变量var1是不同的。这个小规矩经常让脚本编程初学者感到头疼。
值通过等号赋给用户变量。在变量、等号和值之间不能出现空格(另一个困扰初学者的用法)。这里有一些给用户变量赋值的例子:
var1=10
var2=57
var3=testing
var4="still more testing"
shell脚本会自动决定变量值的数据类型。在脚本的整个生命周期里,shell脚本中定义的变量会地直保持着它们的值,但在shell脚本完成时删除掉。
类似于系统变量,用户变量可通过美元引用:
[root@localhost ch10]# cat test3 #!/bin/bash #testing variables days=10 guest="Katie" echo "$guest checked in $days days ago" days=5 guest="Jessica" echo "$guest checked in $days days ago"
运行脚本会有如下输出:
[root@localhost ch10]# ./test3 Katie checked in 10 days ago Jessica checked in 5 days ago
变量每次被引用时,都会输出当前赋给它的值。重要的是记住,引用一个变量值时需要使用美元符,而引用变量来对其进行赋值时则不要使用美元符。通过一个例子你就能明白我的意思;
[root@localhost ch10]# cat test4 #!/bin/bash #assigning a variable value to another variable value1=10 value2=$value1 echo The resulting value is $value2
当你在赋值语句中使用value1变量的值时,你仍然必须用美元符。这段代码产生如下输出:
[root@localhost ch10]# ./test4 The resulting value is 10
要是你忘了用美元符,使得value2的赋值行弄成这样:
value2=value1
你会得到如下输出:
[root@localhost ch10]# ./test4 The resulting value is value1没有美元符,shell会将变量名解释成变通的文本字符串,通常这并不是你想要的结果。
10.4.3 反引号
shell脚本中的最有用的特性之一是反引号(`).注意,这并非是那个你所习惯的用来圈起字符串的变通单引号字符。由于shell脚本之外很少用到它,你可能甚至都不知道在键盘什么地方找到综。但你必须慢慢熟悉它,因为这是许多shell脚本中的重要组件。提示:在美工键盘上,它通常和波浪线(~)位于同一键位。
反引号允许你将shell命令的输出赋给变量。尽管这看起来并不那么重要,但它却是脚本编程中的一个主要构件。
你必须用反引号把整个命令行圈起来:
testing=`date`
shell会运行反引号中的命令,并将其输出赋给变量testing。这里有个通过变通的shell命令输出创建变量的例子:
[root@localhost ch10]# cat test5 #!/bin/bash #using the backtick character testing=`date` echo "The date and time are: " $testing
变量testing收到了date命令的输出,并在echo语句中用来显示它。运行这个shell脚本生成如下输出:
[root@localhost ch10]# chmod u+x test5 [root@localhost ch10]# ./test5 The date and time are: 2018年 06月 12日 星期二 01:52:11 CST
这个例子并没什么特别吸引人的地方(你也可以很轻松地将该命令放在echo语句中),但只要将命令的输出放到了变量里,你就能用它为来干任何事情了。
下面这个例子很常见,它在脚本中通过反引号获得当前日期并用它来生成唯一文件名:
#!/bin/bash #copy the /usr/bin directory listing to a log file today=`date +%y%m%d` ls /usr/bin/ -al > log.$today
today变量被赋予格式化后的date命令的输出。这是用来为日志文件名抓取日期信息常用的一种技术。+%y%m%d格式告诉data命令将日期显示为两位数的年、月、日的组合:
[root@localhost ch10]# date +%y%m%d 180612
这个脚本将值赋给一个变量,之后再将其作为文凭名的一部分。文件自身含有目录列表的重向输出(将在10.5节中详细讨论)。运行脚本之后,你应该能在目录中看到一个新文件:
-rw-r--r--. 1 root root 48620 6月 12 02:00 log.180612
目录中出现的日志文件采用$today变量的值作为文件名的一部分。日志文件的内容是/usr/bin目录的列表输出。如果脚本在后一天运行,日志文件名会是log.100613,因此每天创建一个新文件。
10.5 重定向输入和输出
有些时候你想要保存某个命令的输出而非在显示器上显示它。bash shell提供了一些不同的操作符来将某个命令的输出重定向到另一个位置(比如文件)。重定向可以通过将某个文件重写向到某个命令上来用在输入上,也可以用在输出上。本节介绍了如何在shell脚本中使得重写向。
10.5.1 输出重定向
重定向最基本的类型是将命令的输出发到一个文件中,bash shell采用大于号(>)来完成这项功能:
command > outputfile
之前显示器上出现的命令的输出被保存到指定的输出文件中:
[root@localhost ch10]# date > test7 [root@localhost ch10]# ll -l test7 -rw-r--r--. 1 root root 43 6月 13 09:51 test7 [root@localhost ch10]# cat test7 2018年 06月 13日 星期三 09:51:23 CST
重定向操作符创建了一个文件test7(通过默认的umask设置)并将date命令的输出重定向到test7文件中。如果输出文件已经存在了,则这个重定向操作符会用新的文件数据覆盖已经存在的文件:
[root@localhost ch10]# who > test7 [root@localhost ch10]# cat test7 root pts/0 2018-06-12 02:46 (192.168.1.64)
现在test7文件的内容保存的是who命令的输出。
有时,取代覆盖文件的内容,你可以想要将命令的输出追加到已有文件上,比如你正在创建一个记录系统上某个操作的日志文件。在这种情况下,你可以用双大于号(>>)来追加数据:
[root@localhost ch10]# cat test7 root pts/0 2018-06-12 02:46 (192.168.1.64) 2018年 06月 13日 星期三 09:56:40 CSTtest7文件仍然包含早些时候who命令处理的数据,加上现在新从date命令获得的输出。
10.5.2 输入重定向
输入重定向和输出重定向正好相反。输入重定向文件的内容重定向到命令,而非将命令的输出重定向到文件。
输入重定向符号是小于号(<):
command < inputfile
记住它的简易办法是在命令行上,命令总是在左侧,而重定向符号“指向”数据流动的方向。
小于号说明数据正在从输入文件流向命令。
这里有个和wc命令一起使用输入重定向的例子:
[root@localhost ch10]# wc < test7 2 11 97
wc命令提供了对数据中文本的计数。默认情况下,它会输出3个值:
- 文本的行数;
- 文本的词数;
- 文本的字节数。
通过将文本文件重定向到 wc命令,你可以得到对文件中的行,词和字节的快速计数。这个例子说明test7文件有2行、11个单词以及97字节。
command << marker
data
maker
还有另外一种输入重定向的方法,称为内联输入重定向(inline input redirection)。 这种访求允许你在命令行而不是在文件指定输入重定向的数据。乍看一眼,这可能有点奇怪,但有些应用会用到这个过程(比如在10.7节中提到的那些)。
[root@localhost ch10]# wc << EOF > test string 1 > test string 2 > test string 3 > EOF 3 9 42 [root@localhost ch10]#
次提示符会一直提示输入更多数据,直到你输入了作为文本标记的那个字符串值。wc命令会对内联输入重定向提供的数据执行行、词和字节计数。
10.6 管道
有时你需要发送某个命令的输出作为另一个命令的输入。可以用重定向,只是有些笨拙:
[root@localhost ch10]# rpm -qa > rpm.list [root@localhost ch10]# sort < rpm.list
rpm命令管理着通过Red Hat包管理系统(RPM)安装到系统上的软件包,比如上面列出的Fedora系统。在和-qa参数一起使用时,它会生成已安装包的列表,但并不会遵循某种特定的顺序。如果你在查找某个特定的包或一组包,可能会比较难在rpm命令的输出中找到它。
通过标准的输出重定向,rpm命令的输出被重定向到文件rpm.list。命令完成后,rpm.list文件保存着系统上的所有软件包列表。下一步,输入重定向用来将rpm.list文件的内容发送给sort命令来将包名按字母顺序排序。
这很有用,但仍然是一种比较烦琐的生成信息的方式。取代将命令的输出重定向到文件,你可以重定向输出到另一个命令。这个过程为管道连接(piping)。
类似于反引号(),管理的符号在shell编程之外也很少用到。该符号由两个竖线构成,一个在另一个上面。然而,pipe符号印刷体看起来更像是单个竖线(|).在美式键盘上,它通常和反斜线(\)们于同一个键。管道主在命令键,将一个命令的输出重定向到另一个:
command1 | command2
不要以为管理链接会一个一个地运行。Linux系统实际上会同时运行这两个命令,在系统内部它们连接起来。 在第一个命令产生输出的同时,输出会被立即送给第二个命令。传输数据不会用到任何中间文件或缓冲区域。
现在,通过管道你可以轻松的将rpm命令的输出管理连接到sort命令来产生结果 :
[root@localhost ch10]# rpm -qa | sort
除非你眼神特别好,否则该命令会一闪而过,你很有可能来不及看清就没了。由于管理功能是实时运行的,所以只要rpm命令一输出数据,sort命令就会立即对其进行排序。等到rpm命令输出完数据, sort命令就已经将数据排序好序并显示在显示器上了。
可以在一条命令中使用任意多条管道。可以继续将命令的输出通过管理传给其他命令来简化操作。
在这个例子中,sort命令的输出会一闪而过,所你人钶以用一条文本分页命令(例如less或more)来强行将输出按屏显示。
[root@localhost ch10]# rpm -qa | sort | more acl-2.2.51-12.el7.x86_64 aic94xx-firmware-30-6.el7.noarch alsa-firmware-1.0.28-2.el7.noarch alsa-lib-1.0.28-2.el7.x86_64 alsa-tools-firmware-1.0.28-2.el7.x86_64 apr-1.4.8-3.el7.x86_64 apr-util-1.5.2-6.el7.x86_64 audit-2.7.6-3.el7.x86_64 audit-libs-2.7.6-3.el7.x86_64 audit-libs-python-2.7.6-3.el7.x86_64 authconfig-6.2.8-10.el7.x86_64 autoconf-2.69-11.el7.noarch automake-1.13.4-3.el7.noarch avahi-autoipd-0.6.31-15.el7.x86_64 avahi-libs-0.6.31-15.el7.x86_64 basesystem-10.0-7.el7.centos.noarch bash-4.2.46-19.el7.x86_64 bind-libs-lite-9.9.4-29.el7.x86_64 bind-license-9.9.4-29.el7.noarch binutils-2.23.52.0.1-55.el7.x86_64 biosdevname-0.6.2-1.el7.x86_64 bison-2.7-4.el7.x86_64 boost-system-1.53.0-25.el7.x86_64 boost-thread-1.53.0-25.el7.x86_64 btrfs-progs-3.19.1-1.el7.x86_64 byacc-1.9.20130304-3.el7.x86_64 bzip2-1.0.6-13.el7.x86_64 bzip2-libs-1.0.6-13.el7.x86_64 ca-certificates-2015.2.4-71.el7.noarch centos-logos-70.0.6-3.el7.centos.noarch centos-release-7-2.1511.el7.centos.2.10.x86_64 checkpolicy-2.5-4.el7.x86_64 chkconfig-1.3.61-5.el7.x86_64 coreutils-8.22-15.el7.x86_64 cpio-2.11-24.el7.x86_64 cpp-4.8.5-4.el7.x86_64 cracklib-2.9.0-11.el7.x86_64 cracklib-dicts-2.9.0-11.el7.x86_64 cronie-1.4.11-14.el7.x86_64 cronie-anacron-1.4.11-14.el7.x86_64 crontabs-1.11-6.20121102git.el7.noarch cryptsetup-libs-1.6.7-1.el7.x86_64 cscope-15.8-7.el7.x86_64 ctags-5.8-13.el7.x86_64 curl-7.29.0-25.el7.centos.x86_64 cyrus-sasl-lib-2.1.26-19.2.el7.x86_64 dbus-1.6.12-13.el7.x86_64 dbus-glib-0.100-7.el7.x86_64 dbus-libs-1.6.12-13.el7.x86_64 dbus-python-1.1.1-9.el7.x86_64 --More--
这行命令序列会行执行rpm命令,将它的输出通过管道传给 sort。然后再将sort的输出通过管理传给more命令来显示,在显示完每屏信息后停下来。这样你就可以在继续处理前停下来。阅读显示器上显示的信息。
如果想要更别致点,你也可以搭配使用重定向和管道来将输出保存到文件中;
[root@localhost ch10]# rpm -qa | sort > rpm.list [root@localhost ch10]# more rpm.list acl-2.2.51-12.el7.x86_64 aic94xx-firmware-30-6.el7.noarch alsa-firmware-1.0.28-2.el7.noarch alsa-lib-1.0.28-2.el7.x86_64 alsa-tools-firmware-1.0.28-2.el7.x86_64 apr-1.4.8-3.el7.x86_64 apr-util-1.5.2-6.el7.x86_64 audit-2.7.6-3.el7.x86_64 audit-libs-2.7.6-3.el7.x86_64 audit-libs-python-2.7.6-3.el7.x86_64 authconfig-6.2.8-10.el7.x86_64 autoconf-2.69-11.el7.noarch automake-1.13.4-3.el7.noarch avahi-autoipd-0.6.31-15.el7.x86_64 avahi-libs-0.6.31-15.el7.x86_64 basesystem-10.0-7.el7.centos.noarch bash-4.2.46-19.el7.x86_64 bind-libs-lite-9.9.4-29.el7.x86_64 bind-license-9.9.4-29.el7.noarch binutils-2.23.52.0.1-55.el7.x86_64 biosdevname-0.6.2-1.el7.x86_64 bison-2.7-4.el7.x86_64 boost-system-1.53.0-25.el7.x86_64 boost-thread-1.53.0-25.el7.x86_64 btrfs-progs-3.19.1-1.el7.x86_64 byacc-1.9.20130304-3.el7.x86_64 bzip2-1.0.6-13.el7.x86_64 bzip2-libs-1.0.6-13.el7.x86_64 ...
到目前为止,管理最流行的用法之一是将命令产生的长输出结果通过管理传送给more命令。这对ls命令来说尤其普遍。
ls -l 命令产生了一个目录中所有文件的长列表。对于有大量文件的目录来说,这个列表会相当长。通过将输出管理连接到more命令,你可以强制输出在每屏数据的末尾停下来。
10.7 执行数学运算
另一个参任何编程语言都很重要的特性是操作数字的能力。遗憾的是,对shell 脚本来说,这个过程会比较麻烦。在shell脚本中有两种途径来进行数据运行操作。
10.7.1 expr命令
最开始,Bourne shell提供了一个特别的命令用来处理数字表达式。 expr命令允许在命令行上处理数字表达式,但是特别笨拙:
[root@localhost ch10]# expr 1 + 5 6
尽管标准操作符在expr命令中工作得很好。但在脚本或命令行上使用它们时仍有问题出现。许多expr命令操作符在 shell中有其他意思(比如星号)。在expr命令中使用它们会得到一些诡异的结果:
[root@localhost ch10]# expr 5 * 10 expr: 语法错误
要解决这个问题,在传给expr命令前,你需要使用shell的转义字符(反斜线)来识别容易被shell错误解释字符:
[root@localhost ch10]# expr 5 \* 10 50
现在麻烦才刚刚开始!在shell脚本中使用expr命令也同样麻烦:
[root@localhost ch10]# cat test6 #!/bin/bash # An example o fusing the expr command var1=10 var2=20 var3=`expr $var2 / $var1` echo The result is $var3
要将一个数字算式的结果赋给一个变量,你需要使用反引号来获取expr命令的输出:
[root@localhost ch10]# ./test6 The result is 2
幸好,bash shell有一个针对处理数学运行符的改进,你将会在下节中看到。
10.7.2 使用方括号
bash shelel为了保持 跟Bourne shell的美容而包含了expr命令,但它同样也提供了一个执行数字表达式的更简单的方法。在bash中,在将一个数字运算结果赋给某个变量旱是,你可以用美元符和方括号($[operation])将数学表达式圈起来:
[root@localhost ch10]# var1=$[1 + 5] [root@localhost ch10]# echo $var1 6 [root@localhost ch10]# var2=$[$var1 * 2] [root@localhost ch10]# echo $var2 12
用方括号执行shell数字运算比用expr命令方便很多。这种技术在shell脚本中也能工作起来:
[root@localhost ch10]# cat test7 #!/bin/bash var1=100 var2=50 var3=45 var4=$[$var1 * ($var2 - $var3)] echo The final result is $var4
运行这个脚本会得到如下输出:
[root@localhost ch10]# ./test7 The final result is 500
同样,注意在使用方括号来计算公式时,不用担心shell会误解乘号或其他符号。shell知道它不是通配符,因为它在方括号内。
在bash shell脚本中进行算术运行会有一个主要的限制。看看下面这个例子:
[root@localhost ch10]# cat test8 #!/bin/bash var1=100 var2=45 var3=$[$var1 / $var2] echo The final result is $var3
现在,运行一下,看看会发生什么:
[root@localhost ch10]# ./test8 The final result is 2
bash shell数学运算符只支持整数运算。如果你要进行任何实际的数学计算,这是一个巨大的限制。
说明 z shell(zsh)提供了完整的浮点数算术操作。如果你需要在shell脚本中进行浮点数运算,你可以考虑看看z shell(将在第22章中讨论)。
10.7.3 浮点解决方案
有几种解决方案能够解决bash中数学运算的整数限制。最常见的解决方法是用内建的bash计算器,称作bc。
1.bc 的基本用法
bash计算器其实是允许你在命令行输入浮点表达式、解释表达式、计算并返回结果的一种编程语言。bash计算器能够识别:
数字(整数和浮点数);
变量(简单变量和数组);
注释(以井号开始的行或C语言中的/**/对)
表达式
编程语句(例如if-then)
函数。
你可以在shell提示符下通过bc 命令访问bash计算器:
[root@localhost ch10]# bc bc 1.06.95 Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software Foundation, Inc. This is free software with ABSOLUTELY NO WARRANTY. For details type `warranty'. 12 * 5.4 64.8 3.156 * (3 + 5) 25.248 quit
这个例子由输入表达式12 * 5.4开始。bash计算器会返回答案。每个输入到计算器的后续表达式都会被执行。结果会显示出来。要退出bash计算器,你必须输入quit。
浮点运算是由一个内建的称为scale的变量控制的。你必须将这个值设置为结果里你想要的小数后的位数,否则你不会得到想要的结果的:
[root@localhost ch10]# bc -q 3.44 / 5 0 scale=4 3.44 / 5 .6880 quit
scale 变量的默认值是0.在scale值被设置前,bash计算器提供的答案没有小数点后的位置。在将其值设置成4后,bash计算器显示的答案有四位小数。-q命令行参数会将bash计算器输出的很长的欢迎物权条屏蔽掉。
除了变通数字,bash计算器还能支持变量:
[root@localhost ch10]# bc -q var1=10 var1 * 4 40 var2 = var1 / 5
[root@localhost ch10]# ./test9 The answer is .6880
print var22quit
一旦变量的值被定义了,你就可以在整个bash计算器会话中使用变量了。print语句允许你打印变量和数字。
2.在脚本中使用bc
现在你可能想问bash计算器中如何在shell脚本中帮助你处理浮点运算的。你还记得老朋友反引号吗?是的,你可以用反引号来运行bc命令并将输出赋给一个变量。基本格式是这样的:
variable=`echo "option; expressiion"| bc`
第一部分options允许你来设置变量。如果你需要设置不止一个变量,可以用分号来分开它们。expression参数定义了通过bc执行的数学表达式。这里有个在脚本中使用它的例子:
[root@localhost ch10]# cat test9 #!/bin/bash var1=`echo " scale=4; 3.44 / 5" | bc` echo The anser is $var1
这个例子将scale变量设置成了四位小数,并为表达式指定了特定的运算。运行这个脚本会产生如下输出:
[root@localhost ch10]# ./test9 The answer is .6880
太好了! 现在你不会被限制只能用数字作为表达式了。你也可以用shell脚本中定义好的变量:
#!/bin/bash var1=100 var2=45 var3=`echo "scale=4; $var1 / $var2" | bc` echo The answer for this is $var3
脚本定义了两个变量,它们都可以用在表达式中发送给bc命令。记住用美元符来表示变量的值而不是变量自身。这个脚本的输出如下:
var3=`echo "scale=4; $var1 / $var2" | bc` echo The answer for this is $var3
当然,一旦一个值被赋给了变量那个变量就能在其他运算中使用了:
[root@localhost ch10]# cat test11 #!/bin/bash var1=20 var2=3.14159 var3=`echo "scale=4; $var1 *$var1" | bc` var4=`echo "scale=4; $var3 *$var2" | bc` echo The final result is $var4
这个方法适用于较短的运算,但有时你会更多地和你自己的数字打次道。如果你有很多运算,在同一个命令行中列出多个表达式会有点麻烦。