Unix管道之美

653人浏览 / 0人评论

Unix 哲学强调构建简单且可扩展的软件。每个软件都必须做一件事,并且把它做好。该软件应该能够通过一个通用接口——文本流——与其他程序协同工作。这是 Unix 的核心哲学之一,它使它变得如此强大和直观易用。

这是摘自The Unix Programming Environment

尽管 UNIX 系统引入了许多创新的程序和技术,但没有任何一个程序或想法可以使它运行良好。相反,使它有效的是编程方法,一种使用计算机的哲学。虽然这种哲学不能用一句话写下来,但其核心思想是系统的力量更多地来自于程序之间的关系,而不是程序本身。许多 UNIX 程序孤立地做一些非常琐碎的事情,但是,与其他程序结合起来,就变成了通用和有用的工具。

我认为这很好地解释了这一点。另外,请观看 Brian Kernighan的完整演讲,他解释了 UNIX 操作系统的基础知识,他还通过一个使用管道的示例。

不过,在这篇文章中,我想展示一些实践这一哲学的例子——一个人如何结合使用不同的 unix 工具来完成一些强大的事情。

例子:

示例 1 - 根据 git 存储库中的提交数量打印作者排行榜

让我们从一个简单的开始——显示一个基于提交数量排序的 git 仓库的作者/贡献者列表,并按降序对列表进行排序(大多数提交贡献在顶部)。当您从管道的角度考虑时,这是一项简单的任务。git log用于显示提交日志。我们可以将--format=选项传递给它并提及我们希望以何种格式显示提交。--format='%an'只需为每个提交打印作者姓名。

$ git log --format='%an'AliceBobDeniseDeniseCandiceDeniseAliceAliceAlice

现在我们可以使用该sort实用程序按字母顺序对它们进行排序。

$ git log --format='%an' | sortAliceAliceAliceAliceBobCandiceDeniseDeniseDenise

接下来我们使用uniq

$ git log --format='%an' | sort | uniq -c
    4 Alice
    1 Bob
    1 Candice
    3 Denise

根据 的uniq手册页:

uniq - 报告或省略重复行

从 INPUT(或标准输入)中过滤相邻的匹配行,写入 OUTPUT(或标准输出)。

所以uniq打印出重复的行,但只打印出相邻的行这就是为什么我们必须先将输出传递给sort-c标志在每行前面加上出现次数。

您可以看到输出仍然按字母顺序排序。所以现在剩下的就是按数字排序。sort在flag 中有一个标志-n它根据数值来考虑数字。

$ git log --format='%an' | sort | uniq -c | sort -nr
    4 Alice
    3 Denise
    1 Candice
    1 Bob

-r标志还包括在内以相反的顺序打印列表。默认情况下,它按升序排序。你有他们的——根据提交次数排序的作者列表。

示例 2 - 从/r/memes浏览模因并从/r/earthporn设置壁纸

您知道吗,只需将“ .json”附加到 reddit url 即可获得 json 响应,而不是通常的 html?这允许一个充满可能性的世界!其中之一是直接从命令行浏览模因(当然不完全是,因为实际图像将显示在 GUI 程序上)。我们可以简单地 curl 或 wget url – https://reddit.com/r/memes.json

$ wget -O - -q 'https://reddit.com/r/memes.json''{"kind": "Listing", "data": {"modhash": "xyloiccqgm649f320569f4efb427cdcbd89e68aeceeda8fe1a", "dist": 27, "children":[{"kind": "t3", "data": {"approved_at_utc": null, "subreddit": "memes","selftext": "More info available at....'......More lines......

我在这里使用 wget 是因为 Curl User-Agent 似乎得到了不同的对待。显然,您可以通过简单地更改“User-Agent”标头来解决这个问题,但我只是选择了wgetWget 有一个-O提供输出文件名。大多数采用此类选项的程序还允许其值-表示标准输出或输入,具体取决于上下文。-q选项只是告诉 wget 安静并且不打印进度状态之类的东西。现在我们得到了一个大的 JSON 结构来使用。现在,要在命令行上有意义地解析和使用此 JSON 数据,我们可以使用 jqjq可以被认为是JSON 的sedawk 。它有自己的简单直观语言,您可以从它的手册页中参考。

如果您查看响应 JSON,它看起来像这样:

{
    "kind": "Listing",
    "data": {
        "modhash": "awe40m26lde06517c260e2071117e208f8c9b5b29e1da12bf7",
        "dist": 27,
        "children": [],
        "after": "t3_gi892x",
        "before": null
    }}

所以这里我们有一些“Listing”类型的响应,我们可以看到我们有一个“children”数组。该数组的每个元素都是一篇文章。

这是“children”数组的元素之一:

