Paul Brown 发布的文章

在 Bash 中,你可以使用 & 作为 AND(逻辑和)操作符。

有人可能会认为两篇文章中的 & 意思差不多,但实际上并不是。虽然 第一篇文章讨论了如何在命令末尾使用 & 来将命令转到后台运行,在之后剖析了流程管理,第二篇文章将 & 看作引用文件描述符的方法,这些文章让我们知道了,与 <> 结合使用后,你可以将输入或输出引导到别的地方。

但我们还没接触过作为 AND 操作符使用的 &。所以,让我们来看看。

& 是一个按位运算符

如果你十分熟悉二进制数操作,你肯定听说过 AND 和 OR 。这些是按位操作,对二进制数的各个位进行操作。在 Bash 中,使用 & 作为 AND 运算符,使用 | 作为 OR 运算符:

AND

0 & 0 = 0
0 & 1 = 0
1 & 0 = 0
1 & 1 = 1

OR

0 | 0 = 0
0 | 1 = 1
1 | 0 = 1
1 | 1 = 1

你可以通过对任何两个数字进行 AND 运算并使用 echo 输出结果:

$ echo $(( 2 & 3 )) # 00000010 AND 00000011 = 00000010
2
$ echo $(( 120 & 97 )) # 01111000 AND 01100001 = 01100000
96

OR(|)也是如此:

$ echo $(( 2 | 3 )) # 00000010 OR 00000011 = 00000011
3
$ echo $(( 120 | 97 )) # 01111000 OR 01100001 = 01111001
121

说明:

  1. 使用 (( ... )) 告诉 Bash 双括号之间的内容是某种算术或逻辑运算。(( 2 + 2 ))(( 5 % 2 ))%求模运算符)和 ((( 5 % 2 ) + 1))(等于 3)都可以工作。
  2. 像变量一样,使用 $ 提取值,以便你可以使用它。
  3. 空格并没有影响:((2+3)) 等价于 (( 2+3 ))(( 2 + 3 ))
  4. Bash 只能对整数进行操作。试试这样做: (( 5 / 2 )) ,你会得到 2;或者这样 (( 2.5 & 7 )) ,但会得到一个错误。然后,在按位操作中使用除了整数之外的任何东西(这就是我们现在所讨论的)通常是你不应该做的事情。

提示: 如果你想看看十进制数字在二进制下会是什么样子,你可以使用 bc ,这是一个大多数 Linux 发行版都预装了的命令行计算器。比如:

bc <<< "obase=2; 97"

这个操作将会把 97 转换成十二进制(obase 中的 o 代表 “output” ,也即,“输出”)。

bc <<< "ibase=2; 11001011"

这个操作将会把 11001011 转换成十进制(ibase 中的 i 代表 “input”,也即,“输入”)。

&& 是一个逻辑运算符

虽然它使用与其按位表达相同的逻辑原理,但 Bash 的 && 运算符只能呈现两个结果:1(“真值”)和0(“假值”)。对于 Bash 来说,任何不是 0 的数字都是 “真值”,任何等于 0 的数字都是 “假值”。什么也是 “假值”同时也不是数字呢:

$ echo $(( 4 && 5 )) # 两个非零数字,两个为 true = true
1
$ echo $(( 0 && 5 )) # 有一个为零,一个为 false = false
0
$ echo $(( b && 5 )) # 其中一个不是数字,一个为 false = false
0

&& 类似, OR 对应着 || ,用法正如你想的那样。

以上这些都很简单……直到它用在命令的退出状态时。

&& 是命令退出状态的逻辑运算符

正如我们在之前的文章中看到的,当命令运行时,它会输出错误消息。更重要的是,对于今天的讨论,它在结束时也会输出一个数字。此数字称为“返回码”,如果为 0,则表示该命令在执行期间未遇到任何问题。如果是任何其他数字,即使命令完成,也意味着某些地方出错了。

所以 0 意味着是好的,任何其他数字都说明有问题发生,并且,在返回码的上下文中,0 意味着“真”,其他任何数字都意味着“假”。对!这 与你所熟知的逻辑操作完全相反 ,但是你能用这个做什么? 不同的背景,不同的规则。这种用处很快就会显现出来。

让我们继续!

返回码 临时 储存在 特殊变量 ? 中 —— 是的,我知道:这又是一个令人迷惑的选择。但不管怎样,别忘了我们在讨论变量的文章中说过,那时我们说你要用 $ 符号来读取变量中的值,在这里也一样。所以,如果你想知道一个命令是否顺利运行,你需要在命令结束后,在运行别的命令之前马上用 $? 来读取 ? 变量的值。

