用fzf改善壳体工作流程

2021-04-01 19:37:56

在shell中工作通常涉及一次又一次地执行相同的命令;更改是执行命令的顺序,以及传递给命令的参数。改进shell工作流的一种方法是在命令的执行顺序中找到模式,并将这些模式提取为小脚本;这通常会有所帮助,是在如此强大的壳体中工作的一部分。另一种方法是了解如何添加参数并尝试简化这一点;这就是我将在这个博客文章中关注的是我的重点。

我的工作流程中的典型参数是文件名或Git分支名称(看着我的历史,Git是迄今为止我最常用的命令)。在手动中键入它们通常会繁琐和容易出错,这就是为什么我避免在尽可能做的原因。根据命令的不同,可能有标签完成可以帮助很多,但它并不总是最方便的。在此博客文章中,我将展示如何使用FZF作为替代方案。

FZF的基本功能非常简单:它从STDIN读取一组行,提供了一个用户界面来选择一个或多个行,并将所选行写入STDOUT。这听起来非常基本,但实际上非常强大。 FZF Wiki有很多关于如何有效使用该功能的示例。这是一个很好的资源,我已经拍摄了几个函数,在我的曲目中,几乎每天都使用它们。但在您自己的shell使用情况下,您经常会发现对您使用案例相当具体的工作流程;它们不足以在这样的Wiki上找到足够的一般性,但自动为自己的用途自动化它们仍然很有用。

为了展示我如何往往接近这个,我将描述我曾经遵循的四个shell工作流,然后展示如何用fzf写一个shell函数,使工作流程更方便。它们(在越来越复杂)中:

对于其中大多数,我将显示一个简单的函数,涵盖大多数用例,然后使用更多功能扩展此功能,使其更方便或强大。

我将我的Python虚拟环境保留在〜/ .venv的中央处。要激活其中一个环境,我曾经习惯过

可以使用像VirtualEnvWrapper这样的工具来改进此工作流,但它也是可以使用FZF轻松提升工作流的一个很好的例子。最简单的解决方案可以只是一行。

(将此添加到您的.bashrc以使其永久化),然后使用它如下

脚本的一个次要问题是,如果通过按CTRL-D退出FZF,脚本将失败

您可以选择忽略此内容,因为它具有所需的效果:未激活虚拟环境。但是修复程序也很简单:我们可以将FZF的结果存储在变量中,并且如果变量是非空的,则只需尝试激活虚拟环境。

我发现自己重复的另一种模式在清理我的Git分支机构期间。对于每个功能或实验,我在git中创建一个新的分支。一旦将该功能合并到主分支或不再需要实验,可以删除分支。但我通常不会立即删除分支。相反,分支机构堆积起来,直到它到达它使得与活动代码更加难以导航分支的点。当我实际来到清理分支机构时,工作流程看起来像这样。

有时我不确定分支是否可以真正删除,所以我首先需要运行git登录它以查看它实际包含的内容,然后按照上面删除分支。然后我重复此过程,直到删除所有陈旧的分支。

这个工作流是另一个很好的例子,可以使用fzf更轻松。这次我们将-Multi选项传递给FZF,允许使用< tab&gt选择多个条目。

此版本主要是有效的,但我们可以做一些改进。首先,git branch显示所有分支机构,包括目前检查的分支,标有*。由于您无法删除当前检查的分支,因此首先显示它并没有意义。我们可以通过将Git分支输出通过Grep - Invert-Match' \ *' \ *' \ *'

另一个问题是,我们将$ branches_to_delete除以git branch -d;我们需要这样做,因为Git需要将每个分支接收为单独的参数。如果您使用像Shellcheck这样的Linter,它将抱怨这条线,因为未引入的变量可能导致Globbing和Word Spritting。在我们的情况下,这是一个误报,因为分支名称不能包含Globbing字符;尽管如此,我认为尽可能避免未引发的变量是一个很好的做法。这样做的一种方法是将FZF的输出直接管制到Git分支-D中,而不是将其存储在变量中。如果我们向Xargs添加 - 不运行的IF-NAULE选项,如果至少选择了至少一个分支,则只会调用git。

