xargs i vi - „Wejście nie pochodzi z terminala”

14

W php.inimoim systemie jest około 10 plików rozmieszczonych w dowolnym miejscu i chciałem szybko je przeglądać. Próbowałem tego polecenia:

locate php.ini | xargs vi

Ale viostrzega mnie, Input is not from a terminala potem konsola zaczyna się naprawdę dziwnie - po czym muszę nacisnąć, :q!aby wyjść, via następnie rozłączyć się z sesją ssh i połączyć się ponownie, aby konsola zachowała się normalnie.

Wydaje mi się, że rozumiem, co się tutaj dzieje - w zasadzie polecenie nie zakończyło się po viuruchomieniu, więc polecenie może się nie zakończyło i vinie wydaje się, że terminal jest w trybie normalnym.

Nie mam pojęcia, jak to naprawić. Przeszukałem Google, a także unix.stackexchange.com, ale przy odrobinie szczęścia.

cwd
źródło
Na marginesie, możesz uruchomić, resetaby zresetować terminal, gdy zostanie zepsuty (nie musisz rozłączać się z sesją ssh).
wisbucky

Odpowiedzi:

12
vi $(locate php.ini)

Uwaga: będzie to powodować problemy, jeśli ścieżki do plików mają spacje, ale jest funkcjonalnie równoważne z twoim poleceniem.
Następna wersja poprawnie obsługuje spacje, ale jest nieco bardziej skomplikowana (jednak nowe wiersze w nazwach plików nadal ją łamią)

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


Wyjaśnienie:

To, co się dzieje, polega na tym, że programy dziedziczą swoje deskryptory plików po procesie, który je zrodził. xargsma swój STDIN podłączony do STDOUT locate, więc vinie ma pojęcia, w czym tak naprawdę jest oryginalny STDIN.

Patrick
źródło
2
xargs jest cudowny, jedno z moich ulubionych narzędzi - po prostu nie nadaje się do użytku z programami, które używają standardowego wejścia do innych celów niż do przesyłania danych. podoba mi się twoja odpowiedź i inne wytłumaczenie, więc i tak +1 :)
cas
@CraigSanders Nie podoba mi się to, ponieważ jest zbyt łatwe do nadużywania (niewłaściwego użytkowania) i w końcu się psuje. Nigdy nie spotkałem niczego, co absolutnie musiałem użyć xargsdo tego, czego nie można było zrobić bezpośrednio za pomocą powłoki (lub find). Mogę jednak myśleć o przypadkach, w których byłoby to najlepsze rozwiązanie. Tak długo, jak rozumiesz, co xargssię dzieje, jak dzieli argumenty, jak uruchamia program itp. I używasz go poprawnie, powiedziałbym: idź po to :-P
Patrick,
nie da się tego pokonać w przypadku takich rzeczy ... | awk '{print $3}' | xargs | sed -e 's/ /+/g' | bc(aby dodać wszystkie wartości z pola 3). lub z, sed -e 's/ /|/g'aby skonstruować wyrażenie regularne. i tak, jak każde narzędzie, musisz wiedzieć, jak z niego korzystać oraz jakie są jego ograniczenia i zastrzeżenia.
cas
vi $(...)Podejście ma również problemu z symboli wieloznacznych w muszli innych niż zsh.
Stéphane Chazelas
Należy również zauważyć, że przy xargspodejściu obok problemu białych znaków nazwy plików z pojedynczymi cudzysłowami, podwójnymi cudzysłowami i odwrotnymi ukośnikami również stanowią problem.
Stéphane Chazelas
10

To pytanie zostało wcześniej zadane na forum Super User .

Cytując odpowiedź @ grawity na to pytanie:

Kiedy wywołujesz program przez xargs, standardowe wejście programu (standardowe wejście) wskazuje na / dev / null. (Ponieważ xargs nie zna oryginalnego standardu, robi następną najlepszą rzecz.)

