Bash|Bash 笔记


本文有多个主题:

  • SHell 代码的本质
  • 成对符、结尾符、退出码,和一些习惯
  • 玩玩并行
各个主题相互独立,可直接跳到你想看的那个。
SHell 代码的本质 有句话说是, Bash 上一切皆字符串。
猜测原因: Bash 是被造出来用来统筹 Linux (或者别的类 *nix 系统)的进程交互协作的,而进程间通信时所用数据的类型一定是字符串。
示例:
  1. 下面几行的意义是完全一样的
    echo a echo 'a' echo "a" echo \a

    这里 a 本身就是一般字符串了,所以不管是用引号消除功能还是用转义消除功能,它都仍然是一般字符。
  2. 下面几行的意义是完全一样的
    echo 'foobar' echo "foobar" echo foo''bar echo foo""bar echo foo\ \ bar

  3. 下面几行的意义是完全一样的
    v1=foo v2=ar ; echo $v1\ \ b$v2 v3=\ \; echo foo"$v3"bar v4='' ; echo foo"$v4"bar

    如果上面的 v3 v4 都是只有一个空格字符串而不是像上面那样是两个的话,使用时就可以不必被双引号包围,就像这样: v5=\ ; echo foo${v5}bar
    大括号可以清晰描述变量名的边界。没有歧义的时候可以省略大括号。
  4. 下面几行是意义完全一样的
    bash -c 'echo foo\ \ bar' bash -c "echo foo\ \ bar" bash -c 'echo foo""bar' bash -c "echo foo''bar" v='' ; bash -c "echo foo'$v'bar"

  5. 下面几行是意义完全一样的(我觉得这是比较能体现「一切皆字符串」的地方)
    echo aa e''cho aa 'e'cho aa e'ch''o' aa x1=ec ; ${x1}ho aa x2=o ; ech$x2 aa

    任何 Bash 代码,首先其实是 Bash 数据。当然,其类型是 字符串
上面频繁对空格字符使用了反斜杠转义。这会让它变为 一般字符 而失去 特殊功能空格符 有什么特殊功能?那就是,在代码被解释时,首先会切分成多块,空格就是默认的切分符号。
(这个切分符号也能改。不过没事儿别乱改,改了以后很可能想干啥都不方便。改法就是改一个全局变量,我在这里不告诉你,因为几乎用不到而且也不建议这么玩儿。感兴趣可以自己查。。。)
另外,转义反斜杠后面紧跟一个换行字符(就是在多数语言的代码里写作 \n 的那个字符)的话,它会被转义成等价于「没有这个字符」。
(另外,数据类型的话,基本数据类型就字符串。 Bash 里也有数组,不过与其说是数据类型,不如说是变量的一种特殊功能,就好像 ${x:-321} 一样。这些数组的元素只会是字符串,并且无法是数组。数组变量不带索引的话,就只能包含数组第一元素的信息,而无法是整个数组。)
成对符、结尾符、退出码,和一些习惯 什么什么符 成对符
  • " :这个是和前或后的最近一个自己成对,有限找前没有则找后。
  • ' :这个同上。
  • { } :这俩是一对。
  • ( ) :这俩是一对。
  • [ ] :这俩是一对。
  • do done :这俩是一对。
  • if fi :这俩是一对。
  • case esac :这俩是一对。
对于所有成对符,都有一个共同特性:在终端 SHell 上(就是一般的那种你手动操作的 SHell 命令行界面)编写的时候,只要前有开头而后无结尾,输入回车就不会触发命令执行。
成对符的作用见后文。
在下文会用到的一些概念
  • 「一行命令」指的是:你一敲回车就可以开始执行的整块代码
    它不一定真的只能写成行:因为有时候你敲回车,它只给你显示一个换行,不执行,且解释时这个回车也是被当作一个空格来处理。
  • 「行结尾符」:就是一行代码的最后一个字符。如果是 ; 且后面是换行符的话,可省略不写,它会得到自动补全。
  • 「一条命令」指的是:代表至少一个进程(它下面可能还会有子进程可能不会有)。
  • 「条结尾符」:可以把多条命令连接成一行命令。
  • 下文的「行结尾符」和「条结尾符」的最显著区别就是:
    • 前者后面输入换行符会触发新进程的执行;
    • 后者不会,只会多显示一个换行;
    后者表现类似于「成对符只有头没有尾时输入了换行符」的表现,但本质不同。
    之所以条结尾符后的换行符不会触发调起进程执行,是因为:在它之后、在后面那条命令开始之前,所有换行符,对于解释器都是等价于空格的。
  • 另外, { }( ) 这两对成对符,能够把一行命令变为一条命令,从而进一步参与到代码对进程的组织中。
退出码
一般,任何一条语句都有退出码。因为任何进程都有退出码,而一般一条语句代表一个进程。
退出码范围是 0 ~ 255 。已有约定: 0 表示 正确退出 ,其余表示各种不同的 错误退出
结尾符
  • ; :最普通的行结尾符。表示前面的执行完才执行后面的。一般是可以省略不写的,会被自动补全。
  • & :跟上面那个一样也是行结尾符,但是它会使得,被它结尾的 一行代码 所启动的一切进程,都放入后台执行,而不阻塞当前终端的操作。
  • | :条结尾符。管道,把前一条的 标准输出 内容变为后一条的 标准输入 内容。管道链条本身的退出码就是最后一条的退出码。
  • && :条结尾符。它前一条成功执行(正确退出),它后一条才会执行。否则跳过后一条并以同样的退出码退出。
  • || :条结尾符。与前者刚好相反,它前一条失败执行(错误退出),后一条才会执行,否则跳过后一条并以同样的退出码(也就是 0 )退出。
