Викликаючи vi через find | xargs ламає мій термінал. Чому?


137

При виклику vimчерез find | xargs:

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

Ви отримуєте попередження про

Input is not from a terminal

і термінал з досить сильно порушеною поведінкою згодом. Чому так?


11
Бічна примітка: цю операцію можна виконувати повністю в межах vim, не використовуючи findабо xargsзовсім. Відкрийте vim без аргументів, а потім запустіть, :args **/*.txt<CR>щоб встановити аргументи vim зсередини редактора.
Тревор Пауелл

3
@TrevorPowell: За всі ці роки vim не переставав мене дивувати.
DevSolar



Відповіді:


100

Коли ви викликаєте програму через xargs, stdin (стандартний ввід) програми вказує на /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(або будь-якого нетипового дескриптора файлів) ці йоктли є безглуздими і повертають ENOTTY, що мовчки ігнорується.

  • Моя здогадка про більш конкретну причину: при запуску Vim зчитує та запам'ятовує старі настройки терміналу та відновлює їх назад під час виходу. У нашій ситуації, коли "старі налаштування" запитуються на не-tty fd (дескриптор файлу), Vim отримує всі значення порожні та всі параметри вимкнено, і недбало встановлює те саме на ваш термінал.

    Ви можете побачити це, запустивши vim < /dev/null, вийшовши з нього, потім запустивши stty, що виведе цілу партію <undef>s. У Linux, працює stty saneзроблять термінал придатного для використання (хоча це буде втратило такі варіанти , як iutf8, можливо , викликаючи незначні дратували пізніше).

Ви можете вважати цю помилку у Vim, оскільки вона може відкриватися /dev/ttyдля управління терміналом, але ні. (У якийсь момент під час запуску Vim дублює свій stderr на stdin, що дозволяє йому читати ваші вхідні команди - з fd, відкритого для запису, - але навіть це не робиться досить рано.)


20
+1, а для TL; DR просто бігаютьstty sane
doc_id

@rahmanisback: Інші відповіді, а також коментар Тревора, все це забезпечили в першу чергу способи уникнути поломки терміналу. Я прийняв відповідь шаленості, бо моє запитання було "чому", а не "як уникнути" - це охоплене ще одним питанням, яке насправді породило цю.
DevSolar

@DevSolar Зрозумів, але подумайте про таких розчарованих людей, як я, які просто гуглить, як позбутися такої поведінки, але не, на жаль, - у нас зараз достатньо часу, щоб вивчити "чому", що, однак, дуже цікаво.
doc_id

4
коли мій термінал ламається, як це, я використовую resetзамість цього, stty saneі він працює добре після цього.
Capi Etheriel

136

(Виходячи з пояснення grawity, це xargsвказує stdinна /dev/null.)

Рішення цієї проблеми полягає в додаванні -oпараметра до xargs. Від man xargs:

-o

      Повторно відкрийте stdin, як і /dev/ttyв дочірньому процесі перед виконанням команди. Це корисно, якщо ви хочете 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
Як би ви створили баш-псевдонім із цього? $@здається, не перекладає аргументи правильно.
zanegray

1
@zanegray - ви не можете зробити псевдонім, але ви можете зробити його функцією. Спробуйте:function vimin () { xargs sh -c 'vim "$@" < /dev/tty' vim; }
Крістофер

Детальне пояснення того, як працює рішення xargs GNU і навіщо потрібна фіктивна 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 з домашньої мови (GNU))
localhostdotdev

33

Найпростіший спосіб:

vim $(find . -name "*foo*")

5
Основним питанням було "чому", а не "як цього уникнути", і на нього відповіли задоволення два з половиною роки тому.
DevSolar

5
Це, звичайно, не працює належним чином, коли назви файлів містять пробіли чи інші спеціальні символи, а також є ризиком для безпеки.
Dejay Clayton

1
Моя улюблена відповідь, тому що вона працює для кожної команди, що перераховує файли, а не лише "знайти" або символи. Як зазначає Дежай, це вимагає трохи довіри.
Тревіс Вілсон

1
Це не спрацює з багатьма випадками використання xargs призначений для: наприклад, коли кількість шляхів дуже велика (cc @TravisWilson)
Good Person

21

Це має спрацювати чудово, якщо ви використовуєте опцію -exec для пошуку, а не для вставки в xargs.

find . -type f -name filename.txt -exec vi {} + 

2
Так ... хитрість полягає в тому, щоб +(замість "звичайного" \;) всі знайдені файли перенести в один сеанс Vim - варіант, про який я постійно забуваю. Ви, звичайно, маєте рацію і +1 для цього. Я використовую vim $(find ...)просто за звичкою. Однак я насправді запитував, чому трубопровід прикручує клему, і grawity прибив це своїм поясненням.
DevSolar

2
Це найкраща відповідь, і вона працює як на BSD / OSX / GNU / Linux.
kevinarpe

1
Також пошук не є єдиним способом отримання списку файлів, які повинні одночасно редагуватися vim. Я можу використовувати grep, щоб знайти всі файли з малюнком і спробувати їх редагувати одночасно.
Чандраншу

8

Замість цього використовуйте паралель GNU:

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>; chmod + x файл ', тоді ви можете встановити GNU Parallel: Це просто сценарій perl. Якщо ви хочете доменні сторінки та такі, ви можете встановити її під своїм homedir: ./configure --prefix = $ HOME && make && make install
Ole Tange

2
Добре, спробував це - але паралельно не відкриває всі файли, він відкриває їх послідовно . Це також досить рот для простої операції. vim $(find . -name "*.txt")простіше, і ви отримуєте всі файли відкриті одразу.
DevSolar

5
@DevSolar: Дещо не пов’язаний, але в обох find | xargsі $(find)виникнуть великі проблеми з пробілами у назвах файлів.
grawity

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наприклад


Щодо кількох інших відповідей, зауважте, що власне питання було "чому", а не "як цього уникнути". (Для чого я все-таки зазначу коментар Тревора під моїм запитанням як найбільш твердий спосіб, який не вимагає сценаріїв, псевдонімів чи нічого.)
DevSolar
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.