惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Google DeepMind News
Google DeepMind News
TaoSecurity Blog
TaoSecurity Blog
T
Threat Research - Cisco Blogs
Cisco Talos Blog
Cisco Talos Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
T
Tenable Blog
C
Cybersecurity and Infrastructure Security Agency CISA
GbyAI
GbyAI
Microsoft Security Blog
Microsoft Security Blog
N
Netflix TechBlog - Medium
AWS News Blog
AWS News Blog
Security Latest
Security Latest
L
LINUX DO - 热门话题
Blog — PlanetScale
Blog — PlanetScale
T
Threatpost
Stack Overflow Blog
Stack Overflow Blog
Vercel News
Vercel News
S
Securelist
I
Intezer
D
Darknet – Hacking Tools, Hacker News & Cyber Security
H
Heimdal Security Blog
T
The Exploit Database - CXSecurity.com
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
The Hacker News
The Hacker News
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
H
Hackread – Cybersecurity News, Data Breaches, AI and More
U
Unit 42
The Register - Security
The Register - Security
NISL@THU
NISL@THU
S
Schneier on Security
M
MIT News - Artificial intelligence
The Last Watchdog
The Last Watchdog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Hugging Face - Blog
Hugging Face - Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Hacker News - Newest:
Hacker News - Newest: "LLM"
Y
Y Combinator Blog
Know Your Adversary
Know Your Adversary
罗磊的独立博客
MongoDB | Blog
MongoDB | Blog
美团技术团队
W
WeLiveSecurity
P
Privacy International News Feed
Forbes - Security
Forbes - Security
H
Hacker News: Front Page
小众软件
小众软件
博客园 - 【当耐特】
P
Proofpoint News Feed
P
Privacy & Cybersecurity Law Blog
T
Tor Project blog

卡瓦邦噶!

服务器高性能网络调优 | 卡瓦邦噶! 为何写作 | 卡瓦邦噶! 读《金阁寺》 | 卡瓦邦噶! 雨季又来 | 卡瓦邦噶! MTU Probe 引起的初始延迟 | 卡瓦邦噶! 3.5 秒的固定延迟问题 | 卡瓦邦噶! 学习网络的一点经验 | 卡瓦邦噶! ARP 问题诊断 | 卡瓦邦噶! 网络断断续续…… | 卡瓦邦噶! Piccolo P2P 镜像分发 | 卡瓦邦噶! 一起看电影 | 卡瓦邦噶! 《征服C指针》 | 卡瓦邦噶! 我的姥姥 | 卡瓦邦噶! Python的哲学 Python 3.5的新特性 学校不教的计算机课 垃圾回收(GC)的三种基本方式 在编程中体验纯粹的快乐 从《美丽新世界》谈自由 在快钱实习 迷人的嗓音和迷人的故事——《Sleepyhead》 Python 的十个自然语言处理工具 记一个愚蠢的bug 一年炉石传说的游戏体验 《以撒的结合:重生》网页版图鉴 分清 C++的指针、引用和数组 笑话三则 自由比皇帝更伟大——《悲惨世界》笔记 Git 10 周年访谈:Linus 讲述背后故事 用 0x3f3f3f3f 设定最大int值的优点 Joel给计算机系学生的建议 一个词法分析器的简单实现 怎样才算健康的生活方式 MacVim 配置攻略 学习培训课程的视频效果好吗? 语言的控制 可悲的大多数 CSS样式思维导图 2014年终总结 奥巴马成为首位写程序的美国总统 青岛老城区的下水道好在哪里? 做优秀 UI 的七个建议(第二部分) 选择爱情的骑士 Git简明教程 Java集合总览 读 《1984》 java问答:终极父类(六)——等待/唤醒和接口 Java 问答:终极父类(五)——toString() Hyperlapse快速视频背后的技术细节 平庸之恶 Java程序员须知的七个日志管理工具 苏州 逛书摊随想 关于考试作弊 你的工作不仅仅是编程 推荐在线学习Java的英文资源 关注女性命运——《千禧年三部曲》 用好你的幻灯片——《演说之禅》 使用ReentrantLock和Lambda表达式让同步更纯净 新手学编程,从哪里开始? 程序员都是工程师吗? 不要学习代码,要学会思考 Java 问答:终极父类(四)——hashCode() Java 问答:终极父类(三)——finalize()和 getClass() 程序员职业之路的选择 我是一名摄影家 写给何小树的城市指南 为什么一些语言会比别的快? Java的常见误区与细节 跟朋友在一起玩游戏 死神永生——读《三体》 五种类型的程序员 创业圣经——读《黑客与画家》 纪念加西亚·马尔克斯 Junit中处理异常的另一种方式:catch-exception Java8采用Martin Fowler的方法创建内部DSL Linux HotSopt虚拟机GC线程的CPU占用率 J2EE概念介绍 如何成为一名黑客 Java 问答:终极父类(二)——equals()方法 为什么我喜欢Java Java 问答:终极父类(一)——clone()方法 七个改变世界的Java项目 java中默认类型转换的小问题 传统与创新 欢迎来到互联网 莫言和马尔克斯——读《生死疲劳》 位运算的妙用 读《人为什么活着》 写博客教会我的事情 中国特色操作系统 2013年总结 《永不妥协》影评 恨不相逢未嫁时——《廊桥遗梦》影评 如何优雅地使用PPT 给明年依然年轻的我们 天才与柱子 黑客守则和黑客精神 Looking for Freedom——《被解救的姜戈》 简洁之道
在终端快速选中上一个命令的输出内容
laixintao · 2021-06-12 · via 卡瓦邦噶!

