Linux shell 整理之 复合命令行篇(四)


前置参考文章

Linux shell 整理之 基本概念篇(一)

Linux shell 整理之 基本概念篇(二)

Linux shell 整理之 用户权限篇(三)

开篇

如果读者看过了前三篇文章很容易发现,在我们使用命令的时候(不管是系统篇,权限篇),都是以单个命令执行程序的,而这篇是以多个命令组合、命令前后关联的角度来看待问题的。因此要牢牢抓住如下特性 异常重定向管道
。才能做到游刃有余。

在shell编程中,第一个特性,可能大家都没有注意到,shell编程中存在异常捕获的概念吗?有,当然有,但是与一般高级语言的异常处理不太一样。

前置命令和后置命令

本人特意指出这两个概念有一个核心目的,就是方便本文后面的概念的描述。否则太多的“前面的命令”,“后面的命令”,这样的字眼出现,显得有些笨拙冗余。

在复合命令中,在复合命令操作符(&&,||,<,>,<<,>>) 左端(前面)的命令叫做前置命令后置命令就是复合命令操作符右端(后面)的命令。

异常(非零状态码)

状态码

首先要了解异常,必须要了解状态码,即命令最终的状态信息,一般正常的话为0

表11-2 Linux退出状态码
状 态 码 描 述
0 命令成功结束
1 一般性未知错误
2 不适合的shell命令
126 命令不可执行
127 没找到命令
128 无效的退出参数
128+x 与Linux信号x相关的严重错误
130 通过Ctrl+C终止的命令
255 正常范围之外的退出状态码

在脚本语言中,异常定义的公式如下

  $ exit num ; # 其中 (num!=0)

即在脚本中任一段命令中抛出的状态码num 不等于 0 ,表示了抛出了异常,而不同于一般程序语言中的异常

特点(与高级语言对比)

  • 不能捕获,也就是没有try…catch这样的语法,
  • 一旦出现异常(exit num) 程序依然能够正确执行出错命令之后的命令

如果指定命令异常之后不想执行之后的逻辑命令,请使用特殊变量 ??** 代表距离 **? 命令所处位置的最近一次逻辑可执行命令执行之后的状态码,默认情况下,正常执行完成都会抛出0(代表正常退出)

#!/bin/bash 
ldd; // ldd: missing file arguments
if [ $? -eq 0 ];then
        echo "正常退出";
        
else
        echo "异常退出"; // 执行该分支
        exit 2; // 退出之后结束代码
fi
echo "asads" // 不执行

该代码中 ldd没有指定的命令,所以会抛出一个异常,因此会走分支2。在这里顺便提一句,在/bin/bash登陆shell的模式下,支持整数运算不支持浮点值运算,这是一个非常巨大的缺陷,因此为了支持浮点运算,可以使用另一个非常高级的shell (zsh shell 佐罗/瑞士军刀).

在 zsh 中 扩展了bash shell 的很多功能,其最大的特性就是提供模块加载的功能,以内建命令的执行速度,执行自己扩展的功能。有兴趣的同学可以根据自己需要安装 zsh ubuntu zsh 官网

使用复合命令连接符(逻辑判断符)(&& 或则 ||)

可以使用 && ,使得在前置命令成功执行的情况下可以继续执行 && 的后置命令

原理也是简单,就是在判断 && 前置命令执行退出状态码为0(正常退出)的情况下能够继续执行后置命令。

可以使用 || ,作用于前置命令退出状态码非零的情况下,能够执行后置命令,当然默认shell执行环境下,不管前置命令是否正常退出,都会执行后置命令,但是 || 只有前后命令失败的情况下,才能执行后置命令

#!/bin/bash
ldd && echo "成功执行"
ls || echo "ls执行失败"

都会不执行后面的echo 命令

输入输出重定向符

< > << >>

  1. > 覆盖写入,++输出重定向++

  2. >> 追加写入 ++内联输出重定向++

  3. < 回写,++输入重定向++

  4. << EOF(前置字符串可以为任一字符串) … EOF(必须更前置字符串一致) ++内联输入重定向++

  5. >& 临时重定向 代表这前置输出被临时缓存到后置输出中,效果类似于>,不同的是,会清空前置输出,后置最后导流到后置输出,这个更像一个 ++管道++。也有人叫做 合并输出重定向

  6. exec 永久重定向