Vim oczekuje, że jego stdin będzie taki sam jak terminal kontrolny, i bezpośrednio wykonuje różne związane z nim ioctl. Po wykonaniu na / dev / null (lub dowolnym deskryptorze pliku innego niż tty), te ioctls są bez znaczenia i zwracają ENOTTY, który jest dyskretnie ignorowany.

Jest to wspomniane na stronach podręcznika dla xarg. Z OSX / BSD:

-o Ponownie otwórz stdin jako / dev / tty w procesie potomnym przed wykonaniem polecenia. Jest to przydatne, jeśli chcesz, aby xargs uruchamiał interaktywną aplikację.

Dlatego w systemie OSX można użyć następującego polecenia:

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

Chociaż w wersji GNU nie ma bezpośredniego przełącznika, to polecenie będzie działać. (Pamiętaj, aby dołączyć dummyciąg, w przeciwnym razie spowoduje upuszczenie pierwszego pliku).

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

Powyższe rozwiązania są dzięki uprzejmości Jaime McGuigan na SuperUser . Dodanie ich tutaj dla przyszłych użytkowników przeszukujących witrynę pod kątem tego błędu.

darnir
źródło
3
+1 dzięki za wskazówkę -o. używam xargs od lat i nigdy tego nie zauważyłem .... po prostu sprawdziłem stronę podręcznika systemowego w moim systemie, ponieważ nie jest to funkcja xargs GNU. Strona podręcznika dostarcza, xargs sh -c 'emacs "$@" < /dev/tty' emacsponieważ, jak twierdzą, jest bardziej elastyczną i przenośną alternatywą (chociaż to trochę zabawne, że GNU preferuje przenośność od funkcji :).
cas
2

Dzięki GNU findutilsi powłoce z obsługą zastępowania procesów (ksh, zsh, bash) możesz:

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

Chodzi o to, aby przekazać listę plików -a filenameraczej niż przez standardowe wyjście. Użycie -0powoduje, że działa niezależnie od znaków lub znaków innych niż nazwy plików.

Dzięki zshmożesz wykonać:

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

(gdzie 0jest flaga ekspansji parametru do podziału na wartości NUL).

Należy jednak pamiętać, że w przeciwieństwie do xargs -rtego nadal działa vibez argumentów, jeśli nie znaleziono pliku.

Stéphane Chazelas
źródło
0

Edytować wiele php.ini w tym samym edytorze?

Próbować: vim -o $(locate php.ini)

stokrotka
źródło
0

Ten błąd występuje, gdy vim jest wywoływany i jest podłączony do wyjścia poprzedniego potoku, zamiast terminala i otrzymuje inne nieoczekiwane dane wejściowe (takie jak NUL). To samo dzieje się po uruchomieniu:, vim < /dev/nullwięc resetpolecenie w tym przypadku pomaga. Wyjaśnia to dobrze grawitacja w superużytkowniku .

W systemach Unix / OSX możesz używać xargs z -oparametrem, takim jak:

locate php.ini | xargs -o vim

-oPonownie otwórz stdin jako / dev / tty w procesie potomnym przed wykonaniem polecenia. Jest to przydatne, jeśli chcesz, aby xargs uruchamiał interaktywną aplikację.

W systemie Linux wypróbuj następujące obejście:

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

Alternatywnie użyj GNU parallelzamiastxargs wymusić alokację tty, na przykład:

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

Uwaga: parallelw systemach Unix / OSX nie będzie działać, ponieważ ma inne parametry i nie obsługuje tty.

Wiele innych popularnych poleceń zapewnia także alokację pseudo-tty (np -t w ssh), więc sprawdzić o pomoc.

Alternatywnie użyj, findaby przekazać nazwy plików do edycji, więc nie musisz xargs, po prostu użyj -execna przykład:

find /etc -name php.ini -exec vim {} +
kenorb
źródło
0

@ IFSHack Patryka jest konieczny tylko dla głupich muszli takich jak bashi zsh. fish domyślnie dzieli ciąg na nowe linie.

$ vim (locate php.ini)