在终端里面一个非常日常的操作,就是选中上一个命令的输出内容。比如使用文本处理程序处理一些服务器的 IP,最后得到一个结果,将这个结果复制给同事,或者粘贴到工单系统中;在终端上使用 date 程序转换日期的格式,最后要将这个日期复制到别的地方去使用;比如最常见的一个操作,使用 date +%s 命令得到当前的时间戳,然后复制这个时间戳。如果你使用终端的话,基本上每天都要重复这个操作几十次。

本文就来讨论这个最简单的操作:复制上一个命令的输出结果。

虽然是一个看似很简单的操作,但是我却为了如何能在这个操作上节省几秒钟苦苦思索了多年。也发现了很多人同样在寻找一个方法来高效地执行这个操作。这篇文章将会讨论几种方法来实现这个动作,虽然最后我使用的方法并不是我发明的。发明它的人也同样花了很长的时间(按作者原话说 “Look, it’s still quarantine, okay?”),所以背后的奇技淫巧和神奇的思路也同样精彩!希望这篇文章能给读者每天节省几秒钟,也能在阅读的过程中带来一些乐趣。

笨蛋的方法

这是最显而易见的一种方法。为了复制上一个命令得到的内容,我们要将右手拿开,放到鼠标上,选中文本,然后按下 Command + C (在其他的系统上是别的按键),将内容复制到剪切板里面。

也同样显而易见,这么做太浪费时间了。首先,所有需要将手从键盘上拿开的操作都是浪费时间的;其次,选中操作也不是那么简单,开头和结尾需要定位两次。使用键盘是一个 0 或 1 的操作。按下了就是按下了,没有按就是没有按,闭上眼睛也能操作。而鼠标需要精确地定位,闭上眼睛是绝对无法完成的。如果遇到要复制的命令有很长的输入(比如要复制一段 cat info.log | grep code=404 | tail 的日志输出),那么要同时使用鼠标进行定位和翻页,变成了一个超高难度的动作。

这样增加心智的东西,太反人类了。

朴素的方法

Unix 系统里面的管道真是一个伟大的发明!因为在终端里面程序的输出是一个 stdout, 所以理论上,我们就可以使用一个程序,将它的 stdout 导入到系统的剪切板里面去。比如在 Mac OS X 上面可以使用 pbcopy 将程序的输出内容导入到剪切板中,然后使用 pbpaste 粘贴出来,或者直接使用 Command + V 粘贴。

在其它的 Linux 系统中也可以做到类似的事情,比如 xselxclip 。其实原理非常简单,只需要调用系统提供的剪切板相关的 API,将 stdin 的内容写入到进去就好了。

类似这样使用管道的工具还有很多,比如 fpp 工具。可以自动地识别出来 stdout 中的 file path,然后提供一个 GUI 让你选择文件,按下 Enter 打开。比如 git stat | fpp 这样用。

这种方法的优点是可靠,不涉及鼠标操作。虽然也并不是特别高效,因为要敲很多字母(不过可以使用 alias)。

这类使用管道的最大的缺点就是不是所见即所得的。很多时候需要敲下命令,看到 stdout 确认没有问题,然后再敲一遍命令后面加上 | pbcopy 加到剪切板中,在遇到运行时间很长,或者需要消耗很大资源的时候,就有点不合适了。虽然可以一次性使用 tee 程序既输出到 stdout 又输出到 pipe 中,但是这样一来运行命令的心智负担又太大了。这么长的命令难以形成肌肉记忆,所以本质上来说,效率也算是特别高。

优雅的方法

另一个既简单又傻瓜的方法是使用 iTerm2 自带的功能,在 iTerm2 中选择 “Edit -> Select Output of Last Command” 即可选中上一条命令的输出,使用快捷键的话是 Command + Shift + A .

