Bash 函数将终端输出稳健地通过管道传输到 vim

Zor*_*b29 7 vim command-line bash pipe

我经常以这种方式将我的终端输出重定向到 vim,我厌倦了一直输入:

ls | vim -
Run Code Online (Sandbox Code Playgroud)

我想定义一个函数v来为我做这件事,即我希望能够输入:

v ls 并且这以某种方式“扩展”到上一个命令。

我可以用这种脚本在简单的情况下做到这一点(当然,这可以重构为一个小函数):

#!/bin/bash 
 
touch crrt_cmd

while (( "$#" )); do 
  echo -n $1 >> crrt_cmd
  echo -n " " >> crrt_cmd
  shift 
done

chmod +x crrt_cmd
bash crrt_cmd | vim -

rm crrt_cmd
Run Code Online (Sandbox Code Playgroud)

这适用于非常简单的命令,例如lsor ls -l,但是当存在一些管道时它根本不起作用,例如。

关于如何执行类似操作的任何想法,但适用于任何有效的 bash 命令?


所以只是要清楚,如果可能的话,我希望事情以这种方式工作:

v ls | grep keyword | head -5
Run Code Online (Sandbox Code Playgroud)

会翻译成:

ls | grep keyword | head -5 | vim -
Run Code Online (Sandbox Code Playgroud)

Eli*_*gan 14

进程替换可能会提供与您想要的最接近的语法。

这有效,并且可能符合您的喜好:

vim <(cmd1 | cmd2 | ...)
Run Code Online (Sandbox Code Playgroud)

这使您可以将整个命令的文本保存在一起,并将其放在发送它的命令之后(而不是之前)vim,这似乎是您的主要目标。没有-争论是故意的。

您不需要为此定义函数或别名,但当然您可以通过定义为别名或函数来缩短vim为:vv

alias v=vim
Run Code Online (Sandbox Code Playgroud)
v() { vim "$@"; }
Run Code Online (Sandbox Code Playgroud)

v <(cmd1 | cmd2 | ...)比 多一个字符v 'cmd1 | cmd2 | ...'。但它也避免了穆鲁警告的引用地狱。', ", 和\可以出现在里面<( ),它们以通常的方式工作。这也很好嵌套:您可以在命令中使用其他括号,只要它们的使用方式在语法上是正确的。

这是过程替换。Bash 创建了一个可以通过类似路径访问的管道/dev/fd/63(不要与命令本身中涉及的管道混淆)。它将该管道的路径替换为<(cmd1 | cmd2 | ...),因此vim看到的文件名类似于/dev/fd/63. 您的命令 ,cmd1 | cmd2 | ...在子 shell 中异步运行,其输出被发送到管道,管道vim读取。(这就是为什么你不写-vim/dev/fd/63或最终被调用的任何东西中读取,而不是从标准输入中读取。)

穆鲁已经指出,你可以写一个别名用于扩展为vim <(

alias v='vim <('
Run Code Online (Sandbox Code Playgroud)

你可以把这个别名定义~/.bash_aliases或结束~/.bashrc

这让你更接近你最初想要的语法——你只需要)在命令的末尾写一个:

v cmd1 | cmd2 | cmd3)
Run Code Online (Sandbox Code Playgroud)

进程替换还有一个额外的好处,你可以多次使用它,<( )在同一个命令中编写多个构造,以防你想单独运行多个命令并在单独的vim缓冲区中查看它们的输出。

vim <(cmd1 | cmd2 | cmd3) <(cmd4 <infile) <(FOO=bar cmd5) <(cmd6 | cmd7)
Run Code Online (Sandbox Code Playgroud)

当然,正如穆鲁所说,对于运行管道并在 中打开其输出的用例,后缀操作仍然更简单vim,因为您只需要vim在管道末尾添加另一个命令。

由于穆鲁洞察力v可以由一个别名vim <(


最简单的案例:更好的方式来实现你的原创 v

vim <( ),上面详述,即使在最简单的情况下也可以使用。但是,当您不编写包含两个或多个命令的管道 ( cmd1 | cmd2)、将重定向应用到您的命令 ( cmd <infile) 或为持续时间或您的命令 ( FOO=bar cmd)分配环境变量时,这在语法上是矫枉过正的。所以你可能仍然想要一个函数来完成你原来的v函数所做的。

您的实现运行一个循环来连接参数的文本。这是复杂的,并且也打破在涉及引述大多数情况下,即使你修复$1,以"$1"防止初始分裂通配符。在 中v 'foo bar',您的v函数(与任何函数一样)不会收到任何引号:它会看到foo bar. 这很好,因为它将它作为单个参数接收......直到它构造一个包含它的脚本并运行该脚本,此时foo bar被解析为两个词,成为两个参数。

幸运的是,有一种更可靠的方法,它也更简单、更短。"$@"扩展到传递给当前函数或当前脚本的所有参数(如果不在函数中)。单独的参数既不会进一步拆分,也不会相互连接。所以你只需要:

v() { "$@" | vim -; }
Run Code Online (Sandbox Code Playgroud)

(当然,如果您要定义v做其他事情——也许作为上述的别名vim <(——那么您可能想将其称为其他事情,也许u。)

运行该命令定义了一个 shell 函数v,该函数运行在其第一个参数中命名的命令并传递其后续参数。这不会构造单独的脚本,也不会使用bash -ceval。所以它不会冒着错误地拆分或连接参数的风险,因为参数永远不会被拆分或连接:它们是单独进入函数,并"$@"单独使用它们。

您可以将其放在最后,~/.bashrc以便为您的交互式 shell 定义它。

或者,如果您更喜欢它是一个脚本,则使用以下内容创建一个名为v(或您希望命令名称的任何名称)的文件:

#!/bin/bash
"$@" | vim -
Run Code Online (Sandbox Code Playgroud)

将文件标记为可执行文件 ( chmod +x v) 并将其放在$PATH. 我建议~/bin,它会$PATH在您登录时自动添加到您的,如果它存在(除非您已更改~/.profile为不这样做)。


mur*_*uru 6

函数不能对bash 中的任意非简单命令执行此操作,因为函数本身就是一个简单命令 - 当函数定义开始发挥作用时,为时已晚。别名的扩展可以包含管道,因此您可以在别名中添加管道,但当然只能在命令的开头:

$ alias e='echo foo |'
$ e grep bar  # expands to echo foo | grep bar
Run Code Online (Sandbox Code Playgroud)

如果你选择了不可避免地遵循这样的疯狂:

v () { eval "$@" | vim -; }
# v 'ls -l | nc' 
Run Code Online (Sandbox Code Playgroud)

你迟早引用地狱的话。

进行后缀操作,它更简单。


疯狂的其他选择:

zsh 中的全局别名

您可以在 zsh 的命令行中的任何位置(而不仅仅是开头)扩展别名:

% alias -g v='| vim -'
% echo foo | grep foo v
Vim: Reading from stdin...
Run Code Online (Sandbox Code Playgroud)

  • Eliah Kagan 的进程替换想法很好,也可以用别名缩短(`alias v='vim &lt;('`, `v ls -l | grep foo)`) (2认同)