I Boże, pomóż nam wszystkim, jeśli jeden z nas rzeczywiście ma plik z nową linią w nazwie. Po 17 latach używania Linuksa nie widziałem go ani razu. Chciałbym tylko wspierać nazwy plików z nowymi wierszami dla skryptów, które muszą działać bez względu na wszystko, ale takie skrypty prawdopodobnie nie działają interaktywnie.

enigmatyczny fizyk
źródło
zshdomyślnie dzieli na SPC, TAB, NL i NUL. Rzeczą, której nie robi w porównaniu z tym, bashjest przeprowadzanie globowania wyniku, więc znaki wieloznaczne w nazwach plików nie stanowią problemu. W zsh, zrobiłbyś IFS=$'\0'; vi $(locate -0 php.ini)lub jak pokazałem w mojej odpowiedzi vi ${(0)"$(locate -0 php.ini)"}dla jawnego operatora podziału. Zwróć też uwagę na tcshvi "`locate php.ini`"
Stéphane Chazelas,
o kurwa. OK to działa: $ f='not there'<ret>$ ls $f<ret>ale to nie: ls echo not there. OK wygląda na to, że muszę to trochę zaktualizować.
enigmatyczny fizyk
Tak, zsh nie robi dobrze, kiedy to robisz ls "$(echo test; echo other test)". Tylko ryby robią to, co należy.
enigmatyczny fizyk
Zakładając, że masz na myśli to samo bez cudzysłowów, to nie jest „właściwe”, to jest podział na linie, to po prostu inny wybór. zsh domyślnie dzieli na słowa (podobnie jak wszystkie inne powłoki) i może zostać poproszony o podział na linie lub na wartości NUL, za pośrednictwem $IFSlub za pośrednictwem jawnych operatorów ( fi 0flag rozszerzania parametrów). W przypadku dowolnych nazw plików dzielenie według słów lub dzielenie według wierszy jest równie niepoprawne , musisz podzielić na NUL lub przeanalizować kodowanie, czego fishnie można zrobić. W zsh, to IFS=$'\0'; ls -ld -- $(printf '%s\0' "$file1" "$file2")lubls -ld -- ${(0)"$(printf '%s\0' "$file1" "$file2")"}
Stéphane Chazelas
Meh Podział na nowe linie jest wystarczająco dobry. Jak mówi odpowiedź, nowe wiersze w nazwach plików są niezwykle rzadkie. Dosłownie nigdy nie widziałem, aby stało się to przez 17 lat. A znaki nowej linii są znacznie wygodniejszymi separatorami niż wartości zerowe.
enigmatyczny
0

Szybkim sposobem, aby to zrobić, zakładając, że można zagwarantować żadna ze ścieżek plików zawierać SPC, TAB, NL, *, ?, [znaków (także \i {...}w niektórych muszli) jest użycie back-kleszczy (aka grawis) , aby wykonać polecenie przed uruchomione kolejne polecenie.

Na przykład

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

Polecenie zawarte w tykach zwrotnych zostanie wykonane jako pierwsze. Dane wyjściowe zawartego polecenia są następnie wykonywane przez polecenie podane przed tyknięciami wstecznymi.

Na przykład w powyższym wierszu find / -type f -name 'php.ini'polecenie wykona się najpierw, wyśle ​​dane wyjściowe, a następnie vizostanie wykonane na podstawie wyniku split + glob zastosowanego do tego wyniku.

wtorek
źródło
3
tyknięcia wsteczne są zbyt łatwo mylone dla pojedynczych cudzysłowów. użyj $(find ...)zamiast tego.
cas
1
zgadując, że to też spowoduje uszkodzenie spacji i / lub nowych linii w nazwach plików?
cwd
W ten sposób wykonujesz polecenia powłoki w skryptach bash. Nigdy nie miałem żadnych przerw na spacjach ani nowych wierszach w moich skryptach ani podczas używania ich w jednej linijce. Jednak nigdy nie próbowałem otwierać wielu plików przy viużyciu tej metody. Jest całkiem możliwe, że może się złamać w nowych wierszach lub spacjach, w zależności od tego, jak viodczytuje i wykonuje dane wyjściowe.
tacot Wtorek