我最喜欢的 bash 技巧和窍门

作者:Prentice Bisbal

bash,或称 Bourne again shell,是大多数 Linux 发行版中的默认 shell。bash shell 在 Linux 和 UNIX 用户中的普及并非偶然。它具有许多增强用户友好性和生产力的功能。不幸的是,除非您知道这些功能的存在,否则您无法利用它们。

当我第一次开始使用 Linux 时,我唯一利用的 bash 功能是使用向上箭头浏览命令历史记录。不久之后,我通过观察他人和提问学习了其他功能。在本文中,我想分享一些我多年来学到的 bash 技巧。

本文并非旨在涵盖 bash shell 的所有功能;那需要一本书,而且有很多书籍涵盖了这个主题,包括 O'Reilly and Associates 出版的Learning the bash Shell。相反,本文是我最常用的且离不开的 bash 技巧的总结。

花括号展开

我最喜欢的 bash 技巧绝对是花括号展开。花括号展开接受以逗号分隔的字符串列表,并将这些字符串展开为单独的参数。该列表用花括号,即符号 { 和 } 括起来,并且逗号周围不应有空格。例如

$ echo {one,two,red,blue}
one two red blue

如这个简单示例所示,使用花括号展开对用户没有太大帮助。事实上,上面的示例比简单地键入以下内容需要多键入两个字符

echo one two red blue

这会产生相同的结果。但是,当花括号括起来的列表紧邻另一个字符串之前、之后或内部出现时,花括号展开变得非常有用

$ echo {one,two,red,blue}fish
onefish twofish redfish bluefish

$ echo fish{one,two,red,blue}
fishone fishtwo fishred fishblue

$ echo fi{one,two,red,blue}sh
fionesh fitwosh firedsh fibluesh

请注意,括号内或括号与相邻字符串之间没有空格。如果包含空格,则会破坏规则

$ echo {one, two, red, blue }fish
{one, two, red, blue }fish

$ echo "{one,two,red,blue} fish"
{one,two,red,blue} fish

但是,如果空格用引号括起来在花括号外部或逗号分隔列表中的项目内,则可以使用空格

$ echo {"one ","two ","red ","blue "}fish
one fish two fish red fish blue fish

$ echo {one,two,red,blue}" fish"
one fish two fish red fish blue fish

您也可以嵌套花括号,但您也必须在此处谨慎使用

$ echo {{1,2,3},1,2,3}
1 2 3 1 2 3

$ echo {{1,2,3}1,2,3}
11 21 31 2 3

现在,在看了所有这些示例之后,您可能会对自己说:“哇,这些都是很棒的客厅技巧,但为什么我应该关心花括号展开呢?”

当您需要备份文件时,花括号展开变得非常有用。这就是为什么它是我最喜欢的 shell 技巧。当我需要在更改配置文件之前备份配置文件时,我几乎每天都会使用它。例如,如果我要更改我的 Apache 配置,我可以执行以下操作并节省一些打字时间

$ cp /etc/httpd/conf/httpd.conf{,.bak}

请注意,在左花括号和第一个逗号之间没有字符。这样做是完全可以接受的,并且在向现有文件名添加字符或当一个参数是另一个参数的子字符串时非常有用。然后,如果我需要在当天晚些时候查看我所做的更改,我可以使用 diff 命令并反转花括号内字符串的顺序

$ diff /etc/httpd/conf/httpd.conf{.bak,}
1050a1051
> # I added this comment earlier
命令替换

我喜欢使用的另一个 bash 技巧是命令替换。要使用命令替换,请将任何生成标准输出的命令括在括号内,并在左括号前加上美元符号,$(命令)。当为变量赋值时,命令替换非常有用。这在 shell 脚本中很典型,常见的操作是将日期或时间赋值给变量。它也方便将一个命令的输出用作另一个命令的参数。如果您想将日期赋值给变量,您可以这样做

$ date +%d-%b-%Y
12-Mar-2004

$ today=$(date +%d-%b-%Y)

$ echo $today
12-Mar-2004

我经常使用命令替换一次获取有关多个 RPM 软件包的信息。如果我想要列出名称中包含 httpd 的所有 RPM 软件包中的所有文件,我只需执行以下命令