a) 输出重定向 与 内联输出重定向的区别

homewell:~/shell1$ echo 1 > tail.logs 
homewell:~/shell1$ cat tail.logs 
    1
homewell:~/shell1$ echo 2 > tail.logs 
homewell:~/shell1$ cat tail.logs 
    2
homewell:~/shell1$ echo 3 >> tail.logs 
homewell:~/shell1$ cat tail.logs 
    2
    3

b) ++输入重定向++++内联输入重定向++

其实,没有必要记住哪个符号是输入,哪个符号是输出,而是只要理解箭头地方向就是数据流动的方向,那么时间久了,也会自然而然想给这个方向做个定义,那么就会很自然地我们会 ++以前置命令作为主轴++,只要 ++流入++ 前置命令地方向就叫做 ++输入重定向++,从前置命令 ++流出++ 并向后置命令传入地就叫做 ++输出重定向++

程序接收输入

扩展 标准输入(0),标准输出(1),标准错误(2)

记忆技巧(性)把女人比作0,男人比作1,这样输入和输出的方向就很容易记了

在实际工作中,数据的输入和输出往往是经常使用的,比如日志输入到文件中,或者输入到控制台上。或者更高级一些,我们想把错误日志输出到控制台,而正常的日志输出到文件中(数据的分流,是不是很像过滤操作,亦或是导流)

文件描述符 缩写 描述
0 STDIN 标准输入
1 STDOUT 标准输出
2 STDERR 标准错误

在linux系统中,内核把所有实例或者对象当作文件来处理

奇怪的现象

homewell:~/shell1$ ls -al badfile > test3
    ls: cannot access 'badfile': No such file or directory

homewell:~/shell1$ cat test3

homewell:~/shell1$ 

错误信息只输出到了控制终端显示,并没有输出到文件中,这是一个很让人疑惑的地方。其实shell对错误信息的处理与普通输出是分开处理的。默认情况下也就是2号文件描述符与1号文件描述符默认指向的位置是一样的,都是发送到控制终端界面上的。然而2号描述符并不会随着重定向符(>)的方向而改变。

改变STDERR重定向到文件 (2>)

2> 字面含义,把2号文件描述符(标准错误信息)输出到后置命令中(文件名称。可以不存在)

homewell:~/shell1$ ls -al badfile 2> test3

homewell:~/shell1$ cat test3 
    ls: cannot access 'badfile': No such file or directory

同时把标准输出(1)与标准错误(2)导向同一个文件

1> 代表1号文件描述符所处的默认位置(控制终端)重新导向(覆盖)新地址
1>> 代表1号文件描述符所处的默认位置(控制终端)重新导向(追加)新地址

homewell:~/shell1$ ls -al test badtest test2 2> test5 1>> test5

homewell:~/shell1$ cat test5
    ls: cannot access 'test': No such file or directory
    ls: cannot access 'badtest': No such file or directory
    test2:
    total 8
    drwxrwx--- 2 homewell homewell 4096 10月  9 09:35 .
    drwxrwx--- 4 homewell homewell 4096 10月  9 09:38 ..

简写

(&> test6) == (2> test6 1>> test6)
& 可以理解为 所有输出符的合并(1+2+。。。)

homewell:~/shell1$ ls -al test badtest test2 &> test6

homewell:~/shell1$ cat test6
    ls: cannot access 'test': No such file or directory
    ls: cannot access 'badtest': No such file or directory
    test2:
    total 8
    drwxrwx--- 2 homewell homewell 4096 10月  9 09:35 .
    drwxrwx--- 4 homewell homewell 4096 10月  9 09:38 ..

c) 临时重定向 (>&)

这里>& 更像一个管道 把 &的前置输出后置输出合并为后置输出

本人更习惯 1 (空格) >& (空格)2 这样,更能体会临时重定向作为操作符来使用,而不是把>&2 作为一个整体来记忆,这样我们就能活学活用,看如下事例