最后,我提到它可能有助于查看所选分支的Git日志的输出。我们可以使用fzf的--preview选项来实现。 --preview的值可以是任何命令;每当在FZF中选择新行时,它将被执行,并且输出显示在预览窗口中。在命令中,{}充当用当前所选行替换的占位符。

请注意,我们还通过切割-c 3-将Git分支的输出管道,该删除每行的前两个字符。如果查看Git分支的输出,您将看到除了当前的每个分支,由两个空格为前缀。如果我们不会用剪切删除它们,则预览命令将被执行为git log'分支名称',由于领先的空格,因此引起了Git抱怨。 (替代方案是使用Git Log {..}作为预览命令,它将从所选行中剥离空格。)

以下是我们删除相同的三个分支的示例,但我们在我们这样做时获得更多信息。

对于代码审查,我经常发现签出审查的代码有助于。 GitHub CLI使其简单:如果执行GH Pr Checkout< pr-number>在GIT存储库中,它将通过数字< pr-number&gt检查拉出请求。到当地分支机构。但我们在哪里得到< pr-number>从?在我的工作流程中,我会

这适用于一个或两位数的数字,但即使是三位数字,我有时也必须切换回浏览器以确保我记得正确的数字。

在我的最后一个博客文章中,我已经描述了如何用gh查询github api;我们可以在此处使用它来获得< pr-number>自动地。具体来说,我们可以使用GitHub API获取打开的拉拔请求

它返回一系列JSON对象,一个用于每个拉出请求。我们需要将此阵列的JSON对象转换为FZF可以解释的格式:每个拉出请求的单独行。如果我们考虑我们需要的数据,那么第一件事就是我们要传递给GH结账的拉出请求号。我们还需要一些方法来识别我们感兴趣的拉申请,而标题是最明显的候选人。我们可以使用JQ的字符串插值来将此信息提取到JSON字符串中。

( - Raw-Output String评估JSON字符串;没有它,每行都会被引用字符包围。)例如,如果我在具有https://github.com/junegunn/结帐的目录中执行此操作FZF,它输出

#2368 - Ansi:加速解析大约7.5倍#2349 - 用于Cygwin 3.1.7和上面的Vim插件修复#2348 - [完成]使用FD的默认行为如果习惯使用。#2302 - 领先的双引用完全匹配+区分大小写搜索#2197 - 动作接受-1接受单一匹配#2183 - 修复质量问题#2172 - 草稿:介绍 - 印刷所选 - 计数#2131 - #2130允许sudo -e env fzf完成# 2112 - 添加arglist支持fzf.vim#2107 - 添加有关命令的指令与guix和/或guix system#2077 - 使用fzf-redraw-stult在历史小部件#2004 - milis linux支持#1964 - 使用tmux shell -command#1900 - 提示出现shell准备好#1867 - 添加{r} aw标志禁用模板中的引用#1802 - [zsh完成]递归延伸别名#1705 - 选择输入馈送的线索索引和选择输入源的线索索引和到输出光标线索引#1667 - $(...)应引用:\" $(...)\"#1664 - 添加有关使用Vundle#1616安装有关安装的信息 - 使用Vim - 特定的shell而不是环境变量#1581 - 添加前/后完成'钩子'#1439 - 抑制zsh自动完成行号输出#1299 - zsh完成:为每个命令完成触发添加支持。# 1245 - 尊重switchbuf选项#1177 - [zsh]让关键绑定通过zstyle#1154自定义 - 改善终止完成。#1115 - _fzf_complete_ssh:支持包括在ssh configs#559 - [vim]使用窗口局部变量来查找上一个窗口#489 - Bash:密钥绑定修复

如果我们将其送入FZF,则允许我们选择任何这些行并将其写入STDOUT。我们只对该号码感兴趣,因此我们使用SED和ReGEx与捕获组提取。我们的第一个工作脚本的版本如下所示。

