来源: Linux命令行与shell脚本编程大全
内容
- 基本的脚本函数
- 返回值
- 在函数中使用变量
- 数组变量和函数
- 函数递归
- 创建库
- 在命令行上使用函数
我们可以将shell脚本代码放进函数中封装起来,这样就能在脚本中的任何地方多次使用它了。
下面我们来逐步了解如何创建自己的shell脚本函数并在应用中使用它们。
来源: Linux命令行与shell脚本编程大全
内容
- 基本的脚本函数
- 返回值
- 在函数中使用变量
- 数组变量和函数
- 函数递归
- 创建库
- 在命令行上使用函数
我们可以将shell脚本代码放进函数中封装起来,这样就能在脚本中的任何地方多次使用它了。
下面我们来逐步了解如何创建自己的shell脚本函数并在应用中使用它们。
这里首先要提及一些概念,然后我们再看具体的实例加以理解。
每一个单独的对象都可以被称为对应类的一个实例(instance)。操作指定类的函数称为方法(method)。
把程序接口从具体的实现细节中分离开来的过程称为封装。
在OOP(面向对象编程)中,我们可以通过一个类创建出另外一个类,只需要指定新类的不同信息即可,这种方法称为继承。由此衍生出,被继承的类称为父类或超类(superclass),新创建的类称为子类(subclass)。
在OOP中,允许同一个方法名操纵不同对象并得到不同的结果,称为多态(polymorphism)。
通过一系列的其他类来创建新类的过程称为组合(composition)。在一些语言中,一个类可以从多个类中继承方法,称为多重继承(multiple inheritance)。
参考学习《Bioinformatics. Data. Skills》,这里简要地整理下Linux用来处理数据文本的工具。具体命令详情请在Linux命令大全中搜索或者查阅其他相关资料。
Shell脚本笔记系列:
内容:
- 传递参数
- 跟踪参数
- 移动变量
- 处理选项
- 将选项标准化
- 获得用户输入
经过前面的介绍,我们已经可以掌握一些流程化的脚本编程了。但有时候,我们需要编写的脚本能够跟使用者进行交互。它可以是静态的,输入相应的参数让它运行到底;也可以是动态的,脚本根据输入参数反馈不同的信息,使用者又能根据信息调整下一步的处理,实时与程序互动。
bash shell提供了一些不同的方法来从用户处获得数据,包括命令行参数、命令行选项以及直接从键盘读取输入的能力。下面将一一介绍实现。
内容
- 使用if-then语句
- 嵌套if语句
- test命令
- 复合条件测试
- 使用双方括号和双括号
- case命令
许多程序要求对shell脚本中的命令施加一些逻辑流程控制。而某些命令会根据条件判断执行相应的命令,这样的命令通常叫做结构化命令。从概念上理解,结构化命令是shell脚本的逻辑结构,不像顺序执行shell脚本,而是有组织地执行命令以应对复杂任务需求。
最基本的结构化命令是if-then语句,它的格式如下:
1 | if command |
注意,在其他编程语言中,if
语句之后的对象是一个等式,等式的结果为TRUE
或者FALSE
,但是bash shell中的if
语句是运行if
后面的命令,如果该命令的退出状态码是0(命令成功执行),则运行then
语句后面的命令。fi
表示if
语句到此结束。
下面是一个简单的例子:
1 | wsx@wsx-ubuntu:~/script_learn$ cat test1.sh |
这个例子中在判断成功执行pwd
命令后,执行输出文本字符串。
大家可以尝试把pwd
命令改成随便乱打的字符试试结果。它会显示报错信息,then
后面的语句也不会执行。
if-then语句的另一种形式:
1 | if command; then |
在then部分,我们可以使用多个命令(从格式中command结尾有没有s也可以看出)。
我们再来一个例子:在if
语句中用grep
命令在/etc/passwd
文件中查找某个用户名当前是否在系统上使用。如果有用户使用了哪个登录名,脚本会显示一些文本信息并列出该用户HOME目录的bash文件。
1 | wsx@wsx-ubuntu:~/script_learn$ cat test3.sh |
如果设置的用户名不存在,那么就没有输出。那么如果在这里显示的一些消息可以说明用户名在系统中未找到,这样可能就会显得更友好。所以接下来看看if-then-else
语句。
我相信意思非常容易理解,这里较之前我们添加了一个else
块来处理if
中命令没有成功执行的步骤。格式为:
1 | if command |
有时我们需要检查脚本代码中的多种条件,可以是用嵌套的if-then
语句。
处理一个例子:检查/etc/passwd
文件中是否存在某个用户名以及该用户名的目录是否存在。
1 | wsx@wsx-ubuntu:~/script_learn$ cat test5.sh |
可以使用else
部分的另一种形式:elif
。这样我们就不再用书写多个if-then
语句了。在其他语言中,有的是用elif
的形式,有的使用else if
等形式。面对相同内含在不同语言中不同的表示方式,我们需要有意识地区别,以免接触的东西多了可能各种语言代码串写喔。
1 | if command1 |
这种表示方式逻辑更为清晰,但是也有点容易让写的人搞混。其实可以看到一个if
对应一个fi
。这是一个大的嵌套if
结构。
记住,在elif
语句中,紧跟其后的else
语句属于elif
代码块,而不是属于if-then
代码块。
到此为止,我们很清楚if
后面跟着的是普通的shell命令,那么我们需要测试其他条件怎么办呢?
test
命令提供了在if-then
语句中测试不同条件的途径。如果test
命令中列出的条件成立,test
命令就会退出并返回状态码0。这样if-then
语句就与其他编程语言中的if-then
语句以类似的方式工作了。
test命令格式:
1 | test condition |
condition
是test
命令要测试的一系列参数和值。如果不写这个condition
,test
返回非0,if
语句跳转到else
进行执行。
bash shell提供了一种条件测试方法,无需在if-then
语句中声明test
命令。
1 | if [ condition ] |
这跟我们其他的编程习惯非常接近。建议使用这种方式。
如果使用test
命令,需要记住的是各种条件参数。
数值比较
比较 | 描述 |
---|---|
n1 -eq n2 | (n1)等于(n2) |
n1 -ge n2 | 大于或等于 |
n1 -gt n2 | 大于 |
n1 -le n2 | 小于或等于 |
n1 -lt n2 | 小于 |
n1 -ne n2 | 不等于 |
字符串比较
比较 | 描述 |
---|---|
str1 = str2 | (str1与str2比较)相同 |
str1 != str2 | 不同 |
str1 < str2 | 小 |
str1 > str2 | 大 |
-n str1 | 检查string1的长度非0 |
-z str1 | 检查string1的长度是否为0 |
注意,大于和小于号必须转义;大于和小于顺序和sort命令所采用的不同。
文件比较
比较 | 描述 |
---|---|
-d file | 检查file是否存在并是一个目录 |
-e file | ~是否存在 |
-f file | ~是否存在并是一个文件 |
-r file | ~是否存在并可读 |
-s file | ~是否存在并非空 |
-w file | ~是否存在并可写 |
-x file | ~是否存在并可执行 |
-O file | ~是否存在并属当前用户所有 |
-G file | ~是否存在并且默认组与当前用户相同 |
file1 -nt file2 | 检查file1是否比file2新 |
file1 -ot file2 | 检查file1是否比file2旧 |
if-then
语句允许我们使用布尔逻辑来组合测试。可用
[ condition1] || [ condition2]
双括号
命令格式:
1 | (( expresiion )) |
expression
可以是任意的数学赋值或比较表达式。除了test
命令使用的标准数学运算符,下面列出了一些其他的:
符号 | 描述 | ||
---|---|---|---|
val ++ | 后增 | ||
val – | 后减 | ||
++ val | 先增 | ||
– val | 先减 | ||
! | 逻辑取反 | ||
~ | 位求反 | ||
** | 幂运算 | ||
<< | 左位移 | ||
>> | 右位移 | ||
& | 位布尔和 | ||
\ | 位布尔或 | ||
&& | 逻辑和 | ||
\ | \ | 逻辑或 |
看一个例子:
1 | wsx@wsx-ubuntu:~/script_learn$ cat test23.sh |
双方括号
双方括号命令提供了针对字符串比较的高级特性。命令格式如下:
1 | [[ expression ]] |
双方括号里的expression
使用了test
命令中采用的标准字符串比较。但它提供了test
没有提供的一个特性——模式匹配。
在模式匹配中,可以定义一个正则表达式来匹配字符串值。
1 | wsx@wsx-ubuntu:~/script_learn$ cat test24.sh |
上面一个脚本中,我们使用了双等号。双等号将右边的字符串视为一个模式,并将其应用模式匹配规则。
有了case
命令,就不需要写出所有的elif
语句来不停地检查同一个变量的值了。case
命令会采用列表格式来检查单个变量的多值。
下面是两个脚本实现相同功能进行对比:
if语句:
1 | wsx@wsx-ubuntu:~/script_learn$ cat test25.sh |
case语句:
1 | case variable in |
上面的实例可以用case
语句表示为:
1 | wsx@wsx-ubuntu:~/script_learn$ cat test26.sh |
case
命令会将指定的变量与不同模式进行比较。如果变量和模式是匹配的,那么shell会执行为该模式指定的命令。可以通过竖线操作符在一行中分隔出多个模式。星号会捕获所有与已知模式不匹配的值。注意双分号的使用。
最基本的命令是
if-then
语句;可以拓展
if-then
语句为if-then-else
语句;可以将
if-then-else
语句通过elif
语句连接起来;在脚本中,我们需要测试一种条件而不是命令时,比如数值、字符串内容、文件或目录的状态,
test
命令提供了简单方法;方括号是
test
命令统一的特殊bash命令;双括号使用另一种操作符进行高级数学运算双方括号允许高级字符串模式匹配运算;
case
命令是执行多个if-then-else
命令的简便方式,它会参照一个值列表来检查单个变量的值。
关于结构化命令中循环,将在下次整理的笔记中阐述。
内容
- for循环语句
- until迭代语句使用while语句
- 循环
- 重定向循环的输出
这一节我们来了解如何重复一些过程和命令,也就是循环执行一组命令直到达到了某个特定条件。
基本格式:
1 | for var in list |
也可以
1 | for var in list; do |
分号只用来分隔命令的,让代码更简约。
来个简单例子:
1 | wsx@wsx-ubuntu:~/script_learn$ cat test1 |
这里操作基本和其他语言一致(格式不同),不多讲啦。
在读取列表中的复杂值时,我们可能会遇到问题。比如下面这个例子:
1 | wsx@wsx-ubuntu:~/script_learn$ cat badtest1 |
我们可以看到shell看到了列表值中的单引号尝试使用它们来定义一个单独的数据值。
这里有两种解决办法:
我们将这两种解决办法同时用到上个例子:
1 | wsx@wsx-ubuntu:~/script_learn$ cat test2 |
我们可能明白了for
循环是假定每个值是用空格分隔的,所以当有包含空格的数据时,我们需要用双引号括起来。
通常我们会将列表值存储在一个变量中,然后通过遍历变量的方式遍历了其内容的的列表。
看看怎么完成这个任务:
1 | wsx@wsx-ubuntu:~/script_learn$ cat test3 |
注意,代码中还用了另一个赋值语句向$list
变量包含的已有列表中添加了一个值。这是在已有文本字符串尾部添加文本的一种常用方法。
我们还可以用命令来输出我们需要的列表内容:
1 | wsx@wsx-ubuntu:~/script_learn$ cat test4 |
更改字段分隔符
环境变量IFS
,也叫作字段分隔符。它定义了bash shell用作字段分隔符的一系列字符。默认情况下,bash shell会将空格、制表符和换行符当作字段分隔符。
如果想修改IFS
的值,比如使其只能识别换行符,我们可以将下面这行代码加入脚本:
1 | IFS=$'\n' |
在处理大量脚本时,我们可能只在某一部分使用其他的分隔符,这时候可以先保存原有的IFS
值,然后修改,最后恢复:
1 | IFS.OLD=$IFS |
假如我们要遍历一个文件中用冒号分隔的值:
1 | IFS=: |
假如要指定多个IFS
字符,只要将它们的赋值行串起来:
1 | IFS=$'\n':;" |
这个赋值会将换行符、冒号、分号以及双引号作为字段分隔符。
用通配符读取目录
我们可以用for
命令来自动遍历目录中的文件。进行此操作时,必须在文件名或路径名中使用通配符。它会强制shell使用文件扩展匹配。文件扩展匹配是生成匹配指定通配符的文件名或路径名的过程。
我拿我的一个目录来尝试一下:
1 | wsx@wsx-ubuntu:~/script_learn$ cat test5 |
注意:第一个方括号之后和第二个方括号之前必须加上一个空格,否则会报错。
在Linux中,目录名和文件名中包含空格是合法的,所以将$file
变量用双引号圈起来。当然,大家尽量不要让文件或目录包含空格,不然很容易出问题(命令会把空格当做文件的分隔符)。
C语言风格的for
命令看起来如下:
1 | for (( a = 1; a < 10; a++ )) |
值得注意的是,这里有些部分没有遵循bash shell标准的for
命令:
expr
命令格式。在使用这种格式时要小心,不同的格式不注意就会出错。
下面举个例子:
1 | wsx@wsx-ubuntu:~/script_learn$ cat test6 |
while
命令的格式为:1
2
3
4while test command
do
other commands
done
while
命令某种意义上是if-then
语句和for
循环的混杂体。注意,这里while
后面接的也是命令。while
命令允许定义一个要测试的命令,然后循环执行一组命令,只要定义的测试命令返回的是退出状态码是0(类似一般语言中 的TRUE)。直到非0时退出循环。
while
命令中定义的test command
和if-then
语句中的格式一模一样。可以使用任何普通的bash shell命令,或者用test
命令进行条件测试,比如测试变量值。
最常见的用法是用方括号来检查循环命令中用到的shell
变量的值。
1 | wangsx@SC-201708020022:~/tmp$ cat test |
使用多个测试命令while
命令允许我们在while
语句行中定义多个测试命令。只有最后一个测试命令的退出状态码会被用来决定什么时候结束循环。
比如while echo $var1 [ $var1 -ge 0 ]
检测的就是后面方括号命令的退出状态码。
until
命令和while
命令工作的方式完全相反。只有测试命令的退出状态码不为0,bash shell才会执行循环中列出的命令。一旦测试命令返回了退出状态码0,循环就结束了。
1 | until test command |
一个例子:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17wangsx@SC-201708020022:~/tmp$ cat test12
!/bin/bash
using the until command
var1=100
until [ $var1 -eq 0 ]
do
echo $var1
var1=$[ $var1 - 25 ]
done
wangsx@SC-201708020022:~/tmp$ ./test12
100
75
50
25
同样地,在until
命令中放入多个测试命令时也要注意(类似while
)。
在循环语句内使用任意类型的命令,包括其他循环命令,叫做嵌套循环。因为是在迭代中迭代,需要注意变量的使用以及程序的效率问题。
下面举一个for
循环嵌套for
循环的例子:
1 | wangsx@SC-201708020022:~/tmp$ cat test14 |
shell能够自动识别匹配的do
和done
字符。这种模式很常见,比如通常的小括号((
与)
)、中括号、花括号匹配等等。它们的本质都是字符匹配。
在混用循环命令时也一样,比如在while
循环中内嵌一个for
循环:
1 | wangsx@SC-201708020022:~/tmp$ cat test15 |
如果想要挑战脑力,可以混用until
和while
循环。
1 | wangsx@SC-201708020022:~/tmp$ cat test16 |
外部的until
循环以值3开始,并继续执行到值等于0。内部while
循环以值1开始一直执行,只要值小于5。需要注意循环条件的设置,我跑的几次都没写完整,然后无限循环只好重开终端。
之前的学的命令已经可以让我们写循环程序了,设定好以后等待命令开始执行和等待循环结束。但是很多情况下,在循环中我们设定的某个(多个)变量达到某种条件时,我们就想要停止循环,然后运行循环下面的命令。这时候我们需要用到break
和continue
命令来帮我们控制住循环。
这两个命令在其他语言中基本都时关键字,特别是C
,用法差不多。我也就不具体介绍了,只点出它们的功能。
break
在shell执行break命令时,它会尝试跳出当前正在执行的循环。
在处理多个循环时,break命令会自动终止你所在的最内层循环。
break命令接受单个命令行参数值:
break n
其中n制订了要跳出的循环层级(层数)
continue
continue命令可以提前终止某次循环的命令,但并不会完全终止整个循环。可以在循环内部设置shell不执行命令的条件。
也就是说使用continue命令时,它会自动跳过本次循环中接下来的运行步骤,跳转到下一次循环。但注意不是跳出,跳出时break的功能。
同样的可以使用continue n n制定要继续执行哪一级循环
在shell脚本中,我们可以对循环的输出使用管道或进行重定向。这是通过在done
命令之后添加一个处理命令来实现的。
1 | wangsx@SC-201708020022:~/tmp$ cat test |
shell将for
命令的结果重定向到文件output.txt
中,而不是显示在屏幕上。
下面两个例子演示如何用简单循环来处理数据。
查找可执行文件
Linux运行程序时通过环境变量$PATH
提供的目录搜索可执行文件。如果徒手找的话,比较费时间,我们可以写个脚本来搞定它。
1 | wangsx@SC-201708020022:~$ cat test25 |
先设定IFS
分隔符以便于能正确分隔目录,然后将目录存放在$folder
中,用for
循环来迭代特定的目录中所有文件,然后用if-then
命令检查文件的可执行权限。
Linux有一个tree
工具,非常方便输出目录结构,推荐使用下。
创建多个用户账号
如果你是管理员,需要创建大量账号时。不必每次都有useradd
命令添加用户。将用户信息存放在指定文件,然后用脚本进行处理就可以了。
用户信息的格式如下:
1 | userid, user name |
第一个是你为用户选择的id,第二个是用户的全名。这是csv
文件格式。
为了能够读取它,我们使用以下命令:
1 | while IFS=',' read -r userid name |
read
命令会自动获取.csv
文本文件的下一行内容,所以不用再写一个循环来处理。当read
命令返回FALSE
时(也就是读完了),while
命令就会退出。
为了把数据从文件导向while
命令,只要再while
命令尾部加一个重定向符号。
处理过程写成脚本如下:
1 | !/bin/bash |
内容
- 使用多个命令
- 创建脚本文件
- 显示消息
- 使用变量
- 输入输出重定向
- 管道
- 数学运算
- 退出脚本
如果多个命令一起使用,可以放在一行并用分号分隔。
1 | wsx@wsx-ubuntu:~$ date; who |
在创建脚本文件时,必须在文件的第一行指定要使用的shell,格式为:
1 |
脚本文件的第一行中#
后的惊叹号会告诉shell使用哪个shell来运行脚本(如果是其他编码语言脚本,像python,第一行类似)。
其他地方的#
用作注释行。
添加名为test1
的脚本文件,内容为:
1 |
|
现在运行脚本,结果会是:
1 | wsxubuntu:~/script_learn$ test1 - |
我们现在需要做的是让bash shell能够找到我们的脚本文件。shell会通过PATH
环境变量来查找命令,我们可以看看:
1 | wsx@wsx-ubuntu:~/script_learn$ echo $PATH |
很显然,我们的文件没有在这些目录范围内。要让shell找到test1脚本,我们可以采取以下两种做法之一:
PATH
环境变量中;第二种方法比较简单,我们在这里试试:
1 | wsx@wsx-ubuntu:~/script_learn$ ./test1 |
在echo
命令后面加上一个字符串,就能显示出这个文本字符串。这种方式可以添加自己的文本消息来告诉脚本用户脚本正在做什么。
1 | wsx@wsx-ubuntu:~/script_learn$ echo This is a test |
如果文本本身带有字符串,我们需要用单引号或双引号来划定文本字符串。
1 | wsx@wsx-ubuntu:~/script_learn$ echo "Let's see if this'll work" |
我们修改下之前的test1文件,增加消息显示:
1 | !/bin/bash |
运行:
1 | wsx@wsx-ubuntu:~/script_learn$ ./test1 |
如果想把文本字符串和命令输出显示在同一行中,可以用echo
语句的-n
参数。需要在字符串的两侧加上引号,并且保证字符串尾部有一个空格(不然字符串和命令输出就粘连到一起了)。
1 | !/bin/bash |
变量允许我们临时性地将信息存储在shell脚本中,以便和脚本中的其他命令一起使用。
环境变量
shell维护着一组环境变量,用来记录特定的系统信息。比如系统的名称、登录到系统上的用户名、用户的系统ID(也称为UID)、用户默认主目录以及shell查找程序的搜索路径。
使用set
命令显示一份完整的当前环境变量列表。env
与printenv
命令都可以显示全局变量。(这些命令输出结果比较多,不展示了。之前关于环境变量的笔记有比较详细的描述。)
在环境变量名称之前加上美元符可以使用这些环境变量。
1 | wsx@wsx-ubuntu:~/script_learn$ cat test2 |
可以想象的到,如果我们想要使用实际的美元符而不是引用变量,肯定会出问题。这时候我们需要在美元符前面加上\
进行转义,以显示美元符本身。
用户变量
使用等号将值赋给用户变量。注意,在变量、等号和值之间不能出现空格!这个是初学者常见的一个问题,本人也非常不太适应这个。因为在其他语言中不区分等号两边的空格,相信接触过其他脚本的朋友们肯定有习惯打空格使代码美观的,这在bash shell中是万万行不通滴。
一个使用的例子:
1 | wsx@wsx-ubuntu:~/script_learn$ cat test3 |
变量每次被引用时,都会输出当前赋给它的值。重要的是要记住,引用一个变量值时需要使用美元符,而引用变量来对其进行赋值时则不需要使用美元符。
shell脚本最有用的特性之一就是可以从命令输出中提取信息,并将其赋给变量。
有两种方法可以将命令输出赋给变量:
1 | 要么用一对反引号把整个命令行命令围起来: |
下面是一个例子,在脚本中通过命令替换获得当前日期并用它来生成唯一文件名:
1 | wsx@wsx-ubuntu:~/script_learn$ cat test4 |
通过几个操作符进行重定向,我们可以将命令的结果输出到另外的位置(文件)。当然,重定向可以用于输入。
输出重定向
最基本的操作符是>
。比如我们想要输出命令结果到一个指定文件:
1 | wsx@wsx-ubuntu:~/script_learn$ date > test6 |
如果想要将命令的输出追加到已有文件中,需要用双大于号(>>)来追加数据。
输入重定向
输入重定向和输出重定向正好相反。输入重定向将文件的内容重定向到命令,而非将命令的输出重定向到文件。
使用的符号是小于号(<)。
一种简单的记忆方法是:在命令行上,命令总是在左侧,而重定向符号“指向”数据流动的方向。小于号说明数据正在从输入文件流向命令。
比如用wc命令检查文本的行数、词数和字节数。
1 | wsx@wsx-ubuntu:~/script_learn$ wc < test6 |
另一种输入重定向的方法是内联输入重定向。它无需使用文件进行重定向,只需要在命令行中指定用于输入重定向的数据即可。它使用的符号是远小于号(<<),除了这个符号,我们还需要指定一个文本标记用来划分输入数据的开始和结尾。任何字符串都可以作为文本标记,但在数据的开始和结尾文本标记必须一致。
1 | wsx@wsx-ubuntu:~/script_learn$ wc << EOF |
它的形式为:
1 | command << marker |
有时候需要将一个命令的输出作为另一个命令的输入。通过|
符号分隔命令即可实现管道。
比如我想查看某个文件(test1)的前两行并进行排序,操作如下:
1 | wsx@wsx-ubuntu:~/script_learn$ cat test1 |
管道的强大之处在于可以根据自己的需求灵活地组合和使用各种linux命令工具。这里只是一个简单的例子,要熟练掌握少不了平时多多研究和练习。
对shell脚本来说,执行数学运算非常麻烦。有两种实现方式。
expr命令
expr
命令允许在命令行上处理数学表达式,但是特别笨拙。(Bourne shell中)
1 | wsx@wsx-ubuntu:~/script_learn$ exrpr 1 + 5 |
看到没有,那算了。它基本涉及的操作跟我们使用的其他语言是一致的。但是有些问题需要处理,像*
是通配符,在运算是是做乘号处理的,需要进行转义。
使用方括号
bash shell提供了一种更简单的方法来执行数学表达式。在bash中,在将一个数学运算结果赋给某个变量时,可以用美元符和方括号($[operator])将数学表达式围起来。
1 | wsx@wsx-ubuntu:~/script_learn$ var1=$[1+5] |
这种方式不仅方便,而且因为在方括号内,不会让shell误解乘号或其他符号。
但bash shell计算有一个主要限制:它只支持整数运算!
浮点解决方案
最常见的方案是用内建的bash计算器。它实际上是一门编程语言,它允许在命令行中输入浮点表达式,然后解释并计算该表达式,最后返回结果。bash计算器能够识别:
1 | wsx@wsx-ubuntu:~/script_learn$ bc |
在脚本中使用bc
可以用命令替换运行bc命令,并将输出赋给一个变量。基本格式如下:
1 | variable=$(echo "options; expression" | bc) |
options设置变量,expression参数定义了通过bc执行的数学表达式。
看一个简单实例:
1 | wsx@wsx-ubuntu:~/script_learn$ cat test9 |
这个例子将scale
变量设置为四位小数,并在expression
部分指定了特定的运算。
这个方法适用于较短的运算,但有时我们会涉及更多的数字。如果需要进行大量运算,在一个命令行中列出多个表达式就会有点麻烦。
这里有一个解决方法:使用内联输入重定向,将一个文件重定向到bc命令来处理。格式为:
1 | variable=$(bc << EOF |
EOF
文本字符串标识了内联重定向数据的起始。注意,仍然需要命令替换符号将bc命令的输出赋给变量。
下面是一个例子:
1 | wsx@wsx-ubuntu:~/script_learn$ cat test10 |
在普通的shell脚本中,数字默认当做字符串处理。这也是为什么我们脚本处理计算麻烦和我们需要特定的工具和方法来进行处理。一定要注意区分。
前面运行的脚本都是命令执行完成,脚本自动结束。其实我们可以用更为优雅的方式告诉shell命令运行完成,因为每个命令都使用退出状态码(exit status),它是一个0-255的整数值,我们可以捕获这个值并在脚本中使用。
Linux提供了一个专门的变量$?
来保存上个已执行命令的退出状态码。
1 | wsx@wsx-ubuntu:~/script_learn$ date |
按照惯例,一个成功结束的命令的退出状态码是0。如果有错误,则显示一个正数值。
Linux错误退出状态码没有什么标准,但有一些参考:
状态码 | 描述 |
---|---|
0 | 命令成功结束 |
1 | 一般性未知错误 |
2 | 不适合的shell命令 |
126 | 命令不可执行 |
127 | 没找到命令 |
128 | 无效的退出参数 |
128+x | 与Linux信号x相关的严重错误 |
130 | 通过Ctrl+C终止的命令 |
255 | 正常范围之外的退出状态码 |
1 | wsx@wsx-ubuntu:~/script_learn$ asfg |
exit命令
默认,shell脚本会以脚本最后的一个命令的退出状态码退出。
但是我们可以改变这种默认行为,返回自己的退出状态码。exit命令允许在脚本结束时指定一个状态退出码。
1 | wsx@wsx-ubuntu:~/script_learn$ cat test13 |
注意最大255,如果大于它,得到的是求模的结果(余数)。