通过 find 调用 vi | xargs 破坏了我的终端。为什么?

Dev*_*lar 153 vim find

调用vimthrough 时find | xargs,像这样:

find . -name "*.txt" | xargs vim
Run Code Online (Sandbox Code Playgroud)

你会收到一个警告

Input is not from a terminal
Run Code Online (Sandbox Code Playgroud)

以及之后的行为非常糟糕的终端。为什么


这个问题明确地是关于为什么,而不是关于如何避免。这是在别处被问到并回答的。

Jam*_*gan 165

(从grawity的解释继,这xargsstdin/dev/null。)

这个问题的解决方案是将-o参数添加到xargs. 来自man xargs

-o

      /dev/tty在执行命令之前 像在子进程中一样重新打开 stdin 。如果您想xargs运行交互式应用程序,这很有用。

因此,以下代码行应该适合您:

找 。-name "*.txt" | xargs -o vim

GNU xargs 从 2017 年的某个版本开始支持此扩展(带有长选项名称--open-tty)。

对于旧版本或其他版本的xargs,可以显式传入/dev/tty解决问题:

find . -name "*.txt" | xargs bash -c '</dev/tty vim "$@"' ignoreme
Run Code Online (Sandbox Code Playgroud)

(在ignoreme那里占用 $0,所以 $@ 是来自 xargs 的所有参数。)

  • 您将如何从中创建 bash 别名?`$@` 似乎没有正确翻译参数。 (2认同)
  • @zanegray——你不能做别名,但你可以把它变成一个函数。试试:`function vimin() { xargs sh -c 'vim "$@" &lt; /dev/tty' vim; }` (2认同)

use*_*686 108

当您通过 调用程序时xargs,程序的 stdin(标准输入)指向/dev/null。(因为 xargs 不知道原始的标准输入,它做了次好的事情。)

$ 真 | xargs filan -s
    0 chrdev/dev/null
    1 tty /dev/pts/1
    2 tty /dev/pts/1

$ 真 | xargs ls -l /dev/fd/

Vim 期望其 stdin 与其控制终端相同,并直接在 stdin 上执行各种与终端相关的ioctl。当在/dev/null(或任何非 tty 文件描述符)上完成时,这些 ioctl 毫无意义并返回 ENOTTY,它被默默地忽略。

  • 我对一个更具体的原因的猜测:在启动时 Vim 读取并记住旧的终端设置,并在退出时恢复它们。在我们的情况下,当为非 tty fd(文件描述符)请求“旧设置”时,Vim 接收所有值为空且禁用所有选项,并粗心地将其设置为您的终端。

    您可以通过运行vim < /dev/null,退出它,然后运行来看到这一点stty,这将输出大量的<undef>s。在 Linux 上,运行stty sane将使终端再次可用(尽管它丢失诸如 之类的选项iutf8,以后可能会引起一些小麻烦)。

您可以认为这是 Vim 中的一个错误,因为它可以打开/dev/tty以进行终端控制,但不能。(在启动过程中的某个时刻,Vim 将它的 stderr 复制到 stdin,这允许它读取你的输入命令——从一个为写入而打开的 fd——但即使这样也做得不够早。)

  • +1,对于 TL;DR 人员只需运行“stty sane” (21认同)
  • 当我的终端中断时,像这样,我使用 `reset` 而不是 `stty sane`,然后它就可以正常工作了。 (5认同)

小智 34

最简单的方法:

vim $(find . -name "*foo*")
Run Code Online (Sandbox Code Playgroud)

  • 主要问题是“为什么”,而不是“如何避免”,两年半前就得到了满意的回答。 (5认同)
  • 当然,当文件名包含空格或其他特殊字符时,这将无法正常工作,并且也存在安全风险。 (5认同)
  • 这不适用于许多用例 xargs 的设计目的是:例如,当路径数量非常多时(cc @TravisWilson) (2认同)

小智 22

如果您在 find 上使用 -exec 选项而不是管道到 xargs,它应该可以正常工作。

find . -type f -name filename.txt -exec vi {} + 
Run Code Online (Sandbox Code Playgroud)

  • 嗯...诀窍是`+`(而不是“通常的”`\;`)将所有找到的文件放入*一个* Vim 会话中——我*一直*忘记了这个选项。当然,你是对的,为此 +1。我只是出于习惯使用 `vim $(find ...)`。然而,我实际上是在问*为什么*管道操作把终端搞砸了,grawity用他的解释来说明这一点。 (2认同)
  • 这是最好的答案,它适用于 BSD/OSX/GNU/Linux。 (2认同)

Ole*_*nge 10

改用 GNU Parallel:

find . -name "*.txt" | parallel -j1 --tty vim
Run Code Online (Sandbox Code Playgroud)

或者,如果您想一次性打开所有文件:

find . -name "*.txt" | parallel -Xj1 --tty vim
Run Code Online (Sandbox Code Playgroud)

它甚至可以正确处理如下文件名:

My brother's 12" records.txt
Run Code Online (Sandbox Code Playgroud)

观看介绍视频以了解更多信息:http : //www.youtube.com/watch?v=OpaiGYxkSuQ

  • @DevSolar:有点不相关,但两者都`find | xargs` 和 `$(find)` 会在文件名中出现空格问题。 (5认同)
  • 好的,试过了 - 但并行不*不*打开所有文件,它确实*连续*打开它们。对于一个简单的操作来说也是相当的一口。`vim $(find . -name "*.txt")` 更简单,你可以一次性打开所有文件。 (2认同)
  • @grawity 正确,但没有简单的方法(我知道)。你必须开始摆弄 `$IFS`、`-print0` 和其他东西,然后你离开了一次性命令行解决方案的领域,达到了你应该想出一个脚本的地步......不鼓励文件名中的空格的原因。 (2认同)