Вызов vi через find | xargs ломает мой терминал. Почему?

137

При вызове vimчерез find | xargsвот так:

find . -name "*.txt" | xargs vim

вы получаете предупреждение о

Input is not from a terminal

и терминал с довольно плохим поведением потом. Почему это?

DevSolar
источник
11
Примечание: вы можете выполнить эту операцию полностью внутри vim, не используя ее findили xargsвообще не используя . Откройте vim без аргументов, затем запустите, :args **/*.txt<CR>чтобы установить аргументы vim из редактора.
Тревор Пауэлл
3
@TrevorPowell: За все эти годы vim не переставал меня удивлять.
DevSolar

Ответы:

100

Когда вы вызываете программу через xargs, стандартный ввод программы указывает на /dev/null. (Так как xargs не знает оригинальный stdin, он делает следующую лучшую вещь.)

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

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

Vim ожидает, что его stdin будет таким же, как его управляющий терминал, и напрямую выполняет различные связанные с терминалом ioctl на stdin. Когда все сделано /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 - но даже это делается недостаточно рано.)

grawity
источник
20
+1, а для TL; DR люди просто бегутstty sane
doc_id
@rahmanisback: другие ответы, плюс комментарий Тревора, в первую очередь предоставили способы избежать поломки терминала. Я принял ответ Гравити, потому что мой вопрос был «почему», а не «как избежать» - это покрыто другим вопросом, который фактически породил этот.
DevSolar
@DevSolar Понятно, но подумайте о разочарованных людях, таких как я, которые просто гуглят, как избавиться от такого поведения, а не - к сожалению, - имеют достаточно времени прямо сейчас, чтобы изучить «почему», что, тем не менее, очень интересно.
doc_id
4
когда мой терминал ломается, вот так, я использую resetвместо, stty saneи после этого он работает нормально.
Capi Etheriel
137

(Следуя объяснениям Гравити, это xargsуказывает stdinна /dev/null.)

Решение этой проблемы заключается в добавлении -oпараметра к xargs. От man xargs:

-o

      Откройте /dev/ttyкоманду stdin, как в дочернем процессе, перед выполнением команды. Это полезно, если вы хотите xargsзапустить интерактивное приложение.

Таким образом, следующая строка кода должна работать для вас:

находить . имя "* .txt" | xargs -o vim

GNU xargs поддерживает это расширение с некоторых выпусков в 2017 году (с длинным именем опции --open-tty).

Для более старых или других версий xargs вы можете явно передать их /dev/ttyдля решения проблемы:

find . -name "*.txt" | xargs bash -c '</dev/tty vim "$@"' ignoreme

(Здесь ignoremeнужно взять $ 0, так что $ @ - это все аргументы из xargs.)

Джеймс Макгиган
источник
2
Как бы вы создали псевдоним bash из этого? $@похоже, неправильно переводит аргументы.
Занеграй
1
@zanegray - вы не можете сделать псевдоним, но вы можете сделать его функцией. Попробуйте:function vimin () { xargs sh -c 'vim "$@" < /dev/tty' vim; }
Кристофер
Подробное объяснение того, как работает решение GNU xargs и зачем вам нужна фиктивная ignoremeстрока, см. В vi.stackexchange.com/a/17813
wisbucky
@zanegray, можешь сделать это псевдонимом. Цитаты хитры. Смотрите решение на vi.stackexchange.com/a/17813
wisbucky
The -J, -o, -P and -R options are non-standard FreeBSD extensions which may not be available on other operating systems.(Это не было доступно на macOS для меня, потому что я установил xargs из homebrew (GNU one))
localhostdotdev
33

Самый простой способ:

vim $(find . -name "*foo*")
trolol
источник
5
Главный вопрос был «почему», а не «как этого избежать», и он был удовлетворен два с половиной года назад.
DevSolar
5
Это, конечно, не работает должным образом, когда имена файлов содержат пробелы или другие специальные символы, а также представляет угрозу безопасности.
Деджей Клейтон,
1
Мой любимый ответ, потому что он работает для каждой команды, в которой перечислены файлы, а не только для поиска или подстановочных знаков. Это требует небольшого доверия, как указывает Дежей.
Трэвис Уилсон
1
Это не будет работать во многих случаях использования, для которых предназначен xargs: например, когда число путей очень велико (cc @TravisWilson)
Good Person
21

Это должно работать нормально, если вы используете опцию -exec при поиске, а не в xargs.

find . -type f -name filename.txt -exec vi {} + 
Крис Рэйт
источник
2
Хм ... хитрость есть +(вместо "обычного" \;), чтобы получить все найденные файлы в одной сессии Vim - опция, о которой я постоянно забываю. Вы правы, конечно, и +1 за это. Я пользуюсь vim $(find ...)просто по привычке. Тем не менее, я на самом деле спрашивал, почему операция по трубе портит терминал, и Гравити прибил это к его объяснению.
DevSolar
2
Это лучший ответ, и он работает как на BSD / OSX / GNU / Linux.
Кевинарпе
1
Кроме того, find - не единственный способ получить список файлов, которые должны редактироваться одновременно vim. Я могу использовать grep, чтобы найти все файлы с шаблоном и попытаться редактировать их одновременно.
Чандраншу
8

Вместо этого используйте GNU Parallel:

find . -name "*.txt" | parallel -j1 --tty vim

Или, если вы хотите открыть все файлы за один раз:

find . -name "*.txt" | parallel -Xj1 --tty vim

Он даже правильно работает с именами файлов, такими как:

My brother's 12" records.txt

Посмотрите вступительное видео, чтобы узнать больше: http://www.youtube.com/watch?v=OpaiGYxkSuQ

Оле Танге
источник
1
Доступен не повсеместно. Большую часть дня я работаю на серверах, где я не могу устанавливать дополнительные инструменты. Но все равно спасибо за подсказку.
DevSolar
Если вы можете сделать 'cat> file; chmod + x file ', тогда вы можете установить GNU Parallel: это просто скрипт на perl. Если вам нужны справочные страницы и тому подобное, вы можете установить их в своем домашнем каталоге: ./configure --prefix = $ HOME && make && make install
Ole Tange
2
Хорошо, попробовал это - но параллель не открывает все файлы, она открывает их последовательно . Это также довольно глоток для простой операции. vim $(find . -name "*.txt")проще, и вы получаете все файлы, открытые одновременно.
DevSolar
5
@DevSolar: Несколько не связано, но у обоих find | xargsи $(find)будут большие проблемы с пробелами в именах файлов.
Гравитация
2
@ Grawity Правильно, но не существует простого способа обойти это (что я знаю). Вы должны были бы начать возиться с $IFS, -print0и прочее, а затем покинули сферу решения командной строки , один выстрел , и достиг точки , где вы должны придумать сценарий ... есть причина , почему пробелы в именах файлов не рекомендуется ,
DevSolar
0

может быть, не самый лучший, но вот сценарий, который я использую (по имени vim-open):

#!/usr/bin/env ruby

require 'shellwords'

inputs = (ARGV + (STDIN.tty? ? [] : STDIN.to_a)).map(&:strip)
exec("</dev/tty vim #{inputs.flatten.shelljoin}")

будет работать с vim-open a b cи, ls | vim-openнапример,

localhostdotdev
источник
Что касается нескольких других ответов, обратите внимание, что реальный вопрос был «почему», а не «как этого избежать». (Для которого я все еще указал бы на комментарий Тревора по моему вопросу как наиболее надежный способ, который не требует сценариев, псевдонимов или чего-либо еще.)
DevSolar