管道命令的一个很好的例子是什么?

如果您正在帮助某人学习命令行中的管道概念,您会使用哪个示例?实际出现的示例如下:

cat whatever.txt | less

我觉得那个不是最好的例子,主要是因为只有一个步骤。使用“|”的一个好但基本的例子是什么?
理想情况下,我将提供一个示例,其中使用的程序具有独立运行并显示管道连接的输出的能力。

3你的例子真的不怎么样 - 它基本上就是无用猫奖的提名。 - maxschlepzig
@maxschlepzig 不是说你错了,但你也没有什么帮助;你不需要用cat,直接使用less whatever.txt就可以了。 - Bora M. Alper
14个回答

我将为您演示一个有些复杂的例子,基于一个真实的场景。
问题
假设在我的桌面上,命令conky停止响应了,我想手动终止它。我对Unix有一点了解,所以我知道我需要执行命令kill 。为了获取PID,我可以使用ps或top或任何我Unix发行版提供的工具。但是如何用一条命令完成这个操作呢?
答案
$ ps aux | grep conky | grep -v grep | awk '{print $2}' | xargs kill

免责声明:此命令只在特定情况下有效。请不要复制/粘贴到您的终端并开始使用,可能会导致进程意外终止。相反,请学习如何构建它。
工作原理
1- `ps aux`
此命令将输出正在运行的进程列表和有关它们的一些信息。有趣的信息是,它将在第二列中输出每个进程的PID。这是从我的机器上运行该命令的输出摘录:
$ ps aux
 rahmu     1925  0.0  0.1 129328  6112 ?        S    11:55   0:06 tint2
 rahmu     1931  0.0  0.3 154992 12108 ?        S    11:55   0:00 volumeicon
 rahmu     1933  0.1  0.2 134716  9460 ?        S    11:55   0:24 parcellite
 rahmu     1940  0.0  0.0  30416  3008 ?        S    11:55   0:10 xcompmgr -cC -t-5 -l-5 -r4.2 -o.55 -D6
 rahmu     1941  0.0  0.2 160336  8928 ?        Ss   11:55   0:00 xfce4-power-manager
 rahmu     1943  0.0  0.0  32792  1964 ?        S    11:55   0:00 /usr/lib/xfconf/xfconfd
 rahmu     1945  0.0  0.0  17584  1292 ?        S    11:55   0:00 /usr/lib/gamin/gam_server
 rahmu     1946  0.0  0.5 203016 19552 ?        S    11:55   0:00 python /usr/bin/system-config-printer-applet
 rahmu     1947  0.0  0.3 171840 12872 ?        S    11:55   0:00 nm-applet --sm-disable
 rahmu     1948  0.2  0.0 276000  3564 ?        Sl   11:55   0:38 conky -q

2- grep conky

我只对一个进程感兴趣,所以我使用 grep 命令来查找与我的程序 conky 对应的条目。

$ ps aux | grep conky
 rahmu     1948  0.2  0.0 276000  3564 ?        Sl   11:55   0:39 conky -q
 rahmu     3233  0.0  0.0   7592   840 pts/1    S+   16:55   0:00 grep conky

3- grep -v grep

如你在第二步中所见,命令ps会在其列表中输出grep conky进程(毕竟它是一个正在运行的进程)。为了过滤它,我可以运行grep -v grep。选项-v告诉grep匹配所有不包含该模式的行。

$ ps aux | grep conky | grep -v grep
 rahmu     1948  0.2  0.0 276000  3564 ?        Sl   11:55   0:39 conky -q

NB:我想知道如何在单个grep调用中执行步骤2和3。

4- awk '{print $2}'

现在,我已经分离出了目标进程。我想要检索它的PID。换句话说,我想要检索输出的第二个单词。幸运的是,大多数(全部?)现代Unix都会提供某种版本的awk,这是一种处理表格数据的脚本语言。我们的任务变得非常简单,只需要使用print $2即可。

$ ps aux | grep conky | grep -v grep | awk '{print $2}'
 1948

