xargs и vi - «Вход не из терминала»

14

У меня есть около 10 php.iniфайлов в моей системе, расположенных повсюду, и я хотел быстро просмотреть их. Я попробовал эту команду:

locate php.ini | xargs vi

Но viпредупреждает меня, Input is not from a terminalи затем консоль начинает становиться действительно странной - после чего мне нужно нажать, :q!чтобы выйти, viа затем отключиться от сеанса ssh и повторно подключиться, чтобы консоль снова работала нормально.

Я думаю, что я вроде понимаю, что здесь происходит - в основном, команда не завершилась при viзапуске, поэтому, возможно, команда не завершила и viне думает, что терминал находится в нормальном режиме.

Я понятия не имею, как это исправить. Я искал Google, а также unix.stackexchange.com с неудачей.

УХО
источник
Как примечание стороны, вы можете запустить resetдля сброса вашего терминала, когда он облажался (вам не нужно отключаться от сеанса SSH).
wisbucky

Ответы:

12
vi $(locate php.ini)

Примечание: это будет иметь проблемы, если пути к вашим файлам имеют пробелы, но это функционально эквивалентно вашей команде.
Следующая версия будет корректно обрабатывать пробелы, но она немного сложнее (хотя новые строки в именах файлов все равно будут ее разбивать)

(IFS=$'\n'; vi $(locate php.ini))


Объяснение:

Происходит то, что программы наследуют свои файловые дескрипторы от процесса, который их породил. xargsего STDIN подключен к STDOUT locate, так viчто понятия не имеет, что на самом деле представляет собой оригинальный STDIN.

Патрик
источник
2
xargs замечательный, один из моих любимых инструментов - он просто не подходит для использования с программами, которые используют stdin для чего-либо кроме подачи данных. Мне нравится ваш ответ и ваши объяснения кроме этого, так что +1 в любом случае :)
cas
@CraigSanders Мне не нравится, потому что слишком легко злоупотреблять (использовать ненадлежащим образом) и в конечном итоге сломать. Я никогда не сталкивался ни с чем, что мне абсолютно необходимо было использовать, xargsпоскольку это нельзя было сделать напрямую с помощью оболочки (или find). Однако я могу вспомнить случаи, когда это было бы лучшим решением. Так что, пока вы понимаете, что xargsделаете, как он разделяет аргументы, как запускает программу и т. Д., И правильно ли используете ее, я бы сказал: «П
Патрик,
это не может быть разбито для таких вещей, как ... | awk '{print $3}' | xargs | sed -e 's/ /+/g' | bc(сложить все значения поля 3). или с помощью sed -e 's/ /|/g'построения регулярного выражения. и да, как и любой инструмент, вам нужно знать, как его использовать и каковы его ограничения и предостережения.
CAS
vi $(...)Подход также имеет проблемы с групповыми символами в других , чем снарядах zsh.
Стефан Шазелас
Также обратите внимание, что при xargsподходе к проблеме пробелов имена файлов с одинарными, двойными и обратными слешами также являются проблемой.
Стефан Шазелас
10

Этот вопрос ранее задавался на форуме Super User .

Цитирую ответ @ grawity на этот вопрос:

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

Vim ожидает, что его stdin будет таким же, как его управляющий терминал, и напрямую выполняет различные связанные с терминалом ioctl на stdin. Когда это делается в / dev / null (или любом не-tty файловом дескрипторе), эти ioctl не имеют смысла и возвращают ENOTTY, что молча игнорируется.

Это упомянуто на страницах руководства для xarg. Из OSX / BSD:

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

Следовательно, в OSX вы можете использовать следующую команду:

find . -name "php.ini" | xargs -o vim

Пока нет прямого переключения на версию GNU, эта команда будет работать. (Обязательно включите dummyстроку, в противном случае будет удален первый файл.)

find . -name "php.ini" | xargs bash -c '</dev/tty vim "$@"' dummy

Вышеуказанные решения предоставлены Jaime McGuigan на SuperUser . Добавление их сюда для любых будущих посетителей, ищущих сайт по данной ошибке.

darnir
источник
3
+1 спасибо за подсказку -o. Я использую xargs в течение многих лет и никогда не замечал этого ... просто проверил страницу руководства в моей системе, потому что это не функция GNU xargs. Страница man предоставляет xargs sh -c 'emacs "$@" < /dev/tty' emacsболее гибкую и переносимую альтернативу (хотя для GNU довольно забавно предпочитать переносимость функциям :).
CAS
2

С GNU findutilsи оболочкой с поддержкой подстановки процессов (ksh, zsh, bash) вы можете сделать:

xargs -r0a <(locate -0 php.ini) vi

Идея состоит в том, чтобы передать список файлов, -a filenameа не через стандартный ввод. Использование -0гарантирует, что оно работает независимо от того, какие символы или не символы могут содержать имена файлов.

С помощью zshвы можете сделать:

vi ${(0)"$(locate -0 php.ini)"}

(где 0флаг расширения параметра для разделения на NUL).

Однако обратите внимание, что в противоположность xargs -rэтому все еще работает viбез аргумента, если файл не найден.