$ rpm -ql $(rpm -qa | grep httpd)

内部命令,rpm -qa | grep httpd,列出所有名称中包含 httpd 的软件包。外部命令,rpm -ql,列出每个软件包中的所有文件。

现在,那些有 Bourne shell 经验的人可能会指出,您可以通过用反引号(也称为反撇号)包围命令来执行命令替换。使用 Bourne 风格的命令替换,上面的日期赋值变为

today2=`date +%d-%b-%Y`

$ echo $today2
12-Mar-2004

使用较新的 bash 风格的命令替换语法有两个重要的优点。首先,它可以更轻松地嵌套。由于开始和结束符号不同,因此内部符号不需要用反斜杠转义。其次,它更容易阅读,尤其是在嵌套时。

即使在 bash 是标准的 Linux 上,您仍然会遇到使用较旧的 Bourne 风格语法的 shell 脚本。这样做是为了为各种 UNIX 版本提供可移植性,这些版本并不总是可用 bash,但确实有 Bourne shell。bash 向后兼容 Bourne shell,因此它可以理解较旧的语法。

重定向标准错误

您是否曾经使用find命令查找文件,却发现您要查找的文件淹没在一堆权限被拒绝错误消息中,这些错误消息迅速填满您的终端窗口?

如果您是系统管理员,您可以成为 root 用户并以 root 用户身份再次执行find。由于 root 用户可以读取任何文件,因此您不再收到该错误。不幸的是,并非每个人都拥有所使用系统上的 root 访问权限。此外,除非绝对必要,否则成为 root 用户是不良做法。那么您能做什么呢?

您可以做的一件事是将输出重定向到文件。基本的输出重定向对于任何花费相当长的时间使用任何 UNIX 或 Linux shell 的人来说都应该不是什么新鲜事,因此我不会详细介绍输出重定向的基础知识。要保存 find 命令的有用输出,您可以将输出重定向到文件

$ find /  -name foo > output.txt

您仍然在屏幕上看到错误消息,但看不到您要查找的文件的路径。相反,它被放置在文件 output.txt 中。当 find 命令完成时,您可以cat文件 output.txt 以获取您想要的文件位置。

这是一个可以接受的解决方案,但还有更好的方法。您可以将错误消息重定向到文件,而不是将标准输出重定向到文件。这可以通过在重定向尖括号前面放置一个 2 来完成。如果您对错误消息不感兴趣,您可以简单地将它们发送到 /dev/null

$ find /  -name foo 2> /dev/null

这会显示文件 foo 的位置(如果存在),而不会显示那些令人讨厌的权限被拒绝错误消息。我几乎总是以这种方式调用 find 命令。

数字 2 代表标准错误输出流。标准错误是大多数命令发送其错误消息的位置。正常(非错误)输出发送到标准输出,可以用数字 1 表示。由于大多数重定向输出是标准输出,因此默认情况下输出重定向仅适用于标准输出流。这使得以下两个命令等效

$ find / -name foo > output.txt
$ find / -name foo 1> output.txt

有时您可能希望将错误消息和标准输出都保存到文件中。这通常在 cron 作业中完成,当您希望将所有输出保存到日志文件时。这也可以通过将两个输出流都定向到同一个文件来完成

$ find / -name foo > output.txt 2> output.txt

这可行,但同样,还有更好的方法来做到这一点。您可以使用 & 符号将标准错误流绑定到标准输出流。一旦你这样做,错误消息就会转到你重定向标准输出的任何地方

$ find / -name foo > output.txt 2>&1

关于这样做的一个注意事项是,绑定操作位于生成输出的命令的末尾。如果将输出管道传输到另一个命令,这一点很重要。这一行按预期工作

find -name test.sh 2>&1 | tee /tmp/output2.txt

但这行不起作用

find -name test.sh | tee /tmp/output2.txt 2>&1

这行也不起作用

find -name test.sh 2>&1 > /tmp/output.txt

我使用 find 命令作为示例开始了关于输出重定向的讨论,并且所有示例都使用了 find 命令。然而,这种讨论并不限于 find 的输出。许多其他命令可能会生成足够的错误消息,以掩盖您需要的一两行输出。