{
    "kind": "t3",
    "data": {
        "subreddit": "memes",
        "selftext": "",
        "created": 1589309289,
        "author_fullname": "t2_4amm4a5w",
        "gilded": 0,
        "title": "Its hard to argue with his assessment",
        "subreddit_name_prefixed": "r/memes",
        "downs": 0,
        "hide_score": false,
        "name": "t3_gi8wkj",
        "quarantine": false,
        "permalink": "/r/memes/comments/gi8wkj/its_hard_to_argue_with_his_assessment/",
        "url": "https://i.redd.it/6vi05eobdby41.jpg",
        "upvote_ratio": 0.93,
        "subreddit_type": "public",
        "ups": 11367,
        "total_awards_received": 0,
        "score": 11367,
        "author_premium": false,
        "thumbnail": "https://b.thumbs.redditmedia.com/QZt8_SBJDdKLVnXK8P4Wr_02ALEhGoGFEeNhpsyIfvw.jpg",
        "gildings": {},
        "post_hint": "image",
        ".................."
        "more lines skipped"
        ".................."
    }}

我减少了data总共有 105 个项目。如您所见,您可以获取有关帖子的许多有趣的数据属性。我们感兴趣的是url帖子。这不是实际的 reddit 帖子的 url,而是帖子内容的 url。如果帖子 url 是你想要的,那就是permalink所以在这种情况下,该 url字段是 meme 图像的 url。

我们可以使用以下方法简单地获取每个帖子的所有 url 的列表:

$ wget -O - -q reddit.com/r/memes.json | jq '.data.children[] |.data.url'"https://www.reddit.com/r/memes/comments/g9w9bv/join_the_unofficial_redditmc_minecraft_server_at/""https://www.reddit.com/r/memes/comments/ggsomm/10_million_subscriber_event/""https://i.imgur.com/KpwIuSO.png""https://i.redd.it/ey1f7ksrtay41.jpg""https://i.redd.it/is3cckgbeby41.png""https://i.redd.it/4pfwbtqsaby41.jpg"......

忽略前两个链接,它们基本上是模组放置的粘性帖子,其“url”与“永久链接”相同。

jq从标准输入中读取并提供我们之前看到的 JSON。 .data.children指的是我之前提到的一系列帖子。And –.data.children[] | .data.url表示“遍历数组中的每个元素并打印每个元素的‘data’字段中的‘url’字段”。

所以我们得到了/r/memes的“热门”帖子的所有 url 列表 如果您想获得本周的“热门”帖子,那么您可以点击https://reddit.com/r/memes/top.json?t=week对于所有时间的顶级职位?t=all, 年?t=year等等。

一旦我们有了所有 URL 的列表,我们现在就可以将它通过管道传输到xargsXargs 是一个非常有用的实用程序,可以从标准输入构建命令行。这是 xarg 的手册页所说的:

xargs 从标准输入中读取项目,由空格(可以用双引号或单引号或反斜杠保护)或换行符分隔,并执行命令(默认为 /bin/echo)一次或多次,后跟任何初始参数通过从标准输入读取的项目。标准输入上的空行被忽略

所以运行类似:

$ echo "https://i.redd.it/4pfwbtqsaby41.jpg" | xargs wget -O meme.jpg -q

相当于运行:

$ wget -O meme.jpg -q "https://i.redd.it/4pfwbtqsaby41.jpg"

现在,我们可以将 URL 列表传递给图像查看器,例如 feh接受 eog URL 作为有效参数的图像查看器。

$ wget -O - -q reddit.com/r/memes.json | jq '.data.children[] |.data.url' | xargs feh

现在,feh 弹出模因,我可以使用箭头键浏览它们,就像它们在我的本地磁盘上一样。

铁丝网

或者我可以简单地使用 wget 下载所有图像,替换 fehwget上面的内容。

可能性是无穷无尽的。这个 reddit JSON 数据的另一个很好的用途是 将你的桌面壁纸设置为来自“热门”部分的/r/earthporn的最高投票图像 。

$ wget -O - -q reddit.com/r/earthporn.json | jq '.data.children[] |.data.url' | head -1 | xargs feh --bg-fill

然后,如果需要,您可以将其设置为大约每小时运行一次的 cron-job。我在这里使用head命令只打印第一行,这将是最受好评的帖子。就其本身而言,head似乎做了一些非常琐碎和无用的事情,但在这种情况下,与其他程序一起工作,它就成为一个重要的部分。

你看到 Unix 流水线的威力了吗?一行代码完成了所有工作,从获取 JSON 数据、解析并从中获取相关数据,然后再次从 URL 获取图像,最后将其设置为墙纸。

我用它做的另一件愚蠢的事情是每两个小时从 /r/memes 下载模因。这是在我的机器上设置为 cron 作业。现在我有大约 19566 个模因占用了我磁盘上的 4.5G。我为什么要那样做?不要问我…

