封面
版权信息
O’Reilly Media, Inc. 介绍
业界评论
前言
目标读者
关于本书
排版约定
使用示例代码
O’Reilly在线学习平台(O’Reilly Online Learning)
联系我们
致谢
更多信息
第 1 章 bash入门
1.1 为什么是bash
NOTE
因为 bash 无处不在。它未必是最时髦的,可以说也不是最炫或最厉害的(就算不是,也差不多了),更不是唯一一个作为开源软件发布的 shell,但 bash 的大名尽人皆知
2022-03-20 00:26:46
NOTE
POSIX shell,bash 最初正是该项目的一部分
2022-03-20 00:27:38
NOTE
bash 既是一门强大的编程语言,也是一种优秀的用户界面。它让你在获得复杂编程特性的同时,能够保持键盘输入的便捷性。
2022-03-20 00:28:20
1.2 bash shell
NOTE
自动化,以实现易用性、可靠性以及可重现性
2022-03-20 17:49:54
1.3 提示符揭秘
1.4 显示当前位置
NOTE
pwd 是 print working directory(打印工作目录)的缩写,该命令接受两个选项。-L 显示当前的逻辑路径,这也是默认选项。-P 显示当前的物理路径,如果跟随符号链接,结果可能和逻辑路径不同。与此类似,cd 命令也提供了 -P 和 -L 选项:
2022-03-20 17:51:05
1.5 查找并运行命令
NOTE
可以试试 type、which、apropos、locate、slocate、find 和 ls 命令。
2022-03-20 17:51:35
1.6 获取文件的相关信息
1.7 显示当前目录下的所有隐藏(点号)文件
1.8 使用shell引用
1.9 使用或替换内建命令与外部命令
1.10 确定是否处于交互模式
1.11 将bash安装为默认shell
1.12 持续更新bash
1.13 获取Linux版的bash
1.14 获取xBSD版的bash
1.15 获取macOS版的bash
1.16 获取Unix版的bash
1.17 获取Windows版的bash
1.18 不获取bash的情况下使用bash
1.19 更多的bash文档
第 2 章 标准输出
NOTE
例如,再也不用让操作员将磁带挂载到磁带驱动器上(至少我们见过的桌面系统和笔记本计算机不用了)
2022-03-28 13:46:11
NOTE
软件开发人员是否要针对各种输出设备编写代码,甚至包括尚未发明的设备?这显然是件麻烦事。用户是不是也得知道如何将想要运行的程序连接到不同种类的设备?这也不是什么好主意
2022-03-28 13:59:38
NOTE
操作系统负责实现这套魔法。无论你要写入的目标是磁盘文件、终端、磁带设备、记忆棒,还是其他东西,程序只需要知道如何写入文件就够了,剩下的事情由操作系统搞定
2022-03-28 20:56:13
NOTE
程序怎么知道是该写入代表终端窗口的文件、磁盘文件还是其他种类的文件?不难,这种事情留给 shell 就行了
2022-03-28 20:56:29
NOTE
考虑下面这条简单的命令:[插图]
2022-03-28 20:56:58
2.1 输出到终端/终端窗口
NOTE
shell 负责解析 echo 的命令行参数(shell 对其他命令也是如此)。这意味着,在将参数交给 echo 前,shell 会完成所有的替换、通配符匹配等操作。其次,在解析参数时,参数之间的空白字符会被忽略
2022-03-20 17:54:02
2.2 保留输出中的空白字符
2.3 在输出中加入更多格式控制
2.4 消除输出中的换行符
NOTE
该特性在 shell 脚本中用处更大,你可能希望在形成一整行前由多条语句逐部分输出,或者在读取输入前显示用户提示
2022-03-28 21:41:02
NOTE
转移序列与 C 语言字符串中的类似。要想使用它们,调用 echo 命令时必须加上 -e 选项
2022-03-28 21:41:33
2.5 保存命令输出
NOTE
cat 命令得名自一个较长的单词 concatenation(拼接)
2022-03-28 21:42:52
2.6 将输出保存到其他文件
NOTE
如果文件名以斜线(/)起始,那就是绝对路径名
2022-03-28 21:43:14
NOTE
放置在文件系统层次结构(目录树)中以根目录起始的指定位置(假设所有的中间目录都存在且你有权限进入)
2022-03-28 21:43:26
NOTE
/tmp,这是一个几乎所有 Unix 系统都存在且普遍可用的临时目录
2022-03-28 21:43:31
NOTE
每次引用 .. 就会在文件系统目录树中向上(往根的方向,并非惯常意义中的沿着树“向上”)移动一级
2022-03-28 21:43:53
2.7 保存ls命令的输出
NOTE
我们加上 -C 选项
2022-03-28 21:48:27
NOTE
shell 的重定向功能意在对所有程序保持透明,因此程序无须编写特定代码来让自身的输出能够被重定向
2022-03-28 21:49:01
NOTE
我们可以在程序中加入代码,判断输出何时被发往终端(参见 manisatty)。然后,程序就能够针对不同情况进行处理,这也正是 ls 所做的
2022-03-28 21:49:23
NOTE
那么用户可能希望按列输出(-C 选项)
2022-03-28 21:49:34
NOTE
man isatty
2022-03-28 21:51:51
2.8 将输出和错误消息发送到不同文件
NOTE
1> 和 2> 中,数字表示文件 描述符
2022-03-28 22:34:58
NOTE
1 代表标准输出(STDOUT)
2022-03-28 22:35:04
NOTE
2 代表标准错误(STDERR)
2022-03-28 22:35:24
NOTE
如果不指定数字,则假定为 STDOUT
2022-03-28 22:34:48
2.9 将输出和错误消息发送到同一文件
NOTE
又或者老式且略烦琐(但可移植性更好)的写法:[插图]
2022-03-28 22:37:48
NOTE
both 是准备向 STDERR 和 STDOUT 生成输出的(假想)程序
2022-03-28 22:38:56
NOTE
&> 和 >& 只是将 STDOUT 和 STDERR 发送到相同地方(这正是我们想做的)的便捷写法
2022-03-28 22:39:03
NOTE
1 用作重定向的目标,但是 >& 将 1 解释为文件描述符。实际上,2>&1 是一个实体(其中不允许出现空格),表示标准错误(2)会被重定向(>)到随后的文件描述符(&)1
2022-03-28 22:40:46
NOTE
2>& 必须作为整体出现,不能夹杂空格;否则,2 就成了另一个参数,而 & 代表与其表面完全不同的含义(与在后台运行命令有关)
2022-03-28 22:40:54
NOTE
所有的重定向操作符都带有一个前导数字(如 2>),而 >(标准输出的文件描述符)的默认数字为 1
2022-03-28 22:41:04
NOTE
有时候,错误消息在文件中出现的时间可能早于在屏幕上出现的时间。这与标准错误的无缓冲性质有关,当写入文件而不是屏幕时,这种现象会更加明显
2022-03-28 22:41:37
2.10 追加输出
2.11 仅使用文件的起始或结尾部分
NOTE
使用 head 或 tail 命令。默认情况下,head 输出指定文件的前 10 行,tail 输出最后 10 行
2022-03-28 22:41:52
NOTE
tail 还有 -f 和 -F 选项,这两个选项能够跟踪文件末尾的写入
2022-03-28 22:42:01
NOTE
head、tail、
2022-03-28 22:43:00
NOTE
cut 和 uniq
2022-03-28 22:43:05
2.12 跳过文件标题
NOTE
tail 命令的选项 -n number(或者 -number)可以指定相对于文件末尾的行偏移。因此,tail -n 10 file 会显示 file 的最后 10 行,这也是不指定任何选项时的默认处理方式
2022-03-28 22:44:36
2.13 丢弃输出
NOTE
Unix 和 Linux系统都存在一个特殊设备,该设备并非真实的硬件,而仅仅是一个位桶(bit bucket),我们可以将不需要的数据都扔进去。它就是 /dev/null,非常适用于此类场景
2022-03-29 07:37:27
2.14 保存或分组多个命令的输出
NOTE
花括号({})将这些命令组合在一起,然后将重定向应用于分组中所有命令的输出
2022-03-29 08:54:14
NOTE
花括号实际上是保留字,因此两侧必须有空白字符。另外,闭合花括号之前的拖尾分号也是不能少的
2022-03-29 08:56:18
NOTE
你也可以用括号(())告诉 bash 在子 shell 中运行这些命令,然后重定向整个子shell 的输出
2022-03-29 08:56:34
NOTE
第一处是语法上的,第二处是语义上的。从语法上来看,花括号两侧需要有空白字符,命令列表中的最后一个命令必须以分号结尾。如果使用括号,那就不要求这些了
2022-03-29 08:56:53
NOTE
花括号只是一种组合多个命令的方式而已,更像是重定向的便捷写法,这样我们就不用单独重定向各个命令了。而出现在括号中的命令是在 shell 的另一个实例中运行,也就是当前 shell 的子 shell
2022-03-29 08:57:08
NOTE
子 shell 几乎复刻了当前 shell 的环境,包括 $PATH 在内的变量都是一模一样的,但对陷阱的处理有所不同
2022-03-29 08:57:18
NOTE
陷阱的更多信息
2022-03-29 08:57:25
NOTE
因为 cd 命令是在子 shell 中执行的,所以退出子 shell 后,父 shell 的当前目录仍保持原样,shell 变量也不会发生变化
2022-03-29 08:57:36
NOTE
花括号有一种值得注意的用法,即能够形成更简洁的分支语句块
2022-03-29 09:04:28
NOTE
第一种形式,这种写法更清晰,适合更大范围的受众
2022-03-29 09:05:07
2.15 将输出作为输入,连接两个程序
NOTE
利用管道符号(|)将输出直接发送到下一个程序
2022-03-29 16:23:40
NOTE
使用管道符号意味着不用再创建临时文件,事后再将其删除
2022-03-29 16:24:06
NOTE
像 sort 这种程序,既能从标准输入中获取输入(通过 < 符号进行重定向),也能从作为参数的文件中获取输入。因此,可以按以下方式操作
2022-03-29 16:24:17
NOTE
不用再将输入重定向到 sort
2022-03-29 16:24:21
NOTE
这种程序称为过滤器,如果照此方式编写程序和 shell 脚本,则更有助于你个人以及你的同事
2022-03-29 16:30:55
NOTE
初级的并行处理机制
2022-03-29 16:31:02
NOTE
你可以让两个命令(程序)并行运行并共享数据:一个的输出作为另一个的输入。二者不必按顺序运行(一个结束,另一个接着开始),只要第一个命令产生可用数据,第二个命令立刻就可以开始处理
2022-03-29 16:31:22
NOTE
但要注意,按照这种方式运行的多个命令(通过管道相连)分别在多个独立的进程中运行。尽管这一细微之处经常被忽视,有时所带来的影响却不容小视
2022-03-29 16:31:38
NOTE
19.8 节会对此展开讨论
2022-03-29 16:31:42
NOTE
通过管道将大量数据传给 less 等命令时,总会出现这种事:一旦发现了要找的东西,就会想要退出,哪怕是管道中还有更多数据
2022-03-29 16:33:02
2.16 将输出作为输入,同时保留其副本
NOTE
2.16.2 解决方案
2022-03-29 16:37:27
NOTE
tee 命令
2022-03-29 17:30:06
NOTE
同时发送到 /tmp/x.x 和 awk
2022-03-29 17:31:58
NOTE
后者通过管道与 tee 的输出连接在一起
2022-03-29 17:32:06
NOTE
演示 tee 命令在命令序列中的用法
2022-03-29 17:32:16
NOTE
tee 命令可以用来代替重定向标准输出的做法
2022-03-29 17:34:23
NOTE
tee 的输出并未重定向到别处,因此会显示在屏幕上。但是输出的副本也会发送给指定的文件(如 cat /tmp/all.my.sources),以备后用
2022-03-29 17:34:43
NOTE
这意味着错误(如来自 find 命令)会显示在屏幕上,但不会出现在 tee 指定的文件中。我们可以在 find 命令中加入 2>&1
2022-03-29 17:35:07
2.17 以输出为参数连接两个程序
NOTE
将待删除的文件指定为命令参数
2022-03-29 17:36:02
NOTE
rm 并不会从标准输入中读取参数
2022-03-29 17:36:33
NOTE
能以命令行参数的形式获取文件名,那该如何将先前运行过的命令(如 echo 或 ls)的输出放入命令行呢?
2022-03-29 17:36:57
NOTE
bash 的命令替换特性
2022-03-29 17:37:08
NOTE
也可以使用 xargs 命令
2022-03-29 17:37:15
NOTE
参见 15.13 中的讨论
2022-03-29 17:37:18
NOTE
子 shell 中运行的
2022-03-29 17:37:25
NOTE
可以用 rm -i 降低风险,选项 -i 会提醒你确认每次删除。对于小批量文件来说,这么做没毛病,但如果文件数量众多,那整个过程可就冗长不堪了
2022-03-29 17:38:44
NOTE
用 !! 来确保更加万无一失
2022-03-29 17:39:08
2.18 在一行中多次重定向
NOTE
也就是说,shell 脚本 divert 可以将其输出定向到不同的描述符,调用程序(invoking program)可以将描述符指向不同的目标
2022-03-29 17:40:37
NOTE
如果 divert 是一个 C 程序的可执行文件,则无须任何 open() 调用就可以向文件描述符3、4、5、6 写入
2022-03-29 17:40:53
NOTE
标准输入是 0,标准输出是 1,标准错误是 2
2022-03-29 17:41:40
NOTE
如果不指定数字,则假定为 1。这意味着可以用略显啰唆的写法 1>(而不是简单的 >)跟上文件名来重定向标准输出,不过其实没这个必要,便捷写法 > 就挺好
2022-03-29 17:41:52
NOTE
可以在 shell 中打开任意数量的文件描述符,令其指向各种文件,这样一来,随后在命令行上调用的程序就不用再费事了,直接就可以使用这些已打开的文件描述符
2022-03-29 17:42:00
2.19 重定向不起作用时保存输出
NOTE
Unix 和 Linux 中的每个进程通常一开始都有 3 个已打开的文件描述符:一个用于输入(标准输入 STDIN),一个用于输出(标准输出 STDOUT),一个用于错误消息(标准错误STDERR)
2022-03-29 17:42:48
NOTE
至于是否遵循这种约定,将错误消息写入标准错误,将正常输出写入标准输出,那真的就得看程序员了
2022-03-29 17:56:09
NOTE
每个文件描述符都由一个数字(从 0 开始)表示。标准输入是 0,标准输出是 1,标准错误是 2。这意味着可以用略显啰唆的写法来重定向标准输出:1>(而不是简单的 >)跟上文件名,但其实没这个必要,便捷写法 > 就够了。要想重定向标准错误,可以使用 2>
2022-03-29 18:08:49
NOTE
前者是缓冲式的(buffered),后者是非缓冲式的(unbuffered)
2022-03-29 18:10:58
NOTE
非缓冲意味着每个字符都是单独写入,不会被收集在一起,然后再批量写入。也就是说,你可以立刻看到错误消息,发生故障时丢失此类消息的可能性较小,但由此带来的就是效率问题
2022-03-29 18:12:05
NOTE
如果想要同时保存并查看输出,该怎么办?2.16 节中讨论过的 tee 命令此时就能派上用场了
2022-03-29 18:12:20
NOTE
此时标准输出指向的是屏幕
2022-03-29 18:16:49
NOTE
因此,只有标准输出消息会出现在文件中,而错误消息仍旧会出现在屏幕上
2022-03-29 18:16:23
NOTE
bash 对此做了特殊处理,它可以识别出标准输出连接到了管道 3。因此,当你写出 2>&1 时,bash会假定你希望将标准错误也连入管道,尽管 2>&1 的正常处理方式并非如此
2022-03-29 18:17:20
NOTE
3命令行中的管道会先于重定向进行处理。详见 Bash Reference Manual 的 3.2.2 节
2022-03-29 18:17:26
NOTE
这种做法(包括一般的管道语法)带来的另一个结果是,我们无法只将标准错误(而非标准输出)传入其他命令,除非事先交换文件描述符
2022-03-29 18:17:37
2.20 交换STDERR和STDOUT
NOTE
STDERR和STDOUT
2022-03-29 18:17:46
NOTE
第三个文件描述符交换 STDERR 和 STDOUT:
2022-03-29 18:18:13
NOTE
每次重定向文件描述符时,就会将打开的描述符复制到另一个描述符
2022-03-29 18:18:35
NOTE
将语法 3>&1 读作“使文件描述符 3 指向与标准输出文件描述符 1 相同的值”。
2022-03-29 18:26:51
2.21 避免意外覆盖文件
NOTE
发现将输出重定向到了原本打算保存的文件,这种事情太常见了
2022-03-29 18:28:00
NOTE
noclobber 选项告诉 bash 在重定向输出时不要覆盖任何现有文件
2022-03-29 18:28:12
2.22 有意覆盖文件
NOTE
使用 >| 重定向输出。即便是设置了 noclobber,bash 也会忽略该选项,并覆盖文件
2022-03-29 18:28:39
NOTE
noclobber 的使用并不会代替文件权限。不管有没有使用 >|,如果没有目录的写权限,那么就无法创建文件
2022-03-29 18:30:14
NOTE
不管有没有使用 >|,你必须拥有文件的写权限才能覆盖现有文件
2022-03-29 18:30:09
NOTE
据 Chet 所说,“POSIX 指定了语法 >|,这还是从 ksh88 中选出来的。我也说不清楚 Korn5 为什么会选用这种写法。csh 用的是 >!”。为了帮助记忆,你可以将其视为一种强调。它的用法在英语中带有祈使语气,符合要求 bash 在必要时“无论如何”都要覆盖文件的意味。vi 和 ex 编辑器在其 write 命令中也用 ! 表达了相同含义(:w!filename)。如果没有 !,覆盖已有文件时,编辑器会发出抱怨。要是加上 !,就相当于告诉编辑器“做就行了!”
2022-03-29 18:30:39
第 3 章 标准输入
3.1 从文件获取输入
NOTE
它们可以从视觉上提示重定向的方向
2022-03-26 23:35:37
NOTE
cat
2022-03-26 23:43:11
NOTE
,但这是 shell 脚本编程的一项重要特性(DOS 命令行也借鉴了),对 shell 的功能性和简单性必不可少
2022-03-26 23:42:58
3.2 将数据与脚本存放在一起
NOTE
<<(here-document)从命令行而非文件重定向输入文本
2022-03-30 16:38:59
NOTE
grep 命令查找第一个参数是否在指定文件中出现,如果没有指定文件,那么它会在标准输入中查找。
2022-03-30 16:39:26
NOTE
设置 here-document,告诉 shell 将标准输入重定向(临时)到此处
2022-03-30 16:39:44
NOTE
EOF 是一个任意的字符串(你想用什么都行),用作临时输入的终止符。它并不属于输入的一部分,只是作为标记告诉输入在哪里结束
2022-03-30 16:40:51
NOTE
grep 命令加入 -i 选项,以示搜索时不区分大小写。这样便可以使用 grep -i $1<<EOF同时搜索“Bill”或“bill”
2022-03-30 16:41:04
3.3 避免here-document中的怪异行为
NOTE
足以告诉 bash 你希望区别处理 here-document 中的内容
2022-03-30 16:58:02
NOTE
here-document 的每一行都要执行参数扩展、命令替换以及算术扩展”
2022-03-30 16:58:12
NOTE
1,随后跟着两个 0。这就是为什么我们在搜索“pete”时,得到的是 pete00
2022-03-30 16:58:31
NOTE
搜索“bill”时,得到的是 bill00
2022-03-30 16:58:38
NOTE
bash 就知道不用执行扩展,这样就符合我们的预期行为了
2022-03-30 16:58:48
NOTE
扩展操作
2022-03-30 16:58:55
NOTE
在结尾处的 EOF 标记中出现的拖尾空白字符(哪怕只是一个空格)会导致无法将其识别为结束标记。bash 会吞掉脚本的剩余部分,将其也视为输入并继续查找 EOF。所以,一定要确保 EOF 之后没有额外的空白字符(尤其是空格或制表符)。
2022-03-30 16:59:15
3.4 缩进here-document
NOTE
弄乱 shell 脚本的格式
2022-03-30 17:00:23
NOTE
就像结尾处的 EOF 标记中出现的任何拖尾空白字符都会导致其无法被识别为结束标记一样(参见 3.3 节中的警告部分),使用除制表符外的前导字符也会造成同样的后果。如果你的脚本使用空格或混合空格和制表符来进行缩进,可别在here-document 中这么做。要么使用制表符,要么什么都不用。另外,小心有些文本编辑器会自动将制表符替换成空格。
2022-03-30 17:13:11
3.5 获取用户输入
NOTE
取用户输入并将其保存在 shell 变量 REPLY中,这是 read 的最简形式
2022-03-30 17:22:59
NOTE
记住,要在提示信息结尾处加上标点符号或空格,因为光标会停在那里等待输入
2022-03-30 17:23:19
NOTE
t 选项可以设置超时值。指定秒数达到后,不管用户是否输入,read语句都会返回。我们的示例同时用到了 -t 和 -p 选项,但你也可以单独使用 -t 选项。从 bash 4 开始,你甚至可以将超时值指定为小数,如 .25 或 3.5。如果读取超时,则退出状态码($?)将大于 128
2022-03-30 17:23:31
3.6 获取yes或no
NOTE
尤其是不用区分大小写,如果用户直接按回车键,还能提供默认值
2022-03-30 17:23:56
NOTE
11.7 节中给出了该问题的另一种处理方法
2022-03-30 17:34:07
3.7 选择选项列表
NOTE
select 语句能够轻松地在 STDERR 上为用户生成编号列表,以便用户从中做出选择。虽然按下 Ctrl-D 可以结束 select,空输入会再次输出菜单,但别忘了提供“退出”或“结束”选项
2022-03-30 17:39:35
3.8 提示输入密码
NOTE
从用户那里读取到的输入行保存在变量 $PASSWD 中。
2022-03-30 17:40:04
NOTE
如果禁止了回显功能,当用户按下回车键时,就不会回显换行符,后续输出就会和提示信息出现在同一行
2022-03-30 17:41:48
NOTE
密码在内存中是以明文形式存放的,有可能通过核心转储或 /proc/core(如果你所用的操作系统提供了 /proc/)访问到。在多进程环境中也是如此,其他进程也有可能读取到密码。可能的话,最好使用 SSH 证书。无论任何情况,明智的做法是假定系统中的 root 和其他可能的用户都能接触到密码并对其进行相应的处理
2022-03-30 17:42:31
NOTE
用 stty -echo 来禁止输入密码时的屏幕回显。这么做的问题在于如果脚本意外终止,那么回显仍会处于关闭状态。有经验的用户知道输入 stty sane 将其恢复,但这并非人人都懂
2022-03-30 17:42:46
第 4 章 执行命令
4.1 运行程序
NOTE
从哪里运行程序呢?
2022-03-31 15:06:53
NOTE
bash 使用名为 PATH 变量包含了一个目录列表。各个目录之间以冒号(:)分隔。bash 在这些目录中查找命令行上指定的可执行文件。目录的顺序很重要:bash 按照变量中所列出的目录顺序依次查找,选择所找到的第一个同名的可执行文件。
2022-03-31 15:07:01
NOTE
很多人觉得将点号放进 $PATH 是一种很大的安全风险:别有用心的人会欺骗你执行同名命令的恶意版本(如 ls)。如果将点号放在目录列表的最前面,那么他人的 ls 版本的优先级就高于正常的 ls,你可能不知不觉间就执行了前者
2022-03-31 15:34:02
NOTE
如果将点号作为 $PATH 变量中的最后一个目录,至少不会那么容易被骗。当然,要是压根就不将它列入目录列表,那肯定更安全,而且你仍然可以执行当前目录中的命令,在前面加上点号和斜线即可
2022-03-31 15:53:40
NOTE
绝不要将点号目录或可写目录放进 root 的 $PATH 变量中。有关该话题的更多信息,参见 14.9 节和 14.10
2022-03-31 15:54:04
NOTE
一些 bash 用户的常见做法是创建个人 bin 目录,这类似于保存可执行文件的系统目录/bin 和 /usr/bin。你可以将自己喜欢的 shell 脚本和其他定制或私有命令放入个人的bin 目录中(如果创建于主目录,则路径为~/bin)。然后将该目录加入 PATH)。这样一来,你既可以拥有自己偏好的定制工具,也不存在误执行陌生人命令的安全风险。
2022-03-31 15:54:21
4.2 依次执行多个命令
NOTE
依次执行每个命令
2022-03-31 15:55:25
4.3 同时执行多个命令
NOTE
你可以在命令末尾添加一个 & 符号,在后台运行该命令。这样一来,就能够连续快速地执行这 3 个命令
2022-03-31 15:56:10
NOTE
在“后台”(Linux 其实没有这么个地方)运行某个命令时,真正的意思是我们断开了键盘输入与命令之间的联系,shell 在显示命令行提示符并接受更多命令输入前不会再等着该命令完成。命令输出(除非我们采取明确的操作来改变这种行为)仍然会出现在屏幕上,因此,示例中 3 个命令的输出会在屏幕上交错出现。
2022-03-31 15:58:22
NOTE
之后并未出现 & 符号,因此该命令不会在后台运行,bash 会等待其运行完毕才显示命令行提示符($)
2022-03-31 15:59:05
NOTE
作业号或进程 ID 可用于对作业实施有限的控制。例如,我们可以用 kill %1(因为作业号为 1)或者指定进程 ID(kill 4592)来“杀死”long 作业,二者能够实现相同的结果
2022-03-31 15:59:13
NOTE
你也可以用作业号重新连接到后台作业。例如,可以用 fg %1 将 long 作业放回前台。如果后台只有一个作业在运行,甚至都不用指定作业号,只使用 fg 即可
2022-03-31 15:59:26
NOTE
Ctrl-Z 暂停该命令,返回到提示符下。接着输入 bg 来恢复作业,并在后台继续运行。这么做的效果相当于事前在命令尾部加上 & 符号
2022-03-31 15:59:36
4.4 了解命令是否成功运行
NOTE
shell 变量 $? 中保存着命令的退出状态,其取值范围为 0~255。在编写 shell 脚本时,良好的做法是:如果一切正常,脚本退出时就返回 0;如果运行过程中出错,则返回非 0值
2022-03-31 16:01:09
NOTE
我们推荐只使用 0~127 作为返回值,因为 shell 用 128+N 代表被信号 N“杀死”。另外,如果使用的值大于 255 或小于 0,则会出现值回绕。可以用 exit 语句(如exit 1 或 exit 0)返回退出状态。但要注意,读取命令退出状态的机会只有一次
2022-03-31 16:01:29
NOTE
输入 echo $? 时,结果是 1,这是 badcommand 的返回值。但是该 echo 命令本身是成功执行的,因此最新的退出状态就是 0(表示成功)。因为检查退出状态的机会只有一次,所以很多 shell 脚本会立即将退出状态保存到其他 shell 变量中
2022-03-31 16:02:04
NOTE
bash 的一大特点是,脚本语言与在终端窗口提示符下键入的命令完全相同。在编写脚本时,这使得检查语法和逻辑要容易得多
2022-03-31 16:02:25
NOTE
(( )) 对算术表达式进行求值;参见 6.1 节和 6.2 节。
2022-03-31 16:02:38
4.5 仅当一个命令运行成功后才执行下一个命令
NOTE
写脚本就是另一回事了,对于示例中这样的脚本,进行测试是非常有必要的,这可以确保不会意外地将所在目录中的文件全部删除。
2022-03-31 16:04:50
NOTE
C 语言程序员会将其作为提供给 exit() 函数的参数值,例如,exit(4); 会返回 4。对于 shell 而言,退出码 0 代表成功,非 0 则代表失败
2022-03-31 16:05:16
4.6 减少if语句的数量
4.7 无人值守下运行耗时作业
NOTE
无人值守下运行耗时作业
2022-03-31 16:11:39
NOTE
如果想在后台运行作业并在该作业完成前退出 shell,那就需要对作业使用 nohup。
2022-03-31 16:12:04
NOTE
将作业置入后台时(通过 4.3 节中介绍过的 &),它仍旧是 bash shell 的子进程。如果退出 shell 的某个实例,bash 就会向其所有子进程发送 hangup 信号。这就是作业运行不了多久的原因。只要退出 bash,后台作业就会被“杀死”
2022-03-31 16:12:27
NOTE
nohup 命令只是设置子进程忽略 hangup 信号。你仍可以用 kill 命令“杀死”作业,因为 kill 发送的是 SIGTERM 信号,而非 SIGHUP 信号。但有了 nohup,作业就不会在退出bash 时被无意间“杀死”
2022-03-31 16:12:38
NOTE
nohup 给出的那句关于追加输出的消息只是为了提高自身的实用性。因为你有可能发出nohup 命令后就退出 shell,输出信息也就无处可去了,也就是说,终端中的 bash 会话已经结束,作业无法再向 STDOUT 写入。更重要的是,向不存在的位置写入信息会产生错误。因此,nohup 会替你重定向输出,将其追加(不是覆盖,而是添加到文件现有内容的末尾)到当前目录下的 nohup.out 文件中。你也可以明确地在命令行上指定将输出重定向到其他地方,nohup 足够聪明,能够发现你已经另有安排,也就不会再使用nohup.out 了
2022-03-31 16:12:55
4.8 出现故障时显示错误消息
NOTE
惯用做法是配合使用 || 和命令来输出调试 / 错误消息
2022-03-31 16:17:14
NOTE
注意,最后一个命令必须以分号结尾,闭合花括号与其中的内容之间要用空白字符分隔。2 具体的讨论参见 2.14 节。
2022-03-31 16:21:01
4.9 执行变量中的命令
NOTE
这里要给出一种略有不同的方法,它揭示了 bash 自身的一些东西。我们不仅可以将变量内容(详见第 5 章)用于参数,还可以用于命令本身。
2022-03-31 16:21:58
NOTE
注意你所使用的变量名。有些程序(如 InfoZip)使用环境变量(如 UNZIP)向自身传递设置,如果你做了类似 ZIP=/usr/bin/zip 的操作,少不了要抓耳挠腮上几天,搞不明白为什么该程序可以在命令行上正常运行,偏偏在脚本中就不行了。相信我们,这都是从惨痛教训中总结出的经验。另外,好好读读手册。
2022-03-31 16:22:48
4.10 执行目录中的所有脚本
NOTE
如果匹配到的是文件(由 -f 测试)且具有执行权限(由 -x 测试),那么 shell 会尝试执行此脚本
2022-03-31 16:25:02
第 5 章 脚本编程基础:shell变量
NOTE
变量(存放字符串和数值的容器,可以进行修改、比较、传递)
2022-03-31 16:27:33
NOTE
bash 脚本中的变量名称通常采用全大写,但这并非强制性的,只是一种常见做法而已。变量不用事先声明,直接使用就行了。变量基本上都是字符串类型,不过有些运算符能够将变量内容视为数字
2022-03-31 16:27:58
NOTE
首先,赋值语法 name=value 看起来相当直观,但 = 两侧不能有任何空白字符
2022-03-31 16:28:24
NOTE
shell 的主要目的是执行命令:你在命令行上指定要执行的命令。命令名之后的任何单词都会作为参数传给该命令。
2022-03-31 16:28:55
NOTE
如果允许 = 两侧出现空白字符
2022-03-31 16:29:04
NOTE
此时 shell 很难区分出到底是要调用命令(如本例中的 ls)还是要给变量赋值
2022-03-31 16:29:20
NOTE
shell 不允许在 = 两侧出现空白字符。该规定的另一方面也值得注意:不要在文件名中使用 =,对于 shell 脚本尤为如此(尽管可行,但不推荐)
2022-03-31 16:30:09
NOTE
出现在表达式 $(( )) 中的变量是个例外
2022-03-31 16:30:24
NOTE
用编译器的行话来说,赋值和检索值在语法上的差异就是变量的左值(L-value)与右值(R-value)之间的差异(赋值运算符的左侧和右侧)
2022-03-31 16:30:34
5.1 记录脚本
NOTE
记录脚本
2022-04-25 00:54:21
NOTE
有些人将 shell 语法、正则表达式,以及 shell 脚本编程的其他部分描述为只写(write-only)语法,以此暗示很多 shell 脚本中难以理解的错综复杂之处
2022-03-31 16:31:21
NOTE
最好的方法就是该用注释的地方就用注释(另一种方法是使用有意义的变量名)。在奇怪的语法或紧凑的表达式前加上注释的确有
2022-03-31 16:31:42
5.2 在shell脚本中嵌入文档
NOTE
在shell脚本中嵌入文档
2022-04-25 00:54:26
NOTE
使用内建命令 :(空命令)和 here-document 在脚本中嵌入文档
2022-03-31 16:34:32
NOTE
任何纯文本文档或标记都可以像这样使用,要么散布在脚本内,要么集中放在脚本末尾,后一种做法会更好些。考虑到安装了 bash 的计算机系统可能也安装了 Perl,POD格式也许是不错的选择。Perl 通常包含 pod2* 程序,可用于将 POD 转换成 HTML、LaTeX、手册页、文本以及用法文件
2022-03-31 16:36:28
NOTE
Perl Best Practices(O’Reilly 出版)一书中有一些优秀的库模块和应用程序文档模板,可以轻松地转换包括纯文本在内的任何文档格式。参见本书示例归档文件中的 CODE/ch07/Ch07.001_Best_Ex7.1 和CODE/ch07/Ch07.001_Best_Ex7.2
2022-03-31 16:44:25
5.3 提高脚本可读性
NOTE
提高脚本可读性
2022-04-25 00:54:31
NOTE
记录意图,而不是代码中的繁枝末节。如果遵循余下的要点,则代码应该会相当清晰。写代码时,编制提醒、提供数据布局示例或标题,记下脑子里想到的方方面面的细节。如果实现过程比较微妙或难懂,代码本身也要加以说明
2022-03-31 16:54:44
NOTE
可以用垂直空白字符创建功能相似的代码块。当然,对函数也是如此
2022-03-31 16:55:15
NOTE
我们敢保证,当不得不修正或改动脚本时,你绝对会在某个地方搭进去 10 倍或 100 倍的时间
2022-03-31 16:57:23
NOTE
对于比较长的行,选择在 76 个字符左右的位置处断行。没错,我们知道大多数屏幕(更准确地说,应该是终端程序)能处理的字符个数远超于此,但是 80 个字符宽度的纸张和屏幕仍是默认设置,而且在代码右侧留些空白绝对没什么坏处。不断往右边拖滚动条或看到屏幕上那些丑陋的折行,绝不是一件令人愉悦的事情,还容易让人分神。可别这么做。
2022-03-31 16:57:54
NOTE
在某些情况下,(可能通过 SSH)将生成的行发往别处时,断行会得不偿失。但大部分情况下,这么做是值得的
2022-03-31 16:58:05
5.4 将变量名与周围的文本分开
NOTE
将变量名与周围的文本分开
2022-04-25 00:54:36
NOTE
实际上,有人认为坚持使用花括号是一种好习惯,这样就不用考虑什么时候该用,什么时候不用,还能在整个脚本中保持写法一致。也有人觉得这样需要敲入的可用可不用的字符太多了,不但不好输入,还会让代码看起来很繁杂。说到底,这还是个人喜好问题
2022-03-31 17:04:41
5.5 导出变量
NOTE
导出变量
2022-03-31 17:09:33
NOTE
有时候,两个脚本互不知道对方的变量是件好事。如果在一个脚本的 for 循环中调用了另一个 shell 脚本,那么你肯定不希望这个脚本将 for 循环弄得乱七八糟(这种事不大可能发生,因为该脚本几乎肯定是在子 shell 中运行,这里只是举例说明)
2022-03-31 17:09:42
NOTE
敲入命令 env(或者内建命令 export -p)就能列出各个变量及其值
2022-03-31 17:13:05
NOTE
导出的变量实际上是按值调用的。在被调用脚本中修改导出变量的值并不会改变调用脚本中该变量的值
2022-03-31 17:13:40
NOTE
如何将被调用脚本修改过的值传回来?”答案是:做不到
2022-03-31 17:13:50
NOTE
你只能设计脚本时避开这种需求。有什么办法能够应对这种限制吗?方法之一是让被调用的脚本自己输出修改过的值,然后调用脚本读取该输出。例如,某个脚本导出了变量 VAL。要想在调用脚本中得到 $VAL 的新值,就得将修改后的值写入标准输出,然后获取并重新赋值
2022-03-31 17:14:03
NOTE
(有关 $() 语法的更多讲解,参见 10.5 节。)
2022-03-31 17:14:10
5.6 查看所有的变量值
NOTE
查看所有的变量值
2022-04-25 00:54:48
NOTE
用 set 命令查看当前 shell 中的所有变量值以及函数定义。用 env(或 export -p)命令查看那些被导出的、可用于子 shell 的变量。在 bash 4 或更高版本中,也可以使用 declare -p 命令。
2022-03-31 17:15:34
NOTE
declare 语句形式的输出可以在 shell 脚本中作为源代码,重新创建这些变量并为其赋值。各个选项(-i、-x、-r、-a)分别指明了变量为整数类型、已经导出、只读、数组类型
2022-03-31 17:15:57
5.7 在shell脚本中使用参数
NOTE
在shell脚本中使用参数
2022-04-25 00:54:43
NOTE
单个数位的数字用不着花括号,除非要区分变量名与其后出现的文本
2022-04-24 23:42:45
NOTE
但如果涉及 10 理解为 ${1} 后面紧跟着字符串 0
2022-04-24 23:42:55
5.8 遍历传入脚本的参数
NOTE
遍历传入脚本的参数
2022-04-25 00:54:54
NOTE
$* 引用的是命令行上出现的所有参数
2022-03-31 17:18:39
NOTE
如果文件名中不包含空格,该脚本则万事大吉,但有时难免碰上带有空格的文件名
2022-03-31 17:19:24
5.9 处理包含空格的参数
NOTE
处理包含空格的参数
2022-04-25 00:54:58
NOTE
bash 得到了一个包含 3 个单词的文件名,并将 ls 命令中的 $1 替换成了该文件名。到目前一切都还好。但是,我们并没有将脚本中的变量引用放入引号,因此 ls将文件名中的各个单词视为单独的参数(作为单独的文件名)
2022-03-31 17:20:26
5.10 处理包含空格的参数列表
NOTE
报错的原因与 for 循环中使用的 @
2022-03-31 17:23:59
NOTE
你得改用 shell 变量 * 和 $& 没什么两样
2022-03-31 17:29:28
5.11 统计参数数量
NOTE
统计参数数量
2022-04-25 00:55:02
NOTE
if 测试所提供的参数数量(保存在 $# 中)是否大于 3
2022-03-31 17:30:23
NOTE
错误信息会被重定向到标准错误。这种做法符合标准错误的本意:作为所有错误信息的通道。
2022-03-31 17:30:42
NOTE
该脚本还会根据检测到的错误返回不同的值。尽管这里没什么意义,但对于可能会被其他脚本调用的脚本而言,还是有用处的,这样就拥有了一种程序化的方法,不仅能够检测故障(非 0 的退出值),还可以区分不同的错误类型。
2022-03-31 17:30:54
NOTE
{#}、{#VAR}、${VAR#alt} 都在花括号里用到了 #,就把三者搞混了。第一种语法可以获得参数的数量,第二种语法可以获得变量 VAR 所保存值的长度,最后一种语法会执行某种替换操作。
2022-03-31 17:33:12
5.12 丢弃参数
NOTE
丢弃参数
2022-04-25 00:55:14
NOTE
如何在保留 for 循环的同时添加一个能够关闭文件名显示的选项呢?
2022-03-31 17:33:47
NOTE
用 shift 删除处理过的参数
2022-03-31 17:33:52
NOTE
添加了标记变量 2 变成 3 变成 1)
2022-03-31 17:34:26
NOTE
当 for 循环启动时,参数列表($@)中就再也没有 -v,剩下的是紧随其后的那些参数
2022-03-31 17:35:15
NOTE
按照惯例,shell 脚本的选项应该不区分位置,也就是说,myscript -a -p 应该等同于 myscript -p -a。而且,稳健的脚本还应该能处理重复选项,要么忽略,要么报错
2022-03-31 17:35:34
5.13 获取默认值
NOTE
获取默认值
2022-04-25 00:55:16
NOTE
:- 运算符的意思是,如果指定参数(这里是 1、3等),但后者是最常用到的。
2022-04-02 20:45:27
5.14 设置默认值
NOTE
设置默认值
2022-04-25 00:55:18
NOTE
示例中所引用的 HOME,随后再引用 $HOME 的话,返回的就是这个新值。
2022-04-02 20:46:42
NOTE
赋值运算符有一个重要的例外:不能对位置参数(如 *)赋值。在这种情况下,可以使用 :-(如 ${1:-default}),该表达式只返回值,但不进行赋值
2022-04-02 20:47:29
NOTE
顺便说一下,注意 {VAR:-value} 在形式上的差异,也许可以帮助你记忆这两种让人抓狂的符号。:= 执行赋值操作,同时返回运算符右侧的值。:- 只做了前者一半的工作:返回值,但不赋值。因此,它的符号也只有等号的一半(一个横杠,而不是两个
2022-04-02 20:47:49
5.15 使用空值作为有效的默认值
NOTE
使用空值作为有效的默认值
2022-04-25 00:55:23
NOTE
这个示例将 HOME 的内容。但如果删除该变量,则会发生替换。要想允许出现空字符串,使用不带冒号的 = 即可。但大部分时候是使用 :=,因为无论你是否有意,空值基本上没什么用处。
2022-04-25 00:08:59
5.16 不只使用字符串常量作为默认值
NOTE
不只使用字符串常量作为默认值
2022-04-25 00:55:26
NOTE
我们可以在该运算符(以及其他类似运算符)右侧做些什么?bash 手册页对于出现在运算符右侧的内容是这么表述的:“……要经过波浪号扩展、参数扩展、命令替换以及算术扩展”。
2022-04-25 00:09:51
NOTE
参数扩展意味着可以使用其他变量,如 {BASE:={HOME}}。波浪号扩展意味着可以使用 ~bob 这样的表达式,它会扩展成用户 bob 的主目录。可以通过 (cmds)。算术扩展意味着可以用 $(( )) 语法执行整数算术运算。例如:
2022-04-25 00:09:53
5.17 对不存在的参数输出错误消息
NOTE
对不存在的参数输出错误消息
2022-04-25 00:55:30
NOTE
因为无法控制错误消息的这部分内容,而且这看起来好像是 shell 脚本自身出现的错误,再加上可读性的问题,该技术在商业级的 shell 脚本中并不多见。(不过确实有助于调试。)
2022-04-25 00:52:48
NOTE
要想所有变量都具备这种行为,同时又不想逐个改动,可以使用 set -u 命令,“在变量替换时,将不存在的变量视为一种错误”。
2022-04-25 00:53:01
5.18 修改部分字符串
NOTE
修改部分字符串
2022-04-25 00:55:37
NOTE
为了避免文件名中包含空格,我们要将其放入引号。
2022-04-25 00:56:05