这可能足以让大多数用例(并且GitHub博客有一个更简单的脚本,它直接使用GH的输出)。但也许标题是不够确定我们感兴趣的拉请求;更多信息可能有所帮助。例如,我们可以在预览窗口中显示拉拉请求的描述,或者我们可以从API获取的其他信息。

在上面删除分支的函数中,我们通过在当前所选分支上调用git log来填充预览窗口。第一个想法是在这里尝试类似的东西,并查询当前选择的拉请求的API。但是,每当我们选择不同的线时查询API会产生令人讨厌的延迟,并使难以使用。幸运的是,我们不需要再次查询API。我们已经拥有了第一个调用API的所有数据。我们可以做的是扩展我们的JQ模板字符串以提取我们关心的所有信息,然后使用FZF的功能,允许我们从选择窗口中的输入行隐藏一些信息并在预览中显示它。

FZF将每行视为一系列字段。默认情况下,字段由空格序列(选项卡和空格)分隔,但我们可以使用--delimiter选项控制分隔符。例如,如果我们设置--delimiter =','并将行首先传递,第二,第三到FZF,那么字段是第一个,第二,,第三个字段。本身,这尚未有用。但是使用--with-nth选项,我们可以控制选择窗口中显示的字段。例如,fzf --with-nth = 1,2将仅显示每行的第一和第二字段。此外,我们在上面看到,我们可以将{}写入{}作为预览命令中的占位符,而FZF将用当前选定的行替换它。但{}只是占位符的最简单形式。我们还可以在括号内指定字段指数,FZF将替换与这些字段的占位符。

以下是我们使用的示例--with-nth和--preview,使用< tab>作为分隔符。

FZF将在标签字符下拆分每行; --with-nth = 1选项指示fzf显示选择窗口中的第一部分;预览命令中的{2}将用第二部分替换,并且由于它被传递到回声,它将显示它。

我们将使用它来在预览窗口中显示一些有用的信息。让我们先看看最终脚本,然后我们将逐步浏览它。

简单的功能有一些变化。我们将JQ模板字符串解压缩到变量中并使用更多信息扩展:作者,创建拉拉请求时的时间戳以及上次更新的时间戳,以及提取请求的描述。所有这些信息都包含在GitHub API呼叫GH API' Repos /:Owner /:Repo / Pulls'

请注意,我们通过标签字符\ t将新信息与标题和标题分开。我们也使用它作为fzf的分隔符,然后在选择窗口中显示拉请求编号和标题(使用--with-nth = 1)和预览窗口中的其余部分(使用--preview =' echo -e {2}')。

另请注意,这次我们不使用JQ的 - 拖尾输出选项。原因有点微妙。我们使用JQ创建的字符串包含逃逸的换行符。如果我们将-raw-opput选项传递给JQ,它将解释所有转义的字符;特别是,它将在字符串中为任何\ n编写一个文字新行字符。例如,比较输出

第一个版本是有问题的。请记住,FZF是基于行的程序。它需要一行列表,允许用户选择其中一个或多个,并输出所选行。这意味着使用 - raw-output选项,每个拉出请求将显示为fzf中的多行;这显然不是我们想要的。因此,我们让JQ输出转义版本,以确保每个拉出请求对应于单行。

然而,这引入了新问题。首先,我们仍然想要预览窗口来显示文字新行字符,而不是\ n;我们通过使用echo -e作为预览命令来解决此问题(-e启用逃逸字符的解释)。第二个问题是,没有 - raw-output选项,JQ将在Start的开始结束时显示引号字符,并且它将将我们的分隔符\ t作为转义字符。我们通过手动删除引号来解决此问题,并通过实际标签字符替换第一个转义的选项卡字符;这就是JQ之后的东西正在做的事情。

最后,请注意我们指定--preview-window = top:包装以便在预览窗口中的fzf包裹线,并在屏幕顶​​部而不是右侧显示它。