5- xargs kill 我有进程ID。我只需要将它传递给kill命令。为了做到这一点,我将使用xargsxargs kill将从输入中读取(在我们的情况下是从管道中读取),形成一个由kill <items>组成的命令<items>是从输入中读取的任何内容),然后执行创建的命令。在我们的情况下,它将执行kill 1948。任务完成。
最后的话
请注意,根据您使用的Unix版本,某些程序的行为可能会有所不同(例如,ps可能会在第3列输出PID)。如果出现任何错误或差异,请阅读供应商的文档(或更好地阅读man页面)。此外,长管道可能很危险,请小心。在使用诸如killrm之类的命令时,不要做任何假设。例如,如果还有另一个名为'conky'(或'Aconkyous')的用户,我的命令可能会杀死他所有正在运行的进程!
我想说的是要小心,特别是对于长管道。最好像我们在这里做的那样,以互动的方式建造它,而不是做出假设,然后事后感到遗憾。

NB: 我很想知道如何在一次grep调用中完成步骤2和3。-> grep "conky -q" :) - Wolfy
3实际上,这只是一个不好的例子,因为你可以简单地执行kill $(pgrep conky) - Patrick
6我知道现在有点晚了,但你甚至可以进一步简化为pkill conky - strugee
2NB: 我很想知道如何在一次grep调用中完成步骤2和3。使用"-o pid,comm"代替"aux" - 这样更具可移植性,因为它符合POSIX标准。这样,grep进程将只显示为"grep"而不是"grep conky",因此它不会匹配自身。 - Random832
2NB: 我很想知道如何用单个grep命令完成步骤2和步骤3。 grep [c]onky是你要找的。 - AlexT

我最喜欢的是这个:

youtube-dl $1 -q -o - | ffmpeg -i - $2

这条命令从传入的$1参数中提取给定YouTube网址的视频,并将其作为$2参数指定的文件输出。请注意,文件会以静默模式-q通过STDOUT-o -输出,然后通过管道传递给ffmpeg并在那里作为输入使用-i -

对于Linux新手来说,这可能是一个实用的例子,说明命令行界面为什么比使用图形界面工具更方便和简单。我不确定从YouTube下载视频并将其音频转换为MP3需要多长时间。上述命令可以在几秒钟内完成这个任务。


3youtube-dl有一个选项可以只保存音频。我的常用命令是这样的,其中URL通过stdin输入:youtube-dl --extract-audio --audio-format mp3 -a -。这仍然是一个很酷的例子,但有更简单的方法来实现它。(它内部调用了ffmpeg。) - Brigand
3@FakeRainBrigand: 哈哈,好知道!但我有一个不应该内置的替代方案:youtube-dl $1 -q -o - | mplayer - 直接在mplayer中播放视频。我从我的笔记本电脑使用这个命令告诉我的服务器(连接到电视)播放视频。我必须添加 -display :0.0 -geometry 400x300+1200+200 让mplayer窗口出现在正确的屏幕上。 - Baarn

通常使用(也就是我大部分时候使用的方式)是当某种原因需要我通过多个工具来处理数据时。

所以我会说,管道的使用就像胶水一样将多个构建块(不同的UNIX工具)组合在一起。就像Ulrich所说的,sortuniq是常见的用法。

根据受众的不同,如果你想突出管道的这种用法,你可以例如这样开始:“嘿,这个教学大纲中有几个链接到一些有趣的PDF文件和讲义笔记,但其中一些是重复的。我能否以某种方式自动化处理这个问题?”

然后你可以展示如何使用lynx --dump --listonly获取链接列表,如何使用grep过滤以.pdf结尾的链接,如何使用colrmsed去掉lynx在每个URL左边写的数字,如何使用sortuniq去除重复项,最后如何使用wget -i -来获取文件(当然要使用--wait参数对服务器友好)。
我担心这是一个复杂的例子。另一方面,它可能有助于展示管道的强大之处,当你将其连接起来并让Shell一次性运行时。

2你也可以在GNU coreutils中使用sort -u - admirabilis

我不确切知道好的是什么,但通过grep进行管道操作可能是最常见的用法之一,紧随其后的可能是使用wc -l命令。(是的,grep有一个鲜为人知的-c开关。)
另一个常见的组合是| sort | uniq,因为uniq要求输入必须经过排序。

如果有的话,大多数人更喜欢使用... | sort -u - user28151

