重定向
- 有些时候我们需要保存某个命令的输出而不仅仅只是让它显示在标准输出显示器上。bash shell提供了几个操作符,可以将命令的输出重定向到另一个位置(比如文件等等)
- 重定向不仅可以用于输入,还可以用于输出,将文件重定向到命令输入
输出重定向
覆盖
- 最基本的重定向就是将命令的输出保存到一个文件中。bash shell使用
大于号操作符(>)
来实现此操作的,命令格式如下:
command > outputfile...
- 将命令(command)的输出保存到指定的输出文件中(outputfile),如下:
date > test6
- 重定向操作符创建了一个文件test6(通过默认的umask设置权限),并将命令date的输出重定向到test6这个指定的输出文件中
- 如果指定的输出文件存在,重定向操作符会使用新的文件覆盖已有的文件,如下:
who > test6
- 上述的test6文件内容已经不是第一次date命令的输出了,而是第二次who命令的输出
追加
- 但是有时我们并不希望覆盖掉文件原有的内容,而只是想将当前命令的输出追加到已有的文件中,比如正在创建一个记录系统的某个服务的操作日志文件。在此情况下,可以使用
双大于号(>>)
来进行追加数据,如下:
date >> test6
- 如上所示,特定输出文件test6中不仅包含更早的who命令的输出数据,还包括刚刚操作date命令的输出
输入重定向
- 输入重定向和上述的输出重定向正好相反。输入重定向将文件的内容重定向到命令行,而非将命令行的输出重定向到文件
- 输入重定向操作符号小于号(<),如下:
command < inputfile...
- 可以将两种重定向对比进行记忆:在命令行上,命令总是在第一个(即为左侧),而重定向操作符“指向”数据流动的方向,小于号(<)就代表数据就是从输入文件流向当前命令,如下:
- 举例:wc命令使用输入重定向
wc < test6
- wc命令可以对数据文本进行计数,默认情况,此命令会输出3个值
- 文本的行数
- 文本的词数
- 文本的字节数
- 通过将test6文本文件重定向到wc命令,得到文本文件中的行、词和字节的计数,与
ls -l
命令输出对比查看
内联输入重定向
- 与文件输入重定向不同,还有一种输入重定向的方法,被称为内联输入重定向(inline input redirection),这种方法并不需要使用文件进行重定向,只需要在命令行中指定用于输入重定向的数据即可,乍看起来,怪,很怪!!但是有的时候确实需要用到这种方式
- 内联输入重定向操作符为远小于号(<<),除了这个符号,你还必须指定一个文本标记来划分输入数据的开始与结尾,任何字符串都可以作为文本标记,但是数据的开始和结尾文本标记必须相同!!,格式如下:
command << marker
data
....
....
marker
- 如下测试:
wc << WY
....
....
> WY
- 在命令行使用上使用内联输入重定向时,shell会用PS2环境变量中定义的次提示符(>),来提示输入数据,次提示符会持续提示,以获取更多用户输入的数据,直到你输入了作为文本标记的那个特殊的字符串(上述为:WY),wc命令才会对内联输入重定向提供的数据进行统计
管道
- 有时,我们需要将一个命令的数据作为另一个命令的输入,可以使用上述的重定向知识来实现,只是显得操作繁琐,如下:
pip list > pip.list
sort < pip.list
- pip命令是python的包管理器,使用pip list命令可以展示当前已安装的python包目录列表,但是这个列表并不会遵循某个特定的顺序,如果你要查找某个你已经安装的包的版本,直接在输出查找显得不是很方便
- 通过标准输出重定向,pip命令的输出被重定向到了文件 pip.list。命令完成后,pip.list保存着系统中所有已安装的python软件包列表。接下来,输入重定向将pip.list文件的内容发送给sort命令, 该命令按字母顺序对python软件包名称进行排序,然后展示
- 上述方法确实是管用的,但是操作步骤仍然比较繁琐,我们其实是不需要将命令输出重定向到文件中,然后再将此文件输入重定向到另一个命令,可以直接将命令的输出重定向到另一个命令,这个过程叫做管道连接(piping)
- 和命令替换所使用的反引号(`)一样,管道符号在shell编程之外是很少用到的,在美式键盘上,它通常和反斜线(\)位于同一个键。管道符(|)被放在命令之间,将一个命令的输出重定向到另一个命令中,如下:
command | command
- 由管道串起的两个命令不是依次执行。Linux系统实际上会同时运行这两个命令,在系统内部将它们连接起来。在第一个命令产生输出的同时,输出会被立即送给第二个命令。数据传输不会用到任何中间文件或缓冲区
- 现在可以将上述的重定向操作修改为管道操作,如下:
pip list | sort
- 由于管道操作是实时运行的,所以只要pip命令一输出数据,sort命令就会立即对其进行排序。等到pip命令输出完数据,sort命令就已经将数据排好序并展示在标准输出显示器上
- 还可以在一条命令中使用任意多条管道。可以持续地将命令的输出通过管道传给其他命令来细化操作
- 可以将上述已经排序好的输出,使用文本分页命令(例如less或more)来强制将输出按屏显示,如下:
pip list | sort | more
- 这行命令序列先执行pip 命令,将它的输出通过管道传给sort命令,然后再将sort排序好的输出通过管道传给more命令显示,在显示满一个屏幕后停止,使得我们可以在继续处理之前来浏览屏幕上已经输出的信息,如上图所示
- 我们还可以搭配管道与重定向,将排序好的数据保存到文件中,如下:
pip list | sort > sort.list
more sort.list
- 时下,管道最流行的用法之一是将命令产生的大量输出通过管道传递给more或less命令,这对ls命令是尤为常见的,如下:
ls -l | more
- ls -l 会输出当前目录中的所有文件的长列表,对于包含大量文件的目录来说,这个展示到屏幕的列表会相当长,可以通过将输出管道连接到more命令,强制将输出展示到一个屏幕后停止下来
执行数学运算
对于任何一个编程语言来说都有着一个重要特性就是操作数字,进行各种运算的能力,但是,对于shell脚本来说,这个处理过程比较麻烦,在shell脚本中有两个途径可以进行数学运算
expr命令
- 最开始,Bourne shell提供了一个特别的命令用来处理数学表达式。expr命令允许在命令行上处理数学表达式,但是特别笨拙,如下:
expr 6 + 6
- 上述的数字与符号之间必须加空格,否则会出现奇怪的错误!!!如下:
- expr命令还可以识别少数的数字与字符串操作符,如下表
操作符 | 详细描述 |
---|---|
ARG1 | ARG2 | 如果ARG1既不是null也不是零值,返回ARG1;否则返回ARG2 |
ARG1 & ARG2 | 如果没有参数是null或零值,返回ARG1;否则返回0 |
ARG1 < ARG2 | 如果ARG1小于ARG2,返回1;否则返回0 |
ARG1 <= ARG2 | 如果ARG1小于或等于ARG2,返回1;否则返回0 |
ARG1 = ARG2 | 如果ARG1等于ARG2,返回1;否则返回0 |
ARG1 != ARG2 | 如果ARG1不等于ARG2,返回1;否则返回0 |
ARG1 >= ARG2 | 如果ARG1大于或等于ARG2,返回1;否则返回0 |
ARG1 > ARG2 | 如果ARG1大于ARG2,返回1;否则返回0 |
ARG1 + ARG2 | 返回ARG1和ARG2的算术运算和 |
ARG1 - ARG2 | 返回ARG1和ARG2的算术运算差 |
ARG1 * ARG2 | 返回ARG1和ARG2的算术乘积 |
ARG1 / ARG2 | 返回ARG1被ARG2除的算术商 |
ARG1 % ARG2 | 返回ARG1被ARG2除的算术余数 |
STRING : REGEXP | 如果REGEXP匹配到了STRING中的某个模式,返回该模式匹配 |
match STRING REGEXP | 如果REGEXP匹配到了STRING中的某个模式,返回该模式匹配 |
substr STRING POS LENGTH | 返回起始位置为POS(从1开始计数)、长度为LENGTH个字符的子字符串 |
index STRING CHARS | 返回在STRING中找到CHARS字符串的位置;否则,返回0 |
length STRING | 返回字符串STRING的数值长度 |
+ TOKEN | 将TOKEN解释成字符串,即使是个关键字 |
(EXPRESSION) | 返回EXPRESSION的值 |
- 尽管标准操作符在expr命令中工作得很好,但在脚本或命令行上使用它们时仍有问题出现。许多expr命令操作符在shell中另有含义(比如星号)。当它们出现在在expr命令中时,会得到一些奇奇怪怪的结果,如下:
-要解决上述问题,对于那些容易被shell错误解释的字符,在它们传入expr命令之前,需要使用shell的转义字符(反斜线)将其标出来,如下:
- 在命令行上使用expr已经如此麻烦了,在shell脚本中使用expr命令却更为复杂!!
#!/bin/bash
# 使用expr进行计算
val1=10
val2=60
val3=$(expr $val2 / $val1)
echo 计算结果为:$val3
- 上述,要将一个数学算式的结果赋给一个变量,需要使用命令替换来获取expr命令的输出,还是比较麻烦的,但是bash shell针对处理数学运算符有所改进
使用方括号
- bash shell为了保持跟Bourne shell的兼容而包含了expr命令,但它同样也提供了一种更简单的方法来执行数学表达式。在bash shell中,在将一个数学运算结果赋给某个变量时,可以用美元符和方括号($[ operation ])将数学表达式围起来,如下:
- 用方括号执行shell数学运算比用expr命令方便很多,在shell脚本中也可以实现,如下:
#!/bin/bash
var1=10
var2=50
var3=2
var4=$[$var1 * ($var2 + $var3)]
echo 计算结果为:$var4
- 同样,注意在使用方括号来计算公式时,不用担心shell会误解乘号或其他符号。shell知道它不是通配符,因为它在方括号内
- 在bash shell脚本中进行算术运算还会有一个主要的限制,如下:
#!/bin/bash
var1=100
var2=45
var3=$[$var1 / $var2]
echo 计算结果为:$var3
- bash shell数学运算符只支持整数运算。若要进行任何实际的数学计算,还需要使用其它的操作
浮点数解决方案
有几种解决方案能够克服bash中数学运算的整数限制。最常见的方案是用内建的bash计算器,叫作bc
bc的基本用法
- bash计算器实际上是一种编程语言,它允许在命令行中输入浮点表达式,然后解释并计算该表达式,最后返回结果。bash计算器能够识别:
- 数字(整数和浮点数)
- 变量(简单变量和数组)
- 注释(以#或C语言中的/* */开始的行)
- 表达式
- 编程语句(例如if-then语句)
- 函数
- 可以在命令行中输入bc命令来访问bash计算器,如下:
- 这个例子输入了表达式52.1 * 10,bash计算器返回了计算结果。随后每个输入到计算器的表达式都会被求值并显示出结果。要退出bash计算器,你必须输入quit退出命令
- 浮点运算保留的小数位数是由内建变量scale控制的。必须将这个值设置为你希望在计算结果中保留的小数位数,否则可能无法得到期望的结果,如下:
- scale变量的默认值是0。
注意:scale变量无法限制浮点数的乘法运算
,在scale值被设置前,bash计算器的计算结果不包含小数位。在将其值设置成4后,bash计算器显示的结果包含四位小数。-q 选项可以不显示bash计算器冗长的欢迎信息 - 除了普通的数字计算,bc计算器还支持变量,如下:
- 变量一旦被定义,就可以在整个bash计算器会话中使用该变量了。print语句允许你打印展示变量和数字
在shell脚本中使用bc
- 你可能想问如何在shell脚本中使用bc来处理浮点运算的,还记得命令替换吗?是的,可以用命令替换运行bc命令,并将输出赋给一个变量。如下格式:
var=$(echo "options1; options2;... expression" | bc)
- options参数允许你设置多个变量。如果你需要不止一个变量,可以用分号将其分开。expression参数定义了通过bc执行的数学表达式,如下:
#!/bin/bash
var1=$(echo "scale=4; w=3.14; y=66; w / y" | bc)
echo 计算结果为:$var1
- 上述例子,将scale变量设置成了四位小数,设置了多个options参数,并在expression部分指定了特定的运算
- 还可以使用脚本中定义的变量,它们都可以用在expression部分,然后发送给bc命令,如下:
#!/bin/bash
var1=22
var2=3.14159265354
var3=$(echo "scale=4; $var1 * $var1" | bc)
var4=$(echo "scale=4; $var2 * $var3" | bc)
echo 圆的面积为:$var4
- 上述方法适用于较短的运算,但有时你会涉及更多的数字。当我们需要进行大量运算,在一个命令行中列出多个表达式就会有点麻烦
- 还有一个方法可以解决这个问题。bc命令能识别输入重定向,允许你将一个文件重定向到bc命令来处理。但这同样会令人头疼,因为还得将表达式存放到文件中
- 最好的办法是使用内联输入重定向,它允许你直接在命令行中重定向数据。在shell脚本中,你可以将输出赋给一个变量,如下格式:
var=$(bc << EOF
option1
option2
.....
statements
expressions
EOF
)
- EOF文本字符串标识了内联重定向数据的起止。记住,仍然需要命令替换符号将bc命令的输出赋给变量,如下:
#!/bin/bash
var1=11.1
var2=22.2
var3=33.3
var4=44.4
var5=$(bc << EOF
scale=4
a1=($var1 + $var2)
b1=($var3 * $var4)
a1 + b1
EOF
)
echo 计算结果为:$var5
- 将选项和表达式放在shell脚本的不同行中可以让处理过程变得更清晰,提高易读性。EOF字符串标识了重定向给bc命令的数据的起止。当然,必须用命令替换符号标识出用来给变量赋值的命令
- 可能注意到,在上述例子中,我们可以在bash计算器中赋值给变量。这一点很重要:在bash计算器中创建的变量只在bash计算器中有效,不能在shell脚本中使用
退出脚本
- 到目前为止所有的示例脚本中,shell脚本都是突然停下来的。运行完最后一条命令时,脚本就结束了。当然还有另外一种更优雅的方法可以为脚本划上一个完美句号
- shell中运行的每个命令都使用退出状态码(exit status)告诉shell它已经运行完毕。退出状态码是一个0~255的整数值,在命令结束运行时由命令传给shell。可以捕获这个值并在脚本中使用
查看退出状态码
- Linux提供了一个专门的
变量\$?
来保存上个已执行命令的退出状态码。对于需要进行检查的命令,必须在其运行完毕后立刻查看或使用$?变量。它的值会变成由shell所执行的最后一条命令的退出状态码,如下:
- 按照惯例,一个成功结束的命令的退出状态码是0。如果一个命令结束时有错误,退出状态码应该是一个正数值,如下:
- 无效命令会返回一个退出状态码127。Linux错误退出状态码没有固定标准,但有一些可用的参考,如下表:
状态码 | 描 述 |
---|---|
0 | 命令成功结束 |
1 | 一般性未知错误 |
2 | 不适合的shell命令 |
126 | 命令不可执行 |
127 | 没找到命令 |
128 | 无效的退出参数 |
128+x | 与Linux信号x相关的严重错误 |
130 | 通过Ctrl+C终止的命令 |
255 | 正常范围之外的退出状态码 |
- 退出状态码126表明用户没有执行命令的权限,如下:
- 还有一个常见的错误就是给了某个命令无效的参数,这会产生一般性的退出状态码1,表明在命令中发生了未知错误,如下:
exit命令
- 默认情况下,shell脚本会以脚本中的最后一个命令的退出状态码退出,如下:
- 可以改变这种默认行为,返回自定义的退出状态码。exit命令允许你在脚本结束时指定一个退出状态码,如下:
#!/bin/bash
var1=22
var2=3.14159265354
var3=$(echo "scale=4; $var1 * $var1" | bc)
var4=$(echo "scale=4; $var2 * $var3" | bc)
echo 圆的面积为:$var4
exit 5
- 当查看脚本的退出码时,会得到作为参数传给exit命令的值,如下
- 甚至你还可以在exit命令的参数中使用变量,如下:
#!/bin/bash
var1=44
var2=22
exit $[$var1 + $var2]
- 还要注意一点,退出状态码最大只能是255,当我们自定义的退出状态码大于255时,会发生什么状况呢??如下:
- 退出状态码的区间为0~255。shell通过模运算得到这个结果。一个值的模就是被除后的余数。最终的结果是指定的数值除以256后得到的余数。在上述的例子中,指定的值是521(返回值),余数是9,因此这个余数就成了最终的状态退出码
- 在后续的学习中我们会了解如何用if-then语句来检查某个命令返回的错误状态,以便知道命令是否成功执行