本章内容

  • 构建基本脚本
  • 使用多个命令
  • 创建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 CST

test7文件仍然包含早些时候who命令处理的数据,加上现在新从date命令获得的输出。

10.5.2 输入重定向

输入重定向和输出重定向正好相反。输入重定向文件的内容重定向到命令,而非将命令的输出重定向到文件。

输入重定向符号是小于号(<):

command < inputfile

记住它的简易办法是在命令行上,命令总是在左侧,而重定向符号“指向”数据流动的方向。

小于号说明数据正在从输入文件流向命令。

这里有个和wc命令一起使用输入重定向的例子:

[root@localhost ch10]# wc < test72 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
> EOF3  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

一旦变量的值被定义了,你就可以在整个bash计算器会话中使用变量了。print语句允许你打印变量和数字。

[root@localhost ~]# bc -q
var1=10
var1 * 4
40
var2 = var1 / 5
print var2

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命令。记住用美元符来表示变量的值而不是变量自身。这个脚本的输出如下:

[root@localhost ch10]# ./test10
The answer for this is 2.2222

当然,一旦一个值被赋给了变量那个变量就能在其他运算中使用了:

[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

这个方法适用于较短的运算,但有时你会更多地和你自己的数字打交道。如果你有很多运算,在同一个命令行中列出多个表达式会有点麻烦。

针对这个问题有个解决办法。bc命令能识别输入重定向,允许你将一个文件重定向到bc命令来处理。然而,这同样会叫人困惑,因为你必须将表达式存储到文件中。

最好的办法是使用内联输入值重定向,允许你直接在控制台重定向数据。在shell脚本中,你可以将输出赋给一个变量:

variable=`bc << EOF
options
statements
expressions
EOF
`

EOF文本字符串标识了内联重定向数据的开始和结尾。记住仍然需要反引号来将bc命令的输出赋给变量。

现在你可以将所有独立的bash计算器元素放到同一个脚本文件中的不同行。下面是在脚本中使用这种技术的例子:

[root@localhost ch10]# cat test12
#!/bin/bash
var1=10.46
var2=43.67
var3=33.2
var4=71var5=`bc << EOF
scale = 4
a1 = ( $var1 + $var2)
b1 = ( $var3 + $var4)
a1 + b1
EOF
`
echo The final answer for this mess is $var5

将每个选项和表达式在脚本中不同的行中,可以让事情更清晰,也更容易阅读和跟进。EOF字符串标识了重定向给bc命令的数据的起怒骂 和结尾。当然,你需要用反引号来标识输出要赋给变量的命令。

你还会注意到这个例子中,佝可以在bash计算器中赋值给变量。重要的是记住,在bash计算器创建的变量只在bash计算中有效,不能在shell脚本中使用。

 10.8 退出脚本

到目前为止,在我们的示例脚本中,我们总是匆忙结尾。在我们运行完成最后和条命令时,是结束了脚本。这里还有一个更好的结束事情的办法。

shell中运行的每个命令都使用退出状态码(exit status)来告诉shell它完成了处理。退出状态码是一个0~255之间的整数值,在命令结束运行时由命令传给shell.你可以捕获这个值并在脚本中使用。

10.8.1 查看退出状态码

Linux提供了$?专属变量来保存上个执行的命令退出状态码。你必须在你要查看的命令之后马上查看或使用$?变量。它的值会变成shell中执行最后一条命令的退出状态码:

[root@localhost ch10]# date
2018年 06月 13日 星期三 19:43:43 CST
[root@localhost ch10]# echo $?
0

按照惯例,一个成功结束的命令退出状态码是0。如果一个命令结束时有错误,退出状态码就会有一个正数值:

[root@localhost ch10]# asdfg
-bash: asdfg: 未找到命令
[root@localhost ch10]# echo $?
127

无效命令会返回一个退出状态码127。Linux错误退出状态码没有什么标准惯例。但有一些可用的参考,如表10-2所示。

表10-2 Linux退出状态码

状态码 描述
0 命令成功结束
1 通用未知错误
2 误用shell命令
126 命令不可执行
127 没找到命令
128 无效退出参数
128+x Linux信号x的严重错误
130 命令通过Ctrl+C终止
255 退出状态码越界

退出状态码126表明用户没有执行命令的正确权限:

[root@localhost ch10]# ./aa.list
-bash: ./aa.list: 权限不够
[root@localhost ch10]# echo $?
126

另一个你会碰到的常见错误是给某个命令提供了无效参数:

[root@localhost ch10]# date %t
date: 无效的日期"%t"
[root@localhost ch10]# echo $?
1

这会生成通用的退出状态码1,表明在命令中发生了未知错误。

10.8.2 exit命令

默认情况下,shell脚本会以脚本中的最后一个命令的退出状态码退出:

[root@localhost ch10]# ./test6
The result is 2
[root@localhost ch10]# echo $?
0

你可以改变这个来返回你自己的退出状态吗。exit命令你允许你在脚本结束时指定一个退出状态码:

[root@localhost ch10]# cat test13
#!/bin/bash
# testing the exit status
var1=10
var2=30
var3=$[ $var1 + $var2]
echo The answer is $var3
exit 5

当你查看脚本的退出码时,你会得到作为参数伟给exit命令的值:

[root@localhost ch10]# chmod u+x test13
[root@localhost ch10]# ./test13
The answer is 40
[root@localhost ch10]# echo $?
5

你也可以在exit命令的参数中使用变量:

[root@localhost ch10]# cat test14
#!/bin/bash
#testing the exit status
var1=10
var2=30
var3=$[ $var1 + $var2 ]
exit $var3

当你运行这个命令时,它会产生如下退出状态:

[root@localhost ch10]# chmod u+x test14
[root@localhost ch10]# ./test14
[root@localhost ch10]# echo $?
40

然后,你要注意这个功能,退出状态码最大只能是255.看下面例子会怎样:

[root@localhost ch10]# cat test14b
#!/bin/bash
#testing the exit status
var1=10
var2=30
var3=$[ $var1 * $var2 ]
exit $var3

现在运行它,你会得到如下输出:

[root@localhost ch10]# ./test14b
[root@localhost ch10]# echo $?
44

退出状态码超出了0~255的区间。shell通过模运算得到这个结果。一个值的模就是被除后的余数。结果中的数是指定的数被256除的余数。在这个例子中的300( 返回的值),余数是44.它就成了最后的状态退出码。

在第11章中,你会了解如何用if-then 语句来检查某个命令返回的错误状态,以便知道命令是否成功。

10.9 小结

bash shell脚本允许你将多个命令串起来放进脚本中。创建脚本的最基本的方式是将命令中的多个命令通过分号分开来。shell会按顺序执行每个命令,在显示器上显示每个命令的输出。

你也可以创建一个shell脚本文件,将多个命令放进同一个文件以便shell按顺序执行。shell脚本文件必须定义运行脚本的shell。这个可以通过#!符号脚本文件的第一行指定,后面跟上shell的全路径。

在shell脚本内,你可以通过在变量前使用美元符来引用环境变量。你也可以定义自己的变量以便在脚本内使用,并对其赋值甚至是通过反引号捕获的某个命令的输出。变量值可以在脚本中使用。在变量名前放置一个美元符。

bash shell允许你将命令的输入和输出从标准和为重定向。你可以通过大于号重定向任意命令在显示屏幕上输出到一个文件,后面紧跟着用来存储输出的文件的名称。你也可以通过双大于号将输出数据追加到已有文件。小于号用来重定向输入到命令。你可以将文件中的输入重定向到命令。

Linux管道命令(断条符号)允许你将命令的输出直接重定向到另一个命令的输入。Linux系统会同时运行这两条命令,将第一个命令的输出发送给第二个命令的输入,而不用任何重定向文件。

bash shell提供了许多方式来在shell脚本中执行数学操作。expr命令是一个进行整数运算的简便方法。在bash shell中,你也可以通过方括号包围的表达式来执行基本的数学运算,前加一个美元符。要执行浮点运算,你需要利用bc 计算器命令,并从内联数据重定向输入、将输出存储到用户变量中。

最后,本章讨论了如何在shell脚本中使用退出状态码。shell中运行的每个命令都会产生一个退出状态码。退出状态码是一个0~255间的整数值,表明命令是否成功执行,以及如果不是,可能的原因是什么。退出状态码0表明命令成功执行了。你呆以在shell脚本吕用exit命令在脚本完成时声明特定的退出状态码。

Linux 命令行与shell编程 第10章 构建基本脚本相关推荐

  1. 《linux命令行与shell编程大全》--读书笔记

    1.初识Linux shell 图片链接 2.走进shell 1.进入命令行,CLI(command line interface),也叫Linux控制台 通过Linux控制台终端访问CLI Ctrl ...

  2. 《Linux命令行与shell脚本编程大全 第3版》Linux命令行---10

    以下为阅读<Linux命令行与shell脚本编程大全 第3版>的读书笔记,为了方便记录,特地与书的内容保持同步,特意做成一节一次随笔,特记录如下: 转载于:https://www.cnbl ...

  3. 《Linux命令行与shell脚本编程大全 第3版》创建实用的脚本---10

    以下为阅读<Linux命令行与shell脚本编程大全 第3版>的读书笔记,为了方便记录,特地与书的内容保持同步,特意做成一节一次随笔,特记录如下: 转载于:https://www.cnbl ...

  4. Linux命令行与shell脚本编程大全:第2版

    <Linux命令行与shell脚本编程大全:第2版> 基本信息 作者: (美)布卢姆(Blum,R.) 布雷斯纳汉(Bresnahan.C.) [作译者介绍] 译者: 武海峰 丛书名: 图 ...

  5. Linux_《Linux命令行与shell脚本编程大全》第十八章学习总结

    时间:2017年12月04日星期一 说明:本文部分内容均摘取自书籍<Linux命令行与shell脚本编程大全>,版权归原作者所有.<Linux命令行与shell脚本编程大全>( ...

  6. Linux_《Linux命令行与shell脚本编程大全》第十章学习总结

    时间:2017年08月24日星期四 说明:本文部分内容均摘取自书籍<Linux命令行与shell脚本编程大全>,版权归原作者所有.<Linux命令行与shell脚本编程大全>( ...

  7. 《Linux命令行与Shell脚本编程大全第2版.布卢姆》pdf

    下载地址:网盘下载 内容简介  · · · · · · 本书是一本关于Linux 命令行与shell 脚本编程的全面教程.全书分为四部分:第一部分介绍Linuxshell 命令行:第二部分介绍shel ...

  8. 【Linux命令行与Shell脚本编程】第五章 理解 Shell 父子关系 后台进程 协程

    Linux命令行与Shell脚本编程 第五章 理解 Shell 文章目录 Linux命令行与Shell脚本编程 五,理解 Shell 5.1,shell的类型 5.2,shell的父子关系 5.2.1 ...

  9. 《linux命令行与shell脚本编程大全》第三版 - 核心笔记(3/4):函数与图形化脚本

    <linux命令行与shell脚本编程大全> 全书4部分: ☆ [1]linux命令行(1-10章) ☆ [2]shell脚本编程基础(11-16章) ☆ [3]高级shell脚本编程(1 ...

最新文章

  1. 黑客攻破网站涂鸦特效(强烈建议看看)
  2. STM32F4 HAL库开发 -- SPI Flash
  3. 关于bacula网络备份软件的安装以及配置3
  4. 什么叫白户,白户能贷款吗?
  5. MySQL命令(二)| 表的增删查改、聚合函数(复合函数)、联合查询
  6. 手动配置 iis php环境,iis上手动配置php
  7. JZOJ 1237. 餐桌
  8. Jenkins 无法运行 putty.exe问题解决
  9. OpenCV算子速查表(持续更新)
  10. 数据库网页搭建教程(二)——数据库网页设计
  11. SQL Server 2008 下载及版本说明
  12. 番外9福冈·狂野老司机告诉你如果装AI·1· ——混合现实科幻《地与光》
  13. 1.1各种编程语言的介绍
  14. 玩转SpringCloud(F版本) 四.路由网关(zuul)
  15. Kafka的Topic删不掉
  16. 吴晓波:预见2021(跨年演讲 —— 02 “云上中国”初露峥嵘)
  17. it人才外包可以帮助企业解决这些问题
  18. 设计心理学中的映射交互设计概念|优漫动游
  19. 关于计算ico文件hash值脚本
  20. MySQL为JSON字段创建索引(Multi-Valued Indexes 多值索引)

热门文章

  1. HandyJSON的swift json解析第三方使用教程
  2. Solidworks怎么卸载干净?
  3. Spring(完整版)
  4. 知识小罐头05(tomcat8请求源码分析 上)
  5. 在线PDF格式转换器推荐,小圆象PDF转换器满足您的办公需求
  6. 记录给Lenovo T460机械硬盘升级为SSD的过程
  7. kaldi中特征变换
  8. 炫技!超酷的HTML 3D动态效果
  9. 如何写 Makefile 文件
  10. 前端面试题干货汇总(超详细)