这是我脑海中首先想到的事情... mysqldump 是一个控制台应用程序,它将数据、模式和可选的存储过程和函数发送到标准输出。通常,它会被重定向到一个文件进行备份。 mysqldump > mydb.dump 这将给你一个未压缩的 SQL 脚本。为了节省空间,你可以使用 bzip2 进行压缩。 bzip2 mydb.dump 或者,你可以一步完成两个操作: mysqldump | bzip2 > mydb.dump.bz2 在上面的例子中,mysqldump 的标准输出被导入到 bzip2 中,然后将其输出重定向到一个文件。

1还要添加反向操作:bzcat mydb.dump.bz2 | mysql <选项> - manatwork

虽然在这个例子中你并不需要它,但是:

$ ps aux | grep -v grep | grep conky

...颠倒 greps 的顺序可以保持着色,但效率要低得多。假设在大型列表上,颜色并不重要。

此外,此网页提出:

https://stackoverflow.com/questions/9375711/more-elegant-ps-aux-grep-v-grep

Johnsyweb在2012年2月21日10:31回答: 通常的技巧是这样的: ps aux | grep '[t]erminal' 这将匹配包含terminal的行,而grep '[t]erminal'则不会! 它还适用于许多种类的Unix系统。
...但如果你要寻找一个单个字母(比如进程'X'),那么这个方法就不起作用了。

终于可以分享我大约一年半前制作的这个混乱的一行代码了...

while read in; do host "$in"; done < sites.txt | grep -iv "GOOGLE" | grep -E '1\.2\.3\.4|5\.6\.7\.8' | sed -e 's/has\ address\ 216.70.91.72//' | sed -e 's/has\ address\ 94.23.33.92//' | while read sites; do curl -sL -w "%{http_code} %{url_effective}\\n" "$sites" -o /dev/null; done | grep -ivE '4.*|5.*' | sed -e 's/200//' | sed -e 's/HTTP/http/'

它...

  1. 读取sites.txt文件
  2. 对每个网址运行"host"命令(事后来看,用dig +short会更容易)
  3. 删除包含"GOOGLE"的行 - 这些是mx记录
  4. 获取包含两个IP之一的行
  5. 从列表中获取每个网站的HTTP状态码
  6. 删除返回4xx或5xx的网站
  7. 从返回200的网站中去掉"200"
  8. 将"HTTP"替换为"http" - 纯粹是美观上的考虑,没有实际原因。

如果用一个Python脚本就能做得更好,我敢打赌。


1嗯...我不确定这是否是向新手解释管道的最干净和最简单的例子 ;) - Erathiel
2我的问题是,它的目的是什么? - ADTC
我有一个包含许多域名的文件,我需要查看它们是否在我继承而来的两台服务器上。这个程序会读取文件,使用"host"命令并清理输出,然后发送请求以查看是否返回4xx或5xx错误。如果是,它会丢弃该域名;如果不是,则将其输出并放入另一个文件中。 - tanner

这是我在工作中使用的一个多个管道命令的示例。它使用gawk来搜索MySQL的通用查询日志($OFILE),并查找任何被拒绝的登录。然后,它按名称对该列表进行排序,并将该列表传输到uniq,以计算出现次数,最后再次通过管道将其排序为数字列表...
gawk '{ for (x=1;x<=NF;x++) if ( $x~"Access" && $(x+4)~".*@.*") print $(x+4)}' $OFILE | sort | uniq -c | sort -n

管道与过滤器和翻译工具配合使用效果最佳

find /usr/bin/ |                #produce 
sed 's:.*/::'  |                #translate: strip directory part
grep -i '^z'   |                #filter   : select items starting with z
xargs -d '\n' aFinalConsumer    #consume  

这样,数据可以从一个程序流向下一个程序,无需一次性将所有数据存储在内存中,而是以缓冲区的方式进行传输。

以下是一个示例,我在xauth不可用时用于设置DISPLAY变量的方法...
export DISPLAY=\`who am i |awk '{print $NF}' | sed 's/[()]//g'`":0.0"

第一条命令获取所需数据,即主机名或 IP。 第二条命令只获取该数据(最后一个字段)。 最后,最后一条命令从数据中删除括号。