示例 3 - 从 IMDb 列表中获取随机电影

让我们以一个简单的结束它。IMDb 具有允许您制作列表的功能。您还可以找到其他用户制作的列表。例如 -让您大开眼界的电影如果您附加/export到 url,您将获得某种格式的列表.csv

$ curl https://www.imdb.com/list/ls020046354/exportPosition,Const,Created,Modified,Description,Title,URL,Title Type,IMDb Rating,Runtime (mins),Year,Genres,Num Votes,Release Date,Directors1,tt0137523,2017-07-30,2017-07-30,,Fight Club,https://www.imdb.com/title/tt0137523/,movie,8.8,139,1999,Drama,1780706,1999-09-10,David Fincher2,tt0945513,2017-07-30,2017-07-30,,Source Code,https://www.imdb.com/title/tt0945513/,movie,7.5,93,2011,"Action, Drama, Mystery, Sci-Fi, Thriller",471234,2011-03-11,Duncan Jones3,tt0482571,2017-07-30,2017-07-30,,The Prestige,https://www.imdb.com/title/tt0482571/,movie,8.5,130,2006,"Drama, Mystery, Sci-Fi, Thriller",1133548,2006-10-17,Christopher Nolan4,tt0209144,2018-01-16,2018-01-16,,Memento,https://www.imdb.com/title/tt0209144/,movie,8.4,113,2000,"Mystery, Thriller",1081848,2000-09-05,Christopher Nolan5,tt0144084,2018-01-16,2018-01-16,,American Psycho,https://www.imdb.com/title/tt0144084/,movie,7.6,101,2000,"Comedy, Crime, Drama",462984,2000-01-21,Mary Harron6,tt0364569,2018-01-16,2018-01-16,,Oldeuboi,https://www.imdb.com/title/tt0364569/,movie,8.4,120,2003,"Action, Drama, Mystery, Thriller",491476,2003-11-21,Chan-wook Park7,tt1130884,2018-10-08,2018-10-08,,Shutter Island,https://www.imdb.com/title/tt1130884/,movie,8.1,138,2010,"Mystery, Thriller",1075524,2010-02-13,Martin Scorsese8,tt8772262,2019-12-27,2019-12-27,,Midsommar,https://www.imdb.com/title/tt8772262/,movie,7.1,148,2019,"Drama, Horror, Mystery, Thriller",150798,2019-06-24,Ari Aster

我们可以使用cut来决定我们需要打印哪些字段:

$ curl https://www.imdb.com/list/ls020046354/export | cut -d ',' -f 6TitleFight ClubSource CodeThe PrestigeMementoAmerican PsychoOldeuboiShutter IslandMidsommar

-d选项是为每个字段指定分隔符。字段用什么分隔?在这种情况下,它是一个逗号 ( ,)。-f选项是您要打印的字段编号。在本例中,第六个字段是电影的标题。这也会打印 csv 标题“Title”,因此要删除它,我们可以使用 sed '1 d'这意味着从输入流中删除1行。

然后我们可以将电影列表通过管道传输到shufShuf 只是随机打乱它的输入行并将其吐出。

$ curl https://www.imdb.com/list/ls020046354/export | cut -d ',' -f 6 | sed '1 d' | shufAmerican PsychoMidsommarSource CodeOldeuboiFight ClubMementoShutter IslandThe Prestige

现在只需将其通过管道传输到head -1or中sed '1 q',它只会打印第一行。每次你运行这个,你应该得到一个随机选择。

$ curl https://www.imdb.com/list/ls020046354/export | cut -d ',' -f 6 | sed '1 d' | shuf | head -1Source Code

现在假设您还希望 URL 与标题一起打印,没问题,cut允许您指定要打印的多个字段--field=LIST

$ curl https://www.imdb.com/list/ls020046354/export | cut -d ',' --field=6,7 | sed '1 d' | shuf | head -1Shutter Island,https://www.imdb.com/title/tt1130884/

但是这有一个问题,如果电影标题中有一个逗号,那么您将得到一个完全不同的字段值。克服这个问题的一种方法是使用像这样的 python 单行代码:

python -c 'import csv,sys;[print (a["Title"]) for a in csv.DictReader(sys.stdin)]'
$ curl -s https://www.imdb.com/list/ls020046354/export |\
    python -c 'import csv,sys;[print (a["Title"],a["URL"]) for a in csv.DictReader(sys.stdin)]'|\
    shuf | head -1Oldeuboi https://www.imdb.com/title/tt0364569/

这些只是几个示例,您可以使用管道在一行 shell 中完成很多事情。

全部评论

晴天下起了小雨
2017-10-01 18:00
很喜欢,果断关注了
wjmyly7336064
2017-10-01 18:00
相当实用,赞美了
橘大佬
2017-10-01 18:00
就是有些细节再到位点就好了…