Wywoływanie vi przez find | xargs łamie mój terminal. Czemu?


127

Przywołując vim przez find | xargs, lubię to:

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

dostajesz ostrzeżenie

Input is not from a terminal

i terminal z dość zepsutym zachowaniem. Dlaczego?


9
Uwaga dodatkowa: Możesz wykonać tę operację całkowicie w vimie, nie używając find lub xargs w ogóle. Otwórz vima bez argumentów, a następnie uruchom :args **/*.txt<CR> ustawić argumenty vima z poziomu edytora.
Trevor Powell

3
@ TrevorPowell: Przez te wszystkie lata vim nigdy nie przestał mnie zadziwiać.
DevSolar



Odpowiedzi:


88

Kiedy wywołujesz program przez xargs, stdin programu (standardowe wejście) wskazuje na /dev/null. (Ponieważ xargs nie zna oryginalny stdin, robi kolejną najlepszą rzecz.)

$ 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 oczekuje, że jego standardowe wejście będzie takie samo jak jego terminal sterujący, i wykonuje różne związane z terminalem ioctl bezpośrednio na stdin. Po zakończeniu /dev/null (lub dowolny deskryptor pliku nie będący tty), te ioctle są bez znaczenia i zwracają ENOTTY, która jest ignorowana.

  • Domyślam się bardziej konkretnej przyczyny: przy starcie Vim czyta i zapamiętuje stare ustawienia terminala i przywraca je przy wychodzeniu. W naszej sytuacji, gdy wymagane są „stare ustawienia” dla nie-tty fd (deskryptor pliku), Vim otrzymuje wszystkie wartości puste i wszystkie opcje są wyłączone, i niedbale ustawia je na swoim terminalu.

    Możesz to zobaczyć, uruchamiając vim < /dev/null, wychodząc z niego, a następnie uruchamiając stty, który wygeneruje całą masę <undef> s. W systemie Linux działa stty sane sprawi, że terminal będzie ponownie użyteczny (chociaż to będzie straciłem takie opcje jak iutf8, prawdopodobnie powodując drobne kłopoty później).

Możesz to uznać za błąd w Vimie, ponieważ to mogą otwarty /dev/tty do sterowania terminalem, ale nie. (W pewnym momencie podczas uruchamiania Vim powiela swój stderr na stdin, co pozwala mu odczytać polecenia wejściowe - z fd otwartego do zapisu - ale nawet to nie jest zrobione wystarczająco wcześnie.


3
... z rozmachem. Dziękuję Ci bardzo!
DevSolar

14
+1, a dla TL; ludzie DR po prostu uciekają stty sane
doc_id

@rahmanisback: Pozostałe odpowiedzi, wraz z komentarzem Trevora, zapewniły przede wszystkim sposoby uniknięcia zerwania terminala. Przyjąłem odpowiedź grawity, ponieważ moje pytanie brzmiało „dlaczego”, a nie „jak unikać” - to dotyczy inne pytanie to właściwie zrodzony ten.
DevSolar

@DevSolar Rozumiem, ale pomyśl o sfrustrowanych ludziach, takich jak ja, którzy po prostu google, jak pozbyć się tego zachowania, podczas gdy nie - niestety - masz wystarczająco dużo czasu, aby przestudiować „dlaczego”, co jest jednak bardzo interesujące.
doc_id

4
kiedy mój terminal się zepsuje, używam tego reset zamiast stty sane i to działa dobrze po tym.
Capi Etheriel

123

Zgodnie z odpowiedzią grawity, xargs zwrotnica stdin do /dev/null


Z OSX / BSD man xargs

-o      Reopen stdin as /dev/tty in the child process 
        before executing the command.  This is useful 
        if you want xargs to run an interactive application.

Dlatego następujący wiersz kodu powinien działać dla Ciebie:

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

Dla GNU man xargs nie ma flagi, ale możemy jawnie przekazać / dev / tty, aby rozwiązać problem:

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

Ignoreme ma zająć 0 $, więc $ @ to wszystkie argumenty z xargs


2
Jak wytworzyłbyś z tego alias bash? $@ nie wydaje się poprawnie tłumaczyć argumentów.
zanegray

1
@zanegray - nie możesz utworzyć aliasu, ale możesz uczynić go funkcją. Próbować: function vimin () { xargs sh -c 'vim "$@" < /dev/tty' vim; }
Christopher

Aby uzyskać szczegółowe wyjaśnienie działania rozwiązania GNU xargs i dlaczego potrzebujesz manekina ignoreme sznur, patrz vi.stackexchange.com/a/17813
wisbucky

@zanegray, Możesz zrobić z tego alias. Cytaty są trudne. Zobacz rozwiązanie w vi.stackexchange.com/a/17813
wisbucky

30

Najprostszy sposób:

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

4
Głównym pytaniem było „dlaczego”, a nie „jak tego uniknąć”, a odpowiedź na satysfakcję została udzielona dwa i pół roku temu.
DevSolar

3
To oczywiście nie działa prawidłowo, gdy nazwy plików zawierają spacje lub inne znaki specjalne, a także stanowi zagrożenie bezpieczeństwa.
Dejay Clayton

1
Moja ulubiona odpowiedź, ponieważ działa dla każdego polecenia zawierającego listę plików, a nie tylko „znajdź” lub symbole wieloznaczne. Wymaga to niewielkiego zaufania, jak wskazuje Dejay.
Travis Wilson

1
To nie zadziała w wielu przypadkach użycia, dla których xargs jest przeznaczony: np. Gdy liczba ścieżek jest bardzo wysoka (cc @TravisWilson)
Good Person

20

Powinien działać dobrze, jeśli użyjesz opcji -exec na find zamiast piping do xargs. Na przykład

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


2
Huh ... sztuczka jest + (zamiast „zwykłego” \; ), aby pobrać wszystkie znalezione pliki jeden Sesja Vima - opcja I trzymać zapominając o. Oczywiście masz rację i za to +1. używam vim $(find ...) po prostu z przyzwyczajenia. Jednak właściwie o to prosiłem czemu operacja rurowa zaciska terminal, a grawity przybili go swoim wyjaśnieniem.
DevSolar

2
Jest to najlepsza odpowiedź i działa zarówno na BSD / OSX / GNU / Linux.
kevinarpe

1
Ponadto find nie jest jedynym sposobem uzyskania listy plików, które muszą być edytowane jednocześnie przez vim. Mogę użyć grep, aby znaleźć wszystkie pliki ze wzorem i spróbować edytować je jednocześnie.
Chandranshu

8

Zamiast tego użyj GNU Parallel:

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

Lub jeśli chcesz otworzyć wszystkie pliki za jednym razem:

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

Nawet poprawnie obsługuje nazwy plików takie jak:

My brother's 12" records.txt

Obejrzyj film wprowadzający, aby dowiedzieć się więcej: http://www.youtube.com/watch?v=OpaiGYxkSuQ


1
Nie jest powszechnie dostępny. Większość dnia pracuję na serwerach, na których nie mogę instalować dodatkowych narzędzi. Ale i tak dziękuję za podpowiedź.
DevSolar

Jeśli masz możliwość zrobienia „kota” plik; chmod + x file ', a następnie możesz zainstalować GNU Parallel: Jest to po prostu skrypt perla. Jeśli chcesz mieć strony man i takie, możesz zainstalować je pod swoim katalogiem domowym: ./configure --prefix = $ HOME i amp; marka i amp; wykonaj instalację
Ole Tange

2
OK, spróbuj - ale równolegle nie otwórz wszystkie pliki, otworzy je kolejno . Jest to również całkiem niezły pomysł na prostą operację. vim $(find . -name "*.txt") jest prostsze i wszystkie pliki są otwierane jednocześnie.
DevSolar

5
@DevSolar: Trochę niezwiązany, ale oba find | xargs i $(find) będą miały duże problemy ze spacjami w nazwach plików.
grawity

2
@grawity Poprawnie, ale nie ma łatwego sposobu na obejście go (o czym wiem). Musisz zacząć się bawić $IFS, -print0 i tak dalej, a potem opuściłeś królestwo jednorazowego rozwiązania linii poleceń i dotarłeś do punktu, w którym powinieneś wymyślić skrypt ... jest powód, dla którego przestają być spacje w nazwach plików.
DevSolar
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.