输出重定向也不限于 bash。所有 UNIX/Linux shell 都使用相同的语法支持输出重定向。

搜索命令历史记录

bash shell 最强大的功能之一是命令历史记录,它使您可以通过使用向上和向下箭头键在历史记录中上下导航来轻松浏览过去的命令。如果您要重复的命令在您执行的最近 10-20 个命令之内,这很好,但当命令在您的历史记录中回溯 75-100 个命令时,这会变得乏味。

为了加快速度,您可以通过按 Ctrl-R 以交互方式搜索您的命令历史记录。执行此操作后,您的提示符将更改为

(reverse-i-search)`':

开始键入您要查找的命令的几个字母,bash 将向您显示包含您到目前为止键入的字符串的最新命令。您键入的内容显示在提示符中的 ` 和 ' 之间。在下面的示例中,我键入了htt:

(reverse-i-search)`htt': rpm -ql $(rpm -qa | grep httpd)

这表明我键入的包含字符串 htt 的最新命令是

rpm -ql $(rpm -qa | grep httpd)

要再次执行该命令,我可以按 Enter 键。如果我想编辑它,我可以按向左或向右箭头键。这会将命令放在正常提示符下的命令行上,我现在可以像刚刚键入它一样对其进行编辑。对于历史记录中很远的带有大量参数的命令来说,这可以真正节省时间。

从命令行使用 for 循环

我想提供的最后一个技巧是从命令行使用循环。命令行不是编写包含多个循环或分支的复杂脚本的地方。但是,对于小型循环来说,它可以节省大量时间。不幸的是,我没有看到很多人利用这一点。相反,我经常看到人们使用向上箭头键返回命令历史记录,并为每次迭代修改上一个命令。

如果您不熟悉创建 for 循环或其他类型的循环,那么许多关于 shell 脚本编写的优秀书籍都讨论了这个主题。关于 for 循环的讨论本身就是一篇文章。

您可以通过两种方式以交互方式编写循环。第一种方式,也是我喜欢的方法,是用分号分隔每一行。一个简单的循环,用于备份目录中的所有文件,如下所示

$ for file in * ; do cp $file $file.bak; done

编写循环的另一种方法是在每行之后按 Enter 键而不是插入分号。bash 从 for 关键字的使用中识别出您正在创建循环,并使用辅助提示符提示您输入下一行。当您输入关键字 done 时,它知道您已完成,表示您的循环已完成

$ for file in *
> do cp $file $file.bak
> done
现在来点完全不同的东西

当我最初构思这篇文章时,我打算将其命名为“愚蠢的 bash 技巧”,并炫耀一些我学到的不寻常的、深奥的 bash 命令。从那时起,文章的基调发生了变化,但我想分享一个愚蠢的 bash 技巧。

大约五年前,我负责的一个 Linux 系统内存耗尽。即使是简单的命令(例如 ls)也失败并出现内存不足错误。解决这个问题的显而易见的解决方案是简单地重新启动。另一位系统管理员想查看一个可能包含问题线索的文件,但他不记得文件的确切名称。我们可以切换到不同的目录,因为 cd 命令是 bash 的一部分,但我们无法获取文件列表,因为即使是 ls 也会失败。为了解决这个问题,另一位系统管理员创建了一个简单的循环来向我们显示目录中的文件

$ for file in *; do echo $file; done

当 ls 不起作用时,这起作用了,因为 echo 是 bash shell 的一部分,因此它已经加载到内存中。这是一个针对不寻常问题的有趣解决方案。现在,谁能建议一种仅使用 bash 内置命令显示文件内容的方法?

结论

bash shell 具有许多强大的功能,可以简化用户的生活。我希望这篇我喜欢使用的 bash 技巧摘要向您展示了一些利用 bash 提供的强大功能的新方法。

Prentice Bisbal 于 1997 年 1 月开始使用 Linux,在 486 上使用 Red Hat Linux 4.0。自 1998 年以来,他一直专业地维护 Linux 系统。他是新泽西州中部一家制药公司的系统管理员。

加载 Disqus 评论