如果你看到这个选项是灰色的,说明你没有安装 shell 集成。在菜单栏选择 “Install Shell Integration” 即可,iTerm2 会帮你执行一个 Curl xxx | bash 来安装相关的依赖。

这种方法的优点是使用足够简单,一个快捷键就够了,而且这是选中+复制,并不需要再按下 Command + C。如果大部分时间使用的终端模拟器都是 iTerm2 的话,这个方法也足够了。

缺点也显而易见,这是 iTerm2 提供的功能,如果你要使用 Ubuntu,就不行了。另外,它的工作原理是,它知道你在 iTerm2 中运行的命令,所以可以捕获命令的输出信息。这样就带来一些很严重的问题,比如,如果你使用 Tmux 的,那么在 iTerm2 看来,无论你在 Tmux 里面开多少个 session 和 window, 对它来说都是一个程序,也就无法在 Tmux 里面成功捕获 stdout 了。

Tmux 可能有人不用,那还有一个场景应该无法避免,就是 ssh. 同理,你 ssh 到一台机器上去执行命令,对于 iTerm2 来说它都只看到一个 ssh 命令,所以如果这样复制的话,它会把你在 ssh 命令下看到的所有内容都复制下来。(其实上面提到的 pbcopy 同理,也无法在 ssh 远程机器上工作的。)

而要想在 ssh 下也工作,就必须不区别是在远程机器上执行的命令,还是本地执行的命令,从整个终端模拟器的 buffer 入手。使用正则匹配或许是个好的方法。

黑客的方法

由于没有一个方法能够省心省力地完成这个工作,我这几年来每天都过得郁郁寡欢。

某天在 hackernews 上看有人分享 Tmux 复制文本的操作方法,就点进去读了一下,稍微有些失望,因为这些东西我已经知道了。但是这时候网页突然载入完成了播客中的 gif,在 gif 中发现有一段竟然是在命令的 output 之间跳来挑去!这就是我苦苦寻找多年的东西!

在确认这并不是 Tmux 本身的功能之后,我发邮件问了作者是如何做到能在 Tmux 里面快速选择上一命令输出的。

没想到作者很快回复了我的邮件。

整个 idea 非常简单,使用一个脚本即可实现,只用到了 Tmux 自身的命令。核心思想是去复制当前 cursor 所在的 Shell Prompt 和上一个 Shell Prompt 之间的内容,使用 Tmux 的命令控制光标移动,选择文字。

脚本如下(现在作者有一篇博客,Quickly copy the output of the last shell command you ran ,很详细地介绍了这个脚本每一步都在干什么)。

Shell

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

bind -n S-M-Up {

  copy-mode

  send -X clear-selection

  send -X start-of-line

  send -X start-of-line

  send -X cursor-up

  send -X start-of-line

  send -X start-of-line

  if -F "#{m:*➜\u00A0*,#{copy_cursor_line}}" {

    send -X search-forward-text "➜\u00A0"

    send -X stop-selection

    send -X -N 2 cursor-right

    send -X begin-selection

    send -X end-of-line

    send -X end-of-line

    if "#{m:*➜\u00A0?*,#{copy_cursor_line}}" {

      send -X cursor-left

    }

  } {

    send -X end-of-line

    send -X end-of-line

    send -X begin-selection

    send -X search-backward-text "➜\u00A0"

    send -X end-of-line

    send -X end-of-line

    send -X cursor-right

    send -X stop-selection

  }

}

bind -n 的意思是将这个操作绑定到 root key table,默认是绑定到 Prefix table,改成绑定到 root 的话,这个操作就不涉及按下 Tmux 的 Prefix key 了。S-M-Up 是 Shift + Option + Up 这三个键一起按下的意思,即将这三个键一起按下绑定成下面这个脚本。

然后这个脚本进入 copy-mode,先控制光标到行头。之后分成两个 block,首先看 if 不满足的下面的那个 block,基本上就是向前寻找之前的一个 Shell Prompt,如果找到了,就从这里开始复制,这样,两个 Shell Prompt 之间的内容就被选中了。再来看 if 里面的内容,意思是当前行如果有 Shell Prompt 的话,就直接复制整行。这样就可以做到,依次往上选中上一个 output,上一个命令, 再上一个 output,再上一个命令,…… 缺点就是只能支持向上选择,不支持向下选择。不过其实也够用了。if 里面的那个嵌套的 if 是处理 Tmux 在 vi 的 copy-mode 下的一个 Corner case, 详细的解释可以去看原文。