试试下面的命令:

$ find /etc -iname "*.service"
find: '/etc/audisp/plugins.d': Permission denied
/etc/systemd/system/dbus-org.freedesktop.nm-dispatcher.service
/etc/systemd/system/dbus-org.freedesktop.ModemManager1.service
[......]

正如你在上一篇文章中看到的一样,普通用户权限在 /etc 下运行 find 通常将抛出错误,因为它试图读取你没有权限访问的子目录。

所以,如果你在执行 find 后立马执行……

echo $?

……,它将打印 1,表明存在错误。

(注意:当你在一行中运行两遍 echo $? ,你将得到一个 0 。这是因为 $? 将包含第一个 echo $? 的返回码,而这条命令按理说一定会执行成功。所以学习如何使用 $? 的第一课就是: 单独执行 $? 或者将它保存在别的安全的地方 —— 比如保存在一个变量里,不然你会很快丢失它。)

一个直接使用 ? 变量的用法是将它并入一串链式命令列表,这样 Bash 运行这串命令时若有任何操作失败,后面命令将终止。例如,你可能熟悉构建和编译应用程序源代码的过程。你可以像这样手动一个接一个地运行它们:

$ configure
.
.
.
$ make
.
.
.
$ make install
.
.
.

你也可以把这三行合并成一行……

$ configure; make; make install

…… 但你要希望上天保佑。

为什么这样说呢?因为你这样做是有缺点的,比方说 configure 执行失败了, Bash 将仍会尝试执行 makesudo make install——就算没东西可 make ,实际上,是没东西会安装。

聪明一点的做法是:

$ configure && make && make install

这将从每个命令中获取退出码,并将其用作链式 && 操作的操作数。

但是,没什么好抱怨的,Bash 知道如果 configure 返回非零结果,整个过程都会失败。如果发生这种情况,不必运行 make 来检查它的退出代码,因为无论如何都会失败的。因此,它放弃运行 make,只是将非零结果传递给下一步操作。并且,由于 configure && make 传递了错误,Bash 也不必运行make install。这意味着,在一长串命令中,你可以使用 && 连接它们,并且一旦失败,你可以节省时间,因为其他命令会立即被取消运行。

你可以类似地使用 ||,OR 逻辑操作符,这样就算只有一部分命令成功执行,Bash 也能运行接下来链接在一起的命令。

鉴于所有这些(以及我们之前介绍过的内容),你现在应该更清楚地了解我们在 这篇文章开头 出现的命令行:

mkdir test_dir 2>/dev/null || touch backup/dir/images.txt && find . -iname "*jpg" > backup/dir/images.txt &

因此,假设你从具有读写权限的目录运行上述内容,它做了什么以及如何做到这一点?它如何避免不合时宜且可能导致执行中断的错误?下周,除了给你这些答案的结果,我们将讨论圆括号,不要错过了哟!


via: https://www.linux.com/blog/learn/2019/2/logical-ampersand-bash

作者:Paul Brown 选题:lujun9972 译者:zero-MK 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

了解如何将 “&” 与尖括号结合使用,并从命令行中获得更多信息。

在我们探究大多数链式 Bash 命令中出现的所有的杂项符号(&|;><{[()]} 等等)的任务中,我们一直在仔细研究 & 符号

上次,我们看到了如何使用 & 把可能需要很长时间运行的进程放到后台运行。但是, 与尖括号 < 结合使用,也可用于将输出或输出通过管道导向其他地方。

前面的 尖括号教程中,你看到了如何使用 >,如下:

ls > list.txt

ls 输出传递给 list.txt 文件。

现在我们看到的是简写:

ls 1> list.txt

在这种情况下,1 是一个文件描述符,指向标准输出(stdout)。

以类似的方式,2 指向标准错误输出(stderr):

ls 2> error.log

所有错误消息都通过管道传递给 error.log 文件。

回顾一下:1> 是标准输出(stdout),2> 是标准错误输出(stderr)。

第三个标准文件描述符,0< 是标准输入(stdin)。你可以看到它是一个输入,因为箭头(<)指向0,而对于 12,箭头(>)是指向外部的。

标准文件描述符有什么用?

如果你在阅读本系列以后,你已经多次使用标准输出(1>)的简写形式:>