Стефан Шазелас
источник
0

Редактировать несколько php.ini в одном редакторе?

Пытаться: vim -o $(locate php.ini)

маргаритка
источник
0

Эта ошибка возникает, когда vim вызывается и подключается к выходу предыдущего конвейера, а не к терминалу и получает неожиданный ввод (например, NUL). То же самое происходит при запуске:, vim < /dev/nullтак что resetкоманда в этом случае помогает. Это хорошо объясняется благодарностью суперпользователя .

В Unix / OSX вы можете использовать xargsс -oпараметром, например:

locate php.ini | xargs -o vim

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

В Linux попробуйте следующий обходной путь:

locate php.ini | xargs -J% sh -c 'vim < /dev/tty $@'

В качестве альтернативы используйте GNU parallelвместо xargsпринудительного распределения tty, например:

locate php.ini | parallel -X --tty vi

Примечание: parallelв Unix / OSX не будет работать, так как он имеет разные параметры и не поддерживает tty.

Многие другие популярные команды также предоставляют псевдо-tty распределение (например, -tв ssh), поэтому обратитесь за помощью.

В качестве альтернативы используйте findдля передачи имен файлов для редактирования, так что не нужно xargs, просто используйте -exec, например:

find /etc -name php.ini -exec vim {} +
kenorb
источник
0

IFSВзлом @ Патрика необходим только для таких глупых оболочек, как bashи zsh. fish разбивает строку на новые строки по умолчанию.

$ vim (locate php.ini)

И Бог поможет нам всем, если у одного из нас действительно есть файл с новой строкой в ​​его имени. После 17 лет использования Linux я не видел его ни разу. Я бы не стал поддерживать имена файлов с символами новой строки в них для сценариев, которые должны работать несмотря ни на что, но такие сценарии, вероятно, не запускают vim в интерактивном режиме.

enigmaticPhysicist
источник
zshпо умолчанию разделяется на SPC, TAB, NL и NUL. По сравнению с тем, с чем он не bashсправляется, выполняет глобализацию результата, поэтому подстановочные знаки в именах файлов не являются проблемой. В zsh, вы бы сделали IFS=$'\0'; vi $(locate -0 php.ini)или, как я показал в моем ответе vi ${(0)"$(locate -0 php.ini)"}для явного оператора расщепления. Также обратите внимание, что tcsh'svi "`locate php.ini`"
Стефан Шазелас
Ой, дерьмо. ОК, это работает, $ f='not there'<ret>$ ls $f<ret>но это не так: ls echo not there. ОК, похоже, мне нужно обновить это немного.
загадочный
Да, Zsh не делает правильные вещи, когда вы делаете ls "$(echo test; echo other test)". Только рыба делает правильные вещи.
загадочный
Предполагая, что вы имели в виду то же самое без кавычек, это не «правильно», это разбито на строки, это просто другой выбор. По умолчанию zsh разделяет слова (как и все другие оболочки), и ему можно указывать разделение на строки или на NUL, либо с помощью, либо с $IFSпомощью явных операторов ( fи 0флагов расширения параметров). Для произвольных имен файлов разделение по слову или разделение по строке одинаково неправильно , вам нужно разделить по NUL или проанализировать некоторую кодировку, чего fishне может быть. Во zsh, это IFS=$'\0'; ls -ld -- $(printf '%s\0' "$file1" "$file2")илиls -ld -- ${(0)"$(printf '%s\0' "$file1" "$file2")"}
Стефан Шазелас
Мех. Расщепление на новые строки достаточно хорошо. Как говорится в ответе, новые строки в именах файлов встречаются крайне редко. Я буквально никогда не видел, чтобы это произошло за 17 лет. И переводы строк являются более удобными разделителями, чем NULS.
загадочный
0

Быстрый способ сделать это, если вы не можете гарантировать ни один из файловых путей содержат SPC, TAB, NL, *, ?, [символы (также \и {...}в некоторых оболочках) является использование бэк-тиков (AKA серьезных акцентов) , чтобы выполнить команду до другая команда работает.

Например

vi `find / -type f -name 'php.ini'`

Команда, содержащаяся в обратных тиках, будет выполнена первой. Вывод содержащейся команды затем выполняется командой, указанной перед обратными галочками.

Например, в приведенной выше строке find / -type f -name 'php.ini'команда сначала выполнится, отправит вывод, а затем viбудет выполнена с результатом split + glob, примененным к этому выводу.

tacotuesday
источник
3
обратные галочки слишком легко спутать для одинарных кавычек. используйте $(find ...)вместо этого.
Cas
1
угадайте, что это также сломает пробелы и / или символы новой строки в именах файлов?
cwd
Вот как вы выполняете команды оболочки в скриптах bash. У меня никогда не было чего-либо ломающегося в пробелах или новых строках в моих сценариях или при использовании этого в одной строке Тем не менее, я никогда не пытался открыть несколько файлов с viпомощью этого метода. Вполне возможно, что он может разбиться на новые строки или пробелы, в зависимости от того, как viчитает и выполняет вывод.
tacotuesday