homewell:~/shell1$ cat error.sh 
    #!/bin/bash
    echo "This is an error message" 1 >& 2
    echo "This is nomal message"
    
homewell:~/shell1$ ./error.sh 2 > error #只把错误输出导入到error中
    This is an error message 1
homewell:~/shell1$ cat error
    This is nomal message
homewell:~/shell1$ ./error.sh 2 > error >& error2 # 把错误输出与标准输出都合并到error2中

homewell:~/shell1$ cat error

homewell:~/shell1$ cat error2
This is an error message 1
This is nomal message

d) 永久重定向 (exec)

永久的意思就是方向一旦确定,就无法再回头了,这个是比较麻烦的地方,也是头痛的地方

永久改变输出

homewell:~/shell1$ cat -n test11
     1	#!/bin/bash
     2	exec 2> test11err
     3	
     4	echo "还没有执行exec永久重定向"
     5	
     6	exec 1> test11out
     7	
     8	echo "执行永久重定向并标准输出到test11out文件"
     9	
    10	echo "执行永久重定向并标准错误到testerr中" >& 2
homewell:~/shell1$ ./test11 &> abc

homewell:~/shell1$ cat abc
    还没有执行exec永久重定向
homewell:~/shell1$ cat test11err 
    执行永久重定向并标准错误到testerr中
homewell:~/shell1$ cat test11out 
    执行永久重定向并标准输出到test11out文件

永久改变输入

exec 0< testfile 代表在模块中接下来的程序默认输入永久改变为从testfile文件作为输入(这里暂时不讲while的语法,见后期的结构化命令篇)

homewell:~/shell1/linuxlearnging$ cat testfile 
    a
    b
    c
homewell:~/shell1/linuxlearnging$ cat input3.sh 
    #!/bin/bash
    
    exec 0< testfile
    
    while read line
    do
    	echo "your content:" $line
    done
homewell:~/shell1/linuxlearnging$ ./input3.sh 
    your content: a
    your content: b
    your content: c

exec 扩展

exec 不仅可以当永久重定向使用,也可以作为类似于(./ source)
作为执行者(executor),执行指定的命令和参数来替换当前shell进程

exec 命令行 参数

不管执行成不成功都不会继续往后执行,也就是直接替换了当前的shell进程的所有内容,并直接跳出(这个是最猛的宏替换

homewell:~/shell1/linuxlearnging$ cat -n ./exectest.sh 
     1	#!/bin/bash
     2	
     3	var1=zhangll
     4	echo "$$"
     5	echo "$BASHPID"
     6	exec 0< testfile
     7	
     8	echo "exec1 after var1: $var1"
     9	echo "$BASHPID"
    10	
    11	
    12	exec echo "exec $BASHPID"
    13	echo "exec2 after var1: $var1"
homewell:~/shell1/linuxlearnging$ ./exectest.sh 
    14347
    14347
    exec1 after var1: zhangll
    14347
    exec 14347
homewell:~/shell1/linuxlearnging$ echo "$?"
    0

管道

管道的理解是一个十分头疼的问题,本人刚刚接触的时候也只是以比较肤浅的观点去理解管道,普遍都有个共识,前一截管道(前置命令)的输出结果可以作为后一截命令(后置命令)的输入数据,然而往往忽略了一点,也就是这个过程是实时的,并不是说前置命令执行完之后才会执行后置命令,重点在于 前置命令一旦产生输出(往往前置命令的输出与代码结束式共存的,所以很难把握) 之后,系统内部会立马产生输出到后置命令作为输入

理解这点我们需要一个工具辅助

在这里插入图片描述

当我们以tail命令作为输出的时候,tail命令是一直不停循环的,执行,根本没有停歇过。然后我们用| 命令却很好地把前置命令地输出作为后置命令地输出。
比如 只过滤一个拥有a地输出

管道符 |

# tail -f tail.logs | grep a

在这里插入图片描述
因此,当你在使用管道的时候,切记她是 ++流式的++

参考资料

《Linux命令行与shell脚本编程大全》

未完待续。。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章