例如,当(假如)你知道你的命令会抛出一个错误时,像 stderr2)这样的东西也很方便,但是 Bash 告诉你的东西是没有用的,你不需要看到它。如果要在 home/ 目录中创建目录,例如:

mkdir newdir

如果 newdir/ 已经存在,mkdir 将显示错误。但你为什么要关心这些呢?(好吧,在某些情况下你可能会关心,但并非总是如此。)在一天结束时,newdir 会以某种方式让你填入一些东西。你可以通过将错误消息推入虚空(即 `/dev/null)来抑制错误消息:

mkdir newdir 2> /dev/null

这不仅仅是 “让我们不要看到丑陋和无关的错误消息,因为它们很烦人”,因为在某些情况下,错误消息可能会在其他地方引起一连串错误。比如说,你想找到 /etc 下所有的 .service 文件。你可以这样做:

find /etc -iname "*.service"

但事实证明,在大多数系统中,find 显示的错误会有许多行,因为普通用户对 /etc 下的某些文件夹没有读取访问权限。它使读取正确的输出变得很麻烦,如果 find 是更大的脚本的一部分,它可能会导致行中的下一个命令排队。

相反,你可以这样做:

find /etc -iname "*.service" 2> /dev/null

而且你只得到你想要的结果。

文件描述符入门

单独的文件描述符 stdoutstderr 还有一些注意事项。如果要将输出存储在文件中,请执行以下操作:

find /etc -iname "*.service" 1> services.txt

工作正常,因为 1> 意味着 “发送标准输出且自身标准输出(非标准错误)到某个地方”。

但这里存在一个问题:如果你想把命令抛出的错误信息记录到文件,而结果中没有错误信息你该怎么?上面的命令并不会这样做,因为它只写入 find 正确的结果,而:

find /etc -iname "*.service" 2> services.txt

只会写入命令抛出的错误信息。

我们如何得到两者?请尝试以下命令:

find /etc -iname "*.service" &> services.txt

…… 再次和 & 打个招呼!

我们一直在说 stdin0)、stdout1)和 stderr2)是“文件描述符”。文件描述符是一种特殊构造,是指向文件的通道,用于读取或写入,或两者兼而有之。这来自于将所有内容都视为文件的旧 UNIX 理念。想写一个设备?将其视为文件。想写入套接字并通过网络发送数据?将其视为文件。想要读取和写入文件?嗯,显然,将其视为文件。

因此,在管理命令的输出和错误的位置时,将目标视为文件。因此,当你打开它们来读取和写入它们时,它们都会获得文件描述符。

这是一个有趣的效果。例如,你可以将内容从一个文件描述符传递到另一个文件描述符:

find /etc -iname "*.service" 1> services.txt 2>&1

这会将 stderr 导向到 stdout,而 stdout 通过管道被导向到一个文件中 services.txt 中。

它再次出现:& 发信号通知 Bash 1 是目标文件描述符。

标准文件描述符的另一个问题是,当你从一个管道传输到另一个时,你执行此操作的顺序有点违反直觉。例如,按照上面的命令。它看起来像是错误的方式。你应该像这样阅读它:“将输出导向到文件,然后将错误导向到标准输出。” 看起来错误输出会在后面,并且在输出到标准输出(1)已经完成时才发送。

但这不是文件描述符的工作方式。文件描述符不是文件的占位符,而是文件的输入和(或)输出通道。在这种情况下,当你做 1> services.txt 时,你的意思是 “打开一个写管道到 services.txt 并保持打开状态”。1 是你要使用的管道的名称,它将保持打开状态直到该行的结尾。

如果你仍然认为这是错误的方法,试试这个:

find /etc -iname "*.service" 2>&1 1>services.txt

并注意它是如何不工作的;注意错误是如何被导向到终端的,而只有非错误的输出(即 stdout)被推送到 services.txt

这是因为 Bash 从左到右处理 find 的每个结果。这样想:当 Bash 到达 2>&1 时,stdout1)仍然是指向终端的通道。如果 find 给 Bash 的结果包含一个错误,它将被弹出到 2,转移到 1,然后留在终端!

然后在命令结束时,Bash 看到你要打开 stdout1) 作为到 services.txt 文件的通道。如果没有发生错误,结果将通过通道 1 进入文件。

相比之下,在:

find /etc -iname "*.service" 1>services.txt 2>&1

1 从一开始就指向 services.txt,因此任何弹出到 2 的内容都会导向到 1 ,而 1 已经指向最终去的位置 services.txt,这就是它工作的原因。

在任何情况下,如上所述 &> 都是“标准输出和标准错误”的缩写,即 2>&1

这可能有点多,但不用担心。重新导向文件描述符在 Bash 命令行和脚本中是司空见惯的事。随着本系列的深入,你将了解更多关于文件描述符的知识。下周见!


via: https://www.linux.com/blog/learn/2019/2/ampersands-and-file-descriptors-bash

作者:Paul Brown 选题:lujun9972 译者:zero-mk 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

这篇文章将了解一下 & 符号及它在 Linux 命令行中的各种用法。

如果阅读过我之前的三篇文章(123),你会觉得掌握连接各个命令之间的连接符号用法也是很重要的。实际上,命令的用法并不难,例如 mkdirtouchfind 也分别可以简单概括为“建立新目录”、“更新文件”和“在目录树中查找文件”而已。

但如果要理解

mkdir test_dir 2>/dev/null || touch images.txt && find . -iname "*jpg" > backup/dir/images.txt &

这一串命令的目的,以及为什么要这样写,就没有这么简单了。

关键之处就在于命令之间的连接符号。掌握了这些符号的用法,不仅可以让你更好理解整体的工作原理,还可以让你知道如何将不同的命令有效地结合起来,提高工作效率。

在这一篇文章和接下来的文章中,我会介绍如何使用 & 号和管道符号(|)在不同场景下的使用方法。

幕后工作

我来举一个简单的例子,看看如何使用 & 号将下面这个命令放到后台运行:

cp -R original/dir/ backup/dir/

这个命令的目的是将 original/dir/ 的内容递归地复制到 backup/dir/ 中。虽然看起来很简单,但是如果原目录里面的文件太大,在执行过程中终端就会一直被卡住。

所以,可以在命令的末尾加上一个 & 号,将这个任务放到后台去执行:

cp -R original/dir/ backup/dir/ &

任务被放到后台执行之后,就可以立即继续在同一个终端上工作了,甚至关闭终端也不影响这个任务的正常执行。需要注意的是,如果要求这个任务输出内容到标准输出中(例如 echols),即使使用了 &,也会等待这些输出任务在前台运行完毕。

当使用 & 将一个进程放置到后台运行的时候,Bash 会提示这个进程的进程 ID。在 Linux 系统中运行的每一个进程都有一个唯一的进程 ID,你可以使用进程 ID 来暂停、恢复或者终止对应的进程,因此进程 ID 是非常重要的。

这个时候,只要你还停留在启动进程的终端当中,就可以使用以下几个命令来对管理后台进程:

  • jobs 命令可以显示当前终端正在运行的进程,包括前台运行和后台运行的进程。它对每个正在执行中的进程任务分配了一个序号(这个序号不是进程 ID),可以使用这些序号来引用各个进程任务。
$ jobs
[1]- Running cp -i -R original/dir/* backup/dir/ &
[2]+ Running find . -iname "*jpg" > backup/dir/images.txt &
  • fg 命令可以将后台运行的进程任务放到前台运行,这样可以比较方便地进行交互。根据 jobs 命令提供的进程任务序号,再在前面加上 % 符号,就可以把相应的进程任务放到前台运行。
$ fg %1 # 将上面序号为 1 的 cp 任务放到前台运行
cp -i -R original/dir/* backup/dir/

如果这个进程任务是暂停状态,fg 命令会将它启动起来。

  • 使用 ctrl+z 组合键可以将前台运行的任务暂停,仅仅是暂停,而不是将任务终止。当使用 fg 或者 bg 命令将任务重新启动起来的时候,任务会从被暂停的位置开始执行。但 sleep 命令是一个特例,sleep 任务被暂停的时间会计算在 sleep 时间之内。因为 sleep 命令依据的是系统时钟的时间,而不是实际运行的时间。也就是说,如果运行了 sleep 30,然后将任务暂停 30 秒以上,那么任务恢复执行的时候会立即终止并退出。
  • bg 命令会将任务放置到后台执行,如果任务是暂停状态,也会被启动起来。
$ bg %1
[1]+ cp -i -R original/dir/* backup/dir/ &

如上所述,以上几个命令只能在同一个终端里才能使用。如果启动进程任务的终端被关闭了,或者切换到了另一个终端,以上几个命令就无法使用了。

如果要在另一个终端管理后台进程,就需要其它工具了。例如可以使用 kill 命令从另一个终端终止某个进程:

kill -s STOP <PID>

这里的 PID 就是使用 & 将进程放到后台时 Bash 显示的那个进程 ID。如果你当时没有把进程 ID 记录下来,也可以使用 ps 命令(代表 process)来获取所有正在运行的进程的进程 ID,就像这样:

ps | grep cp

执行以后会显示出包含 cp 字符串的所有进程,例如上面例子中的 cp 进程。同时还会显示出对应的进程 ID:

$ ps | grep cp
14444 pts/3 00:00:13 cp

在这个例子中,进程 ID 是 14444,因此可以使用以下命令来暂停这个后台进程:

kill -s STOP 14444

注意,这里的 STOP 等同于前面提到的 ctrl+z 组合键的效果,也就是仅仅把进程暂停掉。

如果想要把暂停了的进程启动起来,可以对进程发出 CONT 信号:

kill -s CONT 14444

这个给出一个可以向进程发出的常用信号列表。如果想要终止一个进程,可以发送 TERM 信号:

kill -s TERM 14444

如果进程不响应 TERM 信号并拒绝退出,还可以发送 KILL 信号强制终止进程:

kill -s KILL 14444

强制终止进程可能会有一定的风险,但如果遇到进程无节制消耗资源的情况,这样的信号还是能够派上用场的。

另外,如果你不确定进程 ID 是否正确,可以在 ps 命令中加上 x 参数:

$ ps x| grep cp
14444 pts/3 D 0:14 cp -i -R original/dir/Hols_2014.mp4
  original/dir/Hols_2015.mp4 original/dir/Hols_2016.mp4
  original/dir/Hols_2017.mp4 original/dir/Hols_2018.mp4 backup/dir/

这样就可以看到是不是你需要的进程 ID 了。

最后介绍一个将 psgrep 结合到一起的命令:

$ pgrep cp
8
18
19
26
33
40
47
54
61
72
88
96
136
339
6680
13735
14444

pgrep 可以直接将带有字符串 cp 的进程的进程 ID 显示出来。

可以加上一些参数让它的输出更清晰:

$ pgrep -lx cp
14444 cp

在这里,-l 参数会让 pgrep 将进程的名称显示出来,-x 参数则是让 pgrep 完全匹配 cp 这个命令。如果还想了解这个命令的更多细节,可以尝试运行 pgrep -ax

总结

在命令的末尾加上 & 可以让我们理解前台进程和后台进程的概念,以及如何管理这些进程。

在 UNIX/Linux 术语中,在后台运行的进程被称为 守护进程 daemon 。如果你曾经听说过这个词,那你现在应该知道它的意义了。

和其它符号一样,& 在命令行中还有很多别的用法。在下一篇文章中,我会更详细地介绍。


via: https://www.linux.com/blog/learn/2019/2/and-ampersand-and-linux

作者:Paul Brown 选题:lujun9972 译者:HankChow 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

在这篇文章,我们继续来深入探讨尖括号的更多其它用法。

上一篇文章当中,我们介绍了尖括号(<>)以及它们的一些用法。在这篇文章,我们继续来深入探讨尖括号的更多其它用法。

通过使用 <,可以实现“欺骗”的效果,让其它命令认为某个命令的输出是一个文件。

例如,在进行备份文件的时候不确定备份是否完整,就需要去确认某个目录是否已经包含从原目录中复制过去的所有文件。你可以试一下这样操作:

diff <(ls /original/dir/) <(ls /backup/dir/)

diff 命令是一个逐行比较两个文件之间差异的工具。在上面的例子中,就使用了 <diff 认为两个 ls 命令输出的结果都是文件,从而能够比较它们之间的差异。

要注意,在 <(...) 之间是没有空格的。

我尝试在我的图片目录和它的备份目录执行上面的命令,输出的是以下结果:

diff <(ls /My/Pictures/) <(ls /My/backup/Pictures/) 
5d4 < Dv7bIIeUUAAD1Fc.jpg:large.jpg

输出结果中的 < 表示 Dv7bIIeUUAAD1Fc.jpg:large.jpg 这个文件存在于左边的目录(/My/Pictures)但不存在于右边的目录(/My/backup/Pictures)中。也就是说,在备份过程中可能发生了问题,导致这个文件没有被成功备份。如果 diff 没有显示出任何输出结果,就表明两个目录中的文件是一致的。

看到这里你可能会想到,既然可以通过 < 将一些命令行的输出内容作为一个文件提供给一个需要接受文件格式的命令,那么在上一篇文章的“最喜欢的演员排序”例子中,就可以省去中间的一些步骤,直接对输出内容执行 sort 操作了。

确实如此,这个例子可以简化成这样:

sort -r <(while read -r name surname films;do echo $films $name $surname ; done < CBactors)

Here 字符串

除此以外,尖括号的重定向功能还有另一种使用方式。

使用 echo 和管道(|)来传递变量的用法,相信大家都不陌生。假如想要把一个字符串变量转换为全大写形式,你可以这样做:

myvar="Hello World" echo $myvar | tr '[:lower:]' '[:upper:]' HELLO WORLD

tr 命令可以将一个字符串转换为某种格式。在上面的例子中,就使用了 tr 将字符串中的所有小写字母都转换为大写字母。

要理解的是,这个传递过程的重点不是变量,而是变量的值,也就是字符串 Hello World。这样的字符串叫做 HERE 字符串,含义是“这就是我们要处理的字符串”。但对于上面的例子,还可以用更直观的方式的处理,就像下面这样:

tr '[:lower:]' '[:upper:]' <<< $myvar

这种简便方式并不需要使用到 echo 或者管道,而是使用了我们一直在说的尖括号。

总结

使用 <> 这两个简单的符号,原来可以实现这么多功能,Bash 又一次为工作的灵活性提供了很多选择。

当然,我们的介绍还远远没有完结,因为还有很多别的符号可以为 Bash 命令带来更多便利。不过如果没有充分理解它们,充满符号的 Bash 命令看起来只会像是一堆乱码。接下来我会解读更多类似的 Bash 符号,下次见!


via: https://www.linux.com/blog/learn/2019/1/more-about-angle-brackets-bash

作者:Paul Brown 选题:lujun9972 译者:HankChow 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

为初学者介绍尖括号。

Bash 内置了很多诸如 lscdmv 这样的重要的命令,也有很多诸如 grepawksed 这些有用的工具。但除此之外,其实 Bash 中还有很多可以起到胶水作用的标点符号,例如点号(.)、逗号(,)、括号(<>)、引号(")之类。下面我们就来看一下可以用来进行数据转换和转移的尖括号(<>)。

转移数据

如果你对其它编程语言有所了解,你会知道尖括号 <> 一般是作为逻辑运算符,用来比较两个值之间的大小关系。如果你还编写 HTML,尖括号作为各种标签的一部分,就更不会让你感到陌生了。

在 shell 脚本语言中,尖括号可以将数据从一个地方转移到另一个地方。例如可以这样把数据存放到一个文件当中:

ls > dir_content.txt

在上面的例子中,> 符号让 shell 将 ls 命令的输出结果写入到 dir_content.txt 里,而不是直接显示在命令行中。需要注意的是,如果 dir_content.txt 这个文件不存在,Bash 会为你创建;但是如果 dir_content.txt 是一个已有的非空文件,它的内容就会被覆盖掉。所以执行类似的操作之前务必谨慎。

你也可以不使用 > 而使用 >>,这样就可以把新的数据追加到文件的末端而不会覆盖掉文件中已有的数据了。例如:

ls $HOME > dir_content.txt; wc -l dir_content.txt >> dir_content.txt

在这串命令里,首先将家目录的内容写入到 dir_content.txt 文件中,然后使用 wc -l 计算出 dir_content.txt 文件的行数(也就是家目录中的文件数)并追加到 dir_content.txt 的末尾。

在我的机器上执行上述命令之后,dir_content.txt 的内容会是以下这样:

Applications
bin
cloud
Desktop
Documents
Downloads
Games
ISOs
lib
logs
Music
OpenSCAD
Pictures
Public
Templates
test_dir
Videos
17 dir_content.txt

你可以将 >>> 作为箭头来理解。当然,这个箭头的指向也可以反过来。例如,Coen brothers (LCTT 译注:科恩兄弟,一个美国电影导演组合)的一些演员以及他们出演电影的次数保存在 CBActors 文件中,就像这样:

John Goodman 5
John Turturro 3
George Clooney 2
Frances McDormand 6
Steve Buscemi 5
Jon Polito 4
Tony Shalhoub 3
James Gandolfini 1

你可以执行这样的命令:

sort < CBActors
Frances McDormand 6 # 你会得到这样的输出
George Clooney 2
James Gandolfini 1
John Goodman 5
John Turturro 3
Jon Polito 4
Steve Buscemi 5
Tony Shalhoub 3

就可以使用 sort 命令将这个列表按照字母顺序输出。但是,sort 命令本来就可以接受传入一个文件,因此在这里使用 < 会略显多余,直接执行 sort CBActors 就可以得到期望的结果。

如果你想知道 Coens 最喜欢的演员是谁,你可以这样操作。首先:

while read name surname films; do echo $films $name $surname > filmsfirst.txt; done < CBActors

上面这串命令写在多行中可能会比较易读:

while read name surname films;\
 do
 echo $films $name $surname >> filmsfirst;\
 done < CBActors

下面来分析一下这些命令做了什么:

  • while …; do … done 是一个循环结构。当 while 后面的条件成立时,dodone 之间的部分会一直重复执行;
  • read 语句会按行读入内容。read 会从标准输入中持续读入,直到没有内容可读入;
  • CBActors 文件的内容会通过 < 从标准输入中读入,因此 while 循环会将 CBActors 文件逐行完整读入;
  • read 命令可以按照空格将每一行内容划分为三个字段,然后分别将这三个字段赋值给 namesurnamefilms 三个变量,这样就可以很方便地通过 echo $films $name $surname >> filmsfirst;\ 来重新排列几个字段的放置顺序并存放到 filmfirst 文件里面了。

执行完以后,查看 filmsfirst 文件,内容会是这样的:

5 John Goodman
3 John Turturro
2 George Clooney
6 Frances McDormand
5 Steve Buscemi
4 Jon Polito
3 Tony Shalhoub
1 James Gandolfini

这时候再使用 sort 命令:

sort -r filmsfirst

就可以看到 Coens 最喜欢的演员是 Frances McDormand 了。(-r 参数表示降序排列,因此 McDormand 会排在最前面)


via: https://www.linux.com/blog/learn/2019/1/understanding-angle-brackets-bash

作者:Paul Brown 选题:lujun9972 译者:HankChow 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

Paul Brown 解释了 Linux shell 命令中那个不起眼的“点”的各种意思和用法。

在现实情况中,使用 shell 命令编写的单行命令或脚本可能会令人很困惑。你使用的很多工具的名称与它们的实际功能相差甚远(grepteeawk,还有吗?),而当你将两个或更多个组合起来时,所组成的 “句子” 看起来更像某种外星人的天书。

因此,上面说的这些对于你并无帮助,因为你用来编写一连串的指令所使用的符号根据你使用的场景有着不同的意义。

位置、位置、位置

就拿这个不起眼的点(.)来说吧。当它放在一个需要一个目录名称的命令的参数处时,表示“当前目录”:

find . -name "*.jpg"

意思就是“在当前目录(包括子目录)中寻找以 .jpg 结尾的文件”。

ls .cd . 结果也如你想的那样,它们分别列举和“进入”到当前目录,虽然在这两种情况下这个点都是多余的。

而一个紧接着另一个的两个点呢,在同样的场景下(即当你的命令期望一个文件目录的时候)表示“当前目录的父目录”。如果你当前在 /home/your_directory 下并且运行:

cd ..

你就会进入到 /home。所以,你可能认为这仍然适合“点代表附近目录”的叙述,并且毫不复杂,对吧?

那下面这样会怎样呢?如果你在一个文件或目录的开头加上点,它表示这个文件或目录会被隐藏:

$ touch somedir/file01.txt somedir/file02.txt somedir/.secretfile.txt
$ ls -l somedir/
total 0
-rw-r--r-- 1 paul paul 0 Jan 13 19:57 file01.txt
-rw-r--r-- 1 paul paul 0 Jan 13 19:57 file02.txt
$ # 注意上面列举的文件中没有 .secretfile.txt
$ ls -la somedir/
total 8
drwxr-xr-x 2 paul paul 4096 Jan 13 19:57 .
drwx------ 48 paul paul 4096 Jan 13 19:57 ..
-rw-r--r-- 1 paul paul 0 Jan 13 19:57 file01.txt
-rw-r--r-- 1 paul paul 0 Jan 13 19:57 file02.txt
-rw-r--r-- 1 paul paul 0 Jan 13 19:57 .secretfile.txt
$ # 这个 -a  选项告诉 ls 去展示“all”文件,包括那些隐藏的

然后就是你可以将 . 当作命令。是的,你听我说:. 是个真真正正的命令。它是 source 命令的代名词,所以你可以用它在当前 shell 中执行一个文件,而不是以某种其它的方式去运行一个脚本文件(这通常指的是 Bash 会产生一个新的 shell 去运行它)

很困惑?别担心 —— 试试这个:创建一个名为 myscript 的脚本,内容包含下面一行:

myvar="Hello"

然后通过常规的方法执行它,也就是用 sh myscript(或者通过 chmod a+x myscript 命令让它可执行,然后运行 ./myscript)。现在尝试并且观察 myvar 的内容,通过 echo $myvar(理所当然你什么也得不到)。那是因为,当你的脚本赋值 "Hello"myvar 时,它是在一个隔离的bash shell 实例中进行的。当脚本运行结束时,这个新产生的实例会消失并且将控制权转交给原来的shell,而原来的 shell 里甚至都不存在 myvar 变量。

然而,如果你这样运行 myscript

. myscript

echo $myvar 就会打印 Hello 到命令行上。

当你的 .bashrc 文件发生变化后,你经常会用到 .(或 source)命令,就像当你要扩展 PATH 变量那样。在你的当前 shell 实例中,你可以使用 . 来让变化立即生效。

双重麻烦

就像看似无关紧要的一个点有多个含义一样,两个点也是如此。除了指向当前目录的父级之外,两个点(..)也用于构建序列。

尝试下这个:

echo {1..10}

它会打印出从 1 到 10 的序列。在这种场景下,.. 表示 “从左边的值开始,计数到右边的值”。

现在试下这个:

echo {1..10..2}

你会得到 1 3 5 7 9..2 这部分命令告诉 Bash 输出这个序列,不过不是每个相差 1,而是相差 2。换句话说,就是你会得到从 1 到 10 之间的奇数。

它反着也仍然有效:

echo {10..1..2}

你也可以用多个 0 填充你的数字。这样:

echo {000..121..2}

会这样打印出从 0 到 121 之间的偶数(填充了前置 0):

000 002 004 006 ... 050 052 054 ... 116 118 120

不过这样的序列发生器有啥用呢?当然,假设您的新年决心之一是更加谨慎控制您的帐户花销。作为决心的一部分,您需要创建目录,以便对过去 10 年的数字发票进行分类:

mkdir {2009..2019}_Invoices

工作完成。

或者你可能有数百个带编号的文件,比如从视频剪辑中提取的帧,或许因为某种原因,你只想从第 43 帧到第 61 帧每隔三帧删除一帧:

rm frame_{043..61..3}

很可能,如果你有超过 100 个帧,它们将以填充 0 命名,如下所示:

frame_000 frame_001 frame_002 ...

那就是为什么你在命令中要用 043,而不是43 的原因。

花括号花招

说实话,序列的神奇之处不在于双点,而是花括号({})的巫术。看看它对于字母是如何工作的。这样做:

touch file_{a..z}.txt

它创建了从 file_a.txtfile_z.txt 的文件。

但是,你必须小心。使用像 {Z..a} 这样的序列将产生一大堆大写字母和小写字母之间的非字母、数字的字符(既不是数字或字母的字形)。其中一些字形是不可打印的或具有自己的特殊含义。使用它们来生成文件名称可能会导致一系列意外和可能令人不快的影响。

最后一件值得指出的事:包围在 {...} 的序列,它们也可以包含字符串列表:

touch {blahg,splurg,mmmf}_file.txt

将创建了 blahg_file.txtsplurg_file.txtmmmf_file.txt

当然,在别的场景中,大括号也有不同的含义(惊喜吗!)。不过那是别的文章的内容了。

总结

Bash 以及运行于其中的各种工具已经被寻求解决各种特定问题的系统管理员们把玩了数十年。要说这种有自己之道的系统管理员是一种特殊物种的话,那是有点轻描淡写。总而言之,与其他语言相反,Bash 的设计目标并不是为了用户友好、简单、甚至合乎逻辑。

但这并不意味着它不强大 —— 恰恰相反。Bash 的语法和 shell 工具可能不一致且很庞大,但它们也提供了一系列令人眼花缭乱的方法来完成您可能想象到的一切。就像有一个工具箱,你可以从中找到从电钻到勺子的所有东西,以及橡皮鸭、一卷胶带和一些指甲钳。

除了引人入胜之外,探明你可以直接在 shell 中达成的所有能力也很有趣,所以下次我们将深入探讨如何构建更大更好的 Bash 命令行。

在那之前,玩得开心!


via: https://www.linux.com/blog/learn/2019/1/linux-tools-meaning-dot

作者:Paul Brown 选题:lujun9972 译者:asche910 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出