这里有一个很 triky 的地方,就是如果你的 Shell Prompt 的格式里面有空格的话,比如以 $␣ 来结尾,在 Tmux 的复制模式下,对于没有执行过命令的行,比如多按了几次回车,Tmux 会直接将这些行中 Shell Prompt 的空格删除,这样就造成我们的脚本无法匹配到空格。比如下面这个 Shell,复制模式下在 date 和 echo 命令中间的三行就没有空格了。

这里解决的方法是,将 Shell Prompt 最后的空格,改成 Non-breaking space, Unicode 码是 \u00A0 。(可以看到,上面的脚本匹配的其实就是这个 Unicode)。如果使用 Vim 的话,可以在输入模式下按下 Ctrl + V ,进入ins-special-keys mode, 然后依次输入 u 0 0 a 0,就可以输入这个 Unicode。

这样,对我来说几乎就是一个完美的方案了。如果去读作者的博客,就会发现这里面的坑实在太多了,Tmux 在 vi 的 copy mode 下的行为,去掉空格的行为,跳转行为(在行被 Wrap 的情况下必须执行两次 start-of-line 才能真正跳转到行头,等等。估计作者也是花了很多时间才写好这个脚本。

在 ssh 的情况下,理论上也可以做到,因为这个方法是针对 Tmux 显示的 buffer 进行操作。但是要改下这个脚本的匹配,因为远程的主机的 Prompt 可能和你本地电脑不一样。上面的脚本使用的 search-forward-text ,如果改成 search-forward 就可以按照正则搜索。

没有实现的方法

这个方法是我很久之前做的一个尝试,只不过到现在都还没有完成。

之前看到过这么一个项目:tmux-url-select。它能帮助你快速选择当前 Tmux 窗口中的 URL,复制或者打开。

我去看了一下代码,发现思路非常神奇。它是这么做的:

  1. 先 capture 下来当前窗口的全部内容;
  2. 打开一个新的窗口,覆盖掉了原来的窗口;
  3. 新的窗口其实是一个新的 GUI 程序,然后将老窗口的内容放上来,在这个程序内实现了选择、跳转、定义按键等工作;

由于用户实际上是进入了一个新的程序,但是这个新的程序通过修改 Tmux 窗口的名字,让用户还感觉自己在 Tmux 中一样。由于是一个新的程序,那它就可以不受限制的做到任何是事情了!

所以我看到这个东西,第一个想法就是将它 fork 过来,将选择 URL 改成选择上一个命令的 output. 实际上也应该是可行的。到现在没有完成的原因是…… 这个项目是 Perl 写的,Perl 看起来不像是人写的东西。

Ian (上文提到的作者)也说,思路很有趣,但是长期来看,不如花时间提升 Tmux 自身的 copy mode 收益更多。他准备提交 patch 给 Tmux.

其他的方法

在和作者 Ian 的交流中,他还告诉我其他一些他使用的工具,也非常实用。

tmux-thumbs 这个很有意思。这个就像是 vimium/vimperator 的操作模式一样,可以让你快速通过一些按键去选择当前 buffer 的文本块。

extrakto 和上面的工具类似,但是这个工具是用的 fzf 模式。可以通过模糊搜索,查找当前 buffer 出现过的文本,进行快速选中。但是好像不能复制出到剪切板。


2021年07月06日更新:

我发现在服务器上复制命令的时候,连同命令本身以及 prompt 一起复制更加实用,因为我们的 prompt 带有机器的 hostname,包括机器标签,ip 等。复制命令可以让同事知道这个 output 是怎么来的,打开的文件路径?awk?grep?都可以一眼就能看出来。所以我将这个脚本改了一下,← 是只复制 output,↑ 是向上复制 prompt、命令以及 output,↓ 也一样,只不过是向下选择。

详情见:https://twitter.com/laixintao/status/1412081667498332161

实现代码如下( commit ,可以关注下 myrc 这个仓库,我将我的所有配置文件都放在这里,从这里可以看到最新的版本):

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

bind -n S-M-Up {

  copy-mode

  send -X clear-selection

  send -X start-of-line

  send -X start-of-line

  send -X cursor-left

  send -X begin-selection

  send -X search-backward "(\\$ )|(# )"

  send -X start-of-line

  send -X start-of-line

  send -X stop-selection

}

bind -n S-M-Down {

  copy-mode

  send -X clear-selection

  send -X end-of-line

  send -X end-of-line

  send -X search-forward "(\\$ )|(# )"

  send -X start-of-line

  send -X start-of-line

  send -X begin-selection

  send -X search-forward "(\\$ )|(# )"

  send -X search-forward "(\\$ )|(# )"

  send -X start-of-line

  send -X start-of-line

  send -X cursor-left

  send -X stop-selection

}