我们看到了我们如何使用FZF删除Git分支。现在让我们看看相反的:创建新分支机构。在我的工作场所,我们正在使用JIRA进行问题跟踪。每个特征分支通常都对应于一些JIRA问题。要跟踪此对应关系,我将使用以下命名方案进行Git分支。假设JIRA项目名为博客,我目前正在使用标题“向启动脚本添加详细标志”问题博客-1232。然后我会命名我的分支博客-1232 / add-a-renbose-flag-the-startup脚本;描述部分通常提供足够的信息来确定分支对应的特征,并且博客-1232允许我允许我跳转到JIRA机票,如果我需要查找有关该问题的更多详细信息。

通常,我必须多次在浏览器和终端之间切换,并且我仍然可以在分支名称中创建拼写错误。

这是另一个工作流程可以完全自动化。与GitHub拉拔请求一样,我们可以从JIRA API获取问题。我们创建的函数类似于Pr-checkout,但存在一些显着的差异。

首先,没有方便的CLI工具像GH一样与JIRA API通信。我们必须直接使用卷曲与它交谈。其次,至少我使用的JIRA服务器不允许创建访问令牌,这在与API交谈时使用用户名和密码使用基本身份验证。我不想将密码存储在shell脚本中,或者实际上在任何未加密的文件中,所以我们将使用秘密工具进行更安全的密码存储。最后,分支名称的创建需要比简单文本提取更多的逻辑;我们将使用切割,sed和awk的组合。

让我们再次先查看最终脚本,然后尝试了解它的工作原理。

我们可以看到三个部分。首先,卷曲命令及其变量与JIRA API交谈。然后将API输出的转换成FZF读取的行;这部分与PR-Checkout中的相同。最后将FZF的输出转换为分支名称的所需格式。

与PR-Checkout相比的最大变化是Curl命令。我们使用JIRA搜索端点,该端点预计将为JQL(JIRA查询语言)字符串作为URL参数。在我的情况下,我对分配给我的博客项目的所有问题都很感兴趣,并将其标记为正在进行中。 JQL字符串包含空格,等于符号和括号,所有这些都不允许在URL中,因此需要编码它们。卷曲可以使用--data-urlencode选项自动执行此操作;由于此选项默认使用Post Post请求,因此我们需要添加--get选项将其切换回G获取请求。我们还使用-User选项,该选项指示CURL添加基本身份验证标题。最后,我们添加--Silent选项以省略任何进度信息和 - 构C抑制的选项以保存一些带宽。

然后,我们使用与上述相同的技术将JSON响应中的每个数组条目转换为单行,将搜索字符串和标签字符分开并将其传递给FZF以允许用户选择一个条目。 FZF的输出将是博客-1232等行。将详细标志添加到启动脚本< tab> {...预览部分}。我们使用剪切来剥离行的预览部分(默认情况下,剪切使用<作为分隔符,-f1指示它输出第一个字段),导致博客-1232。将详细标志添加到启动脚本。然后sed取代了第一个。通过标签字符,并通过a - (但保留我们的新引入的标签字符)替换任何非字母数字字符,该字符产生博客-1232< tab> add-a-renbose-flag-the-startup-script。最后,AWK拍摄字符串,将其拆分在标签字符上,将第二部分转换为小写,并将两个部件与/作为分隔符组合在一起。

我介绍了四个典型的shell工作流程,并显示了如何用fzf简化它们。由此产生的函数范围从一个简单的单行函数到更复杂的功能,具有API调用和非微不足道的逻辑,但所有这些都是共同的,因此它们在没有任何参数的情况下将几个步骤的工作流程降低到单个命令。

我呈现的确切工作流可能与您无关。但希望一般技术可能是:尝试观察你如何为命令添加参数,并查看是否可以自动化。参数可以是固定位置中的文件(如虚拟环境),或者它们可能是您可以通过另一个命令(如git分支)或通过API(如拉请求号码或JIRA标题)的参数。