一些习惯 在终端命令行手动临时输入的时候,只要没必要,能省则省。
在脚本文件里,尽可能写清楚所有的 结尾符
在脚本文件里,尽可能在所有结尾符后都有换行。对于条结尾符,可根据个人喜好在换行后适当缩进,以突出你想强调的一些信息给代码读者(代码读者可能还是自己)。
玩玩并行 词汇解释:
  • 并行:同时执行(不关注是不是互相独立)
  • 并发:互相独立(不关注是不是同时执行)
一般需要同时执行的话,可能会想到用 & 指定进程后台执行。或在循环体内部这样。
我这有个更好的方案: xargs 。这是一个软件,通过安装 find-utils 软件包来安装。
示例 批量增加文件名后缀
你有以下文件:
asser xerz mio wee heed mox-x ...

你需要给他们共同的后缀 .png
你实际需要让下面每行代码一起执行:
mv asser asser.png mv xerz xerz.png mv mio mio.png mv wee wee.png mv heed heed.png mv mox-x mox-x.png ...

如果有一万个文件,总不能全手打。就算你的编辑软件可以列编辑,这仍旧是个大工程。
用下面的代码就能让它们在受限制的情况下同时执行:
ls | xargs -i -P0 -- mv {} {}.png

这是比较完整的写法。
其中:
  • 那个 {}-i 后的默认值,只是 -i 其实就等于 -i{}
  • 那个 -P 后面的数字就是并发度了。 0 表示动态契合当前运行机器的核心数。 1 就是普通的串行,不写 -P 也是串行。
稍微复杂一点的工作
如果你需要让稍微复杂一点的工作被 xargs 这样限制着同时执行——比如每一个工作都有「失败后就重来」的能力。
举个例子:
你在某目录下有以下文件:
10.21.233.1.txt 10.21.179.6.txt 10.33.230.5.txt ...

现在需要发他们到同文件名的服务器的同名目录。
一般代码是:
rsync -avz -- 10.21.233.1.txt 10.21.233.1:$PWD

这是把 10.21.233.1.txt 发到 10.21.233.1 里的,和 本地的当前所在目录 一样的目录。
如果想增加重试:
retring () { retried=${1:-0} ; rsync -avz -- 10.21.233.1.txt 10.21.233.1:$PWD && { echo :succ :r :: $retried ; } || { echo :fail :r :: $retried ; exec bash -c "$(declare -f retring) ; retring $((retried + 1))" ; } ; } ;

就像上面这样定义,然后执行 retring 就好了。
(多说一句,这里只是举例子,对于 rsync 应该没这么用的。。。。网不通的话,就停止它并去检查网络,而不是像这样硬去重试。但是,往网上上传批量图片的话,网络不好,就可以用这招。)
如果是批量呢?
retring_xrg () { retried=${1:-0} ; rsync -avz -- {}.txt {}:$PWD && { echo :succ :r :: $retried ; } || { echo :fail :r :: $retried ; exec bash -c "$(declare -f retring_xrg) ; retring_xrg $((retried + 1))" ; } ; } &&ls | xargs -i -P4 -- bash -c "$(declare -f retring_xrg) ; retring_xrg" ;

这样就行啦!
其实说这个还是想强调一点:代码首先是数据。
上面的几个 bash -c 后面的紧挨着的那个值,是一个比较长的字符串。 declare -f retring_xrg 的效果大家可以自行试试,它就是把一个函数定义当作字符串给送到标准输出里头。
直接执行那么标准输出就是屏幕打印了,而,如果像上面 $(declare -f retring_xrg) 那样用的话,被丢给标准输出的字符串就会被用来替换掉这个 $(xxxx) 整体。从而,实际上, bash -c 后面的那个值,在被 bash 作为参数接收的时候,就已经是个写好了的函数定义,以及这个定义后面紧跟的 ; retring_xrg 了。
像如 $(xxx) 里面的 xxx 这部分代码,在它还没有结果的时候,它前面的 bash -c 是还没被解析的——换个比方就是:还没出生、只是做好了准备而已,这是因为关于它的代码还尚未得到完整的生成。
当然了,要清楚一点就是,不论是 -- 还是 -c 还是 bash 还是后面双引号里那一大坨,这些都是 xargs 的 参数(或者有人可能习惯管这个叫「实参」不过我觉得其实也没必要)。其中的 -- 属于选项的概念了,双短横线一般用于分开「选项」和「一般参数」,它们对于软件(这里就是 xargs 了)来说都是参数( args ),只不过根据软件的规则(这个规则不同软件一般也会统一)细分后,它前面的属于「选项」( opt ),它后面的是「一般参数」( para )。总之, xargs 以及后面所有的,这整个,是 一条命令 。再包含上前面的 ls | 就是 一行命令 了。
EOF Bash|Bash 笔记
文章图片

【Bash|Bash 笔记】body{visibility:hidden}

    推荐阅读