Uruchom dwie komendy na jednym argumencie (bez skryptów)

28

Jak mogę wykonać dwa polecenia na jednym wejściu, nie wpisując tego dwukrotnie?

Na przykład polecenie stat wiele mówi o pliku, ale nie wskazuje jego typu:

stat fileName

Polecenie file określa typ pliku:

file fileName

Możesz to zrobić w jednym wierszu w ten sposób:

stat fileName ; file fileName

Musisz jednak wpisać fileName dwa razy.

Jak możesz wykonać oba polecenia na tym samym wejściu (bez dwukrotnego wpisywania wejścia lub zmiennej wejścia)?

W systemie Linux wiem, jak przesyłać dane wyjściowe, ale jak przesyłać dane wejściowe?

Lonniebiz
źródło
14
Żeby było jasne, nie są to „dane wejściowe”, to argumenty (inaczej parametry). Dane wejściowe można przesyłać strumieniowo przez <(dane wejściowe z pliku do lewej strony) lub |(dane wejściowe ze strumienia do prawej strony). Jest różnica.
goldilocks
6
Nie jest to odpowiedź na twoje pytanie, ale może odpowiedź na Twój problem: W bashu, yank-last-argkomenda (domyślny skrót Meta-.lub Meta-_; Metaczęsto jest lewy Altklawisz) spowoduje skopiowanie ostatniego parametru z poprzedniego wiersza poleceń do obecnego. Tak więc, po wykonaniu stat fileNamewpiszesz file [Meta-.]i nie trzeba wpisywać, że fileNameponownie. Zawsze tego używam.
Dubu,
2
A różnica jest taka, że <i >przekierowania , nie potokiem . Potok łączy dwa procesy, a przekierowanie po prostu ponownie przypisuje stdin / stdout.
bsd
@bdowning: Tak, ale przekierowujesz potok, prawda? Możesz przekierować początek potoku do odczytu z pliku na dysku lub przekierować koniec potoku do zapisu do pliku na dysku. Nadal leci. ;-)
James Haigh
Powiązane (ale bardziej wyspecjalizowane): Czy istnieje jedna linijka, która pozwala mi utworzyć katalog i przenieść się do niego w tym samym czasie?
G-Man mówi „Przywróć Monikę”

Odpowiedzi:

22

Oto inny sposób:

$ stat filename && file "$_"

Przykład:

$ stat /usr/bin/yum && file "$_"
  File: ‘/usr/bin/yum’
  Size: 801         Blocks: 8          IO Block: 4096   regular file
Device: 804h/2052d  Inode: 1189124     Links: 1
Access: (0755/-rwxr-xr-x)  Uid: (    0/    root)   Gid: (    0/    root)
Context: system_u:object_r:rpm_exec_t:s0
Access: 2014-06-11 22:55:53.783586098 +0700
Modify: 2014-05-22 16:49:35.000000000 +0700
Change: 2014-06-11 19:15:30.047017844 +0700
 Birth: -
/usr/bin/yum: Python script, ASCII text executable

To działa w bashi zsh. Działa to również w trybie interaktywnym mkshi dashtylko wtedy. W AT&T ksh działa to tylko wtedy, gdy file "$_"znajduje się w innym wierszu niż ten stat.

Cuonglm
źródło
1
!$Jeden nie zadziała, ponieważ !$rozszerza się do ostatniego słowa ostatniego zdarzenia historii (jak poprzednio wprowadzonej linii poleceń, a nie polecenia stat).
Stéphane Chazelas,
@ StéphaneChazelas: Och, tak, mój błąd, usunąłem go.
cuonglm
32

Prawdopodobnie doprowadzę do tego, że moje kostki zostaną w tym celu zastrzelone, oto hacky zestaw ekspansji nawiasów klamrowych i ewaluacji, który wydaje się załatwić sprawę

eval {stat,file}" fileName;"
iruvar
źródło
8
Przepraszam, ale nie mogę się oprzeć .
Andreas Wiese
2
ekspansja nawiasów pochodzi csh, a nie bash. bashskopiowałem to z ksh. Ten kod będzie działał we wszystkich csh, tcsh, ksh, zsh, fish i bash.
Stéphane Chazelas,
1
Szkoda, że ​​straciłeś możliwość „tab out” (autouzupełniania) fileName, z powodu cytatu.
Lonniebiz
Niestety, ale nie mógł się oprzeć albo
tomd
przepraszam, z czym jest problem eval? (odnosząc się do najlepszych komentarzy)
user13107 11.03.16
28

Za pomocą zshmożesz korzystać z anonimowych funkcji:

(){stat $1; file $1} filename

Z eslambdami:

@{stat $1; file $1} filename

Możesz także:

{ stat -; file -;} < filename

(wykonanie statpierwszego jako filespowoduje aktualizację czasu dostępu).

Mogłabym zrobić:

f=filename; stat "$f"; file "$f"

jednak po to są zmienne.

Stéphane Chazelas
źródło
3
{ stat -; file -;} < filenamejest magia. statmusi sprawdzać, czy jego standardowe wejście jest połączone z plikiem i zgłaszać atrybuty pliku? Prawdopodobnie nie przesuwa wskaźnika na stdin, dlatego pozwala filewąchać zawartość stdin
iruvar
1
@ 1_CR, tak. stat -każe statzrobić fstat()na swoim fd 0.
Stéphane Chazelas,
4
{ $COMMAND_A -; $COMMAND_B; } < filenameWariant nie będzie działać w ogólnym przypadku, jeśli $ COMMAND_A faktycznie czyta żadnych danych; np. { cat -; cat -; } < filenamewydrukuje zawartość nazwy pliku tylko raz.
Max Nanasy
2
stat -jest obsługiwany specjalnie od coreutils-8.0 (październik 2009), w wersjach wcześniejszych pojawi się błąd (chyba że masz plik wywołany -z poprzedniego eksperymentu, w którym to przypadku otrzymasz nieprawidłowe wyniki ...)
mr .spuratic
1
@ mr.spuratic. Tak, BSD, IRIX i statystyki zsh też nie rozpoznają tego. W przypadku statystyk zsh i IRIX można użyć stat -f0zamiast tego.
Stéphane Chazelas
9

Wszystkie te odpowiedzi wydają mi się w mniejszym lub większym stopniu skryptem. Aby uzyskać rozwiązanie, które naprawdę nie wymaga pisania skryptów i zakłada, że ​​siedzisz przy klawiaturze, pisząc w powłoce, możesz spróbować:

stat Enter
plik somefile Esc_   Enter

Przynajmniej w bash, zsh i ksh, zarówno w trybach vi, jak i emacs, naciśnięcie Escape, a następnie podkreślenie wstawia ostatnie słowo z poprzedniego wiersza do bufora edycji bieżącego wiersza.

Lub możesz użyć podstawienia historii:

stat Enter
plik somefile ! $Enter

W powłokach bash, zsh, csh, tcsh i większości innych !$jest zastępowany ostatnim słowem poprzedniego wiersza poleceń, gdy bieżący wiersz poleceń jest analizowany.

godlygeek
źródło
jakie są inne opcje dostępne w Zsh / Bash Esc _? Czy możesz połączyć się z oficjalną dokumentacją?
Abhijeet Rastogi
1
zsh: linux.die.net/man/1/zshzle i wyszukaj „insert-last-word”
godlygeek
1
@shadyabhi ^ Linki do standardowych skrótów klawiszowych bash i zsh. Zauważ, że bash używa tylko readline, więc (większość) bash będzie działał w każdej aplikacji korzystającej z biblioteki readline.
godlygeek
3

Sposób, w jaki znalazłem to rozsądnie, polega na użyciu xargs, bierze plik / potok i konwertuje jego zawartość na argumenty programu. Można to połączyć z trójnikiem, który dzieli strumień i wysyła go do dwóch lub więcej programów. W twoim przypadku potrzebujesz:

echo filename | tee >(xargs stat) >(xargs file) | cat

W przeciwieństwie do wielu innych odpowiedzi, zadziała to w bash i większości innych powłok pod Linuksem. Zasugeruję, że jest to dobry przypadek użycia zmiennej, ale jeśli absolutnie nie możesz jej użyć, jest to najlepszy sposób, aby to zrobić tylko za pomocą potoków i prostych narzędzi (zakładając, że nie masz szczególnie wymyślnej powłoki).

Dodatkowo możesz to zrobić dla dowolnej liczby programów, po prostu dodając je w taki sam sposób, jak te dwa po tee.

EDYTOWAĆ

Jak sugerowano w komentarzach, w tej odpowiedzi jest kilka wad, pierwszą z nich jest potencjał przeplatania wyników. Można to naprawić w następujący sposób:

echo filename | tee >(xargs stat) >(( wait $!; xargs file )) | cat

Zmusi to kolejno polecenia do działania, a dane wyjściowe nigdy nie będą miały przeplotu.

Drugim problemem jest unikanie podstawiania procesów, które nie jest dostępne w niektórych powłokach, można to osiągnąć za pomocą tpipe zamiast tee, ponieważ:

echo filename | tpipe 'xargs stat' | ( wait $!; xargs file )

Powinien być przenośny na prawie każdą powłokę (mam nadzieję) i rozwiązuje problemy w innym poleceniu, jednak piszę ten ostatni z pamięci, ponieważ mój obecny system nie ma dostępnego tpipe.

Rzeczywistość
źródło
1
Podstawianie procesów to funkcja ksh, która działa tylko w ksh(nie w wariantach domeny publicznej) bashi zsh. Zauważ, że stati filebędą działać jednocześnie, więc być może ich dane wyjściowe zostaną splecione (przypuszczam, że używasz | catwymuszania buforowania danych wyjściowych w celu ograniczenia tego efektu?).
Stéphane Chazelas,
@ StéphaneChazelas Masz rację co do kota, podczas korzystania z niego nigdy nie udało mi się stworzyć przypadku wprowadzania danych z przeplotem podczas korzystania z niego. Jednak zamierzam spróbować za chwilę stworzyć bardziej przenośne rozwiązanie.
Vality
3

Ta odpowiedź jest podobna do kilku innych:

nazwa pliku stat   && file! #: 1

W przeciwieństwie do innych odpowiedzi, które automatycznie powtarzają ostatni argument z poprzedniego polecenia, ta wymaga policzenia:

ls -ld nazwa pliku   && plik! #: 2

W przeciwieństwie do niektórych innych odpowiedzi, nie wymaga to cytowania:

stat "useless cat"  &&  file !#:1

lub

echo Sometimes a '$cigar' is just a !#:3.
Scott
źródło
2

Jest to podobne do kilku innych odpowiedzi, ale jest bardziej przenośne niż ta i znacznie prostsze niż ta :

sh -c 'stat "$ 0"; plik nazwa pliku „$ 0”

lub

sh -c 'stat "$ 1"; plik „$ 1” ”sh nazwa pliku

w którym shjest przypisany do $0. Chociaż do wpisania są trzy znaki, ta (druga) forma jest lepsza, ponieważ $0taką nazwę nadajesz temu wbudowanemu skryptowi. Jest używany na przykład w komunikatach o błędach przez powłokę dla tego scenariusza, jak wtedy, gdy filealbo statnie można znaleźć lub nie może być wykonany z dowolnego powodu.


Jeśli chcesz to zrobić

stat nazwa pliku 1  nazwa pliku 2  nazwa pliku 3 …; plik nazwa pliku 1  nazwa pliku 2  nazwa pliku 3

dla dowolnej liczby plików, wykonaj

sh -c 'stat "$ @"; plik „$ @” ”sh nazwa pliku 1  nazwa pliku 2  nazwa pliku 3

który działa, ponieważ "$@"jest równoważny z "$1" "$2" "$3" ….

Scott
źródło
To $0jest nazwa, którą chcesz nadać temu wbudowanemu skryptowi. Jest używany na przykład w komunikatach o błędach przez powłokę dla tego skryptu. Jak wtedy, gdy filealbo statnie można znaleźć lub nie może być wykonany z dowolnego powodu. Chcesz tutaj czegoś sensownego. Jeśli nie jesteś zainspirowany, po prostu użyj sh.
Stéphane Chazelas,
1

Myślę, że zadajesz złe pytanie, przynajmniej za to, co próbujesz zrobić. stata filepolecenia przyjmują parametry, które są nazwą pliku, a nie jego zawartością. Jest to później polecenie, które odczytuje plik identyfikowany przez podaną nazwę.

Rura ma mieć dwa końce, jeden używany jako dane wejściowe, a drugi jako dane wyjściowe, co ma sens, ponieważ może być konieczne wykonanie różnych operacji po drodze przy użyciu różnych narzędzi, aż do uzyskania pożądanego rezultatu. Mówienie, że wiesz, jak przesyłać dane wyjściowe, ale nie wiesz, jak przesyłać dane wejściowe, jest w zasadzie niepoprawne.

W twoim przypadku wcale nie potrzebujesz potoku, ponieważ musisz podać dane wejściowe w postaci nazwy pliku. I nawet jeśli te narzędzia ( stati file) odczytałyby standardowe wejście, potok nie ma tu znaczenia, ponieważ dane wejściowe nie powinny być zmieniane przez żadne z narzędzi, gdy dojdzie do drugiego.

Vadimbog
źródło
1

Powinno to działać dla wszystkich nazw plików w bieżącym katalogu.

sh -c "$(printf 'stat -- "$1";file -- "$1";shift%.0b\n' *)" -- *
mikeserv
źródło
1
Zakładając, że nazwy plików nie zawierają podwójnego cudzysłowu, ukośnika odwrotnego, dolara, }znaku wstecznego, ... i nie zaczynaj od -. Niektóre printfimplementacje (zsh, bash, ksh93) mają %q. Z zshmoże to być:printf 'stat %q; file %1$q\n' ./* | zsh
Stéphane Chazelas,
@StephaneChezales - wszystko to jest teraz naprawione, z wyjątkiem możliwości hardquote. Mogę sobie z tym poradzić jednym sed s///, ale chyba chodzi o zakres - a jeśli ludzie umieszczają to w swoich nazwach plików, powinni używać okien.
mikeserv
@ StéphaneChazelas - nieważne - zapomniałem, jak łatwo było sobie z nimi poradzić.
mikeserv
Ściśle mówiąc, nie jest to odpowiedź na zadane pytanie: „Jak można wykonać oba polecenia na tym samym wejściu ( bez wpisywania wejścia… dwa razy )?” (Podkreślenie dodane). Twoja odpowiedź dotyczy wszystkich plików w bieżącym katalogu - i obejmuje *dwa razy . Równie dobrze możesz powiedzieć stat -- *; file -- *.
Scott
@Scott - dane wejściowe nie są wpisywane dwukrotnie - *jest rozwijane. Rozprężny z *plus dwa polecenia dla każdej zmiennej w * to wejściowego. Widzieć? printf commands\ %s\;shift *
mikeserv
1

Jest tu kilka różnych odniesień do „danych wejściowych”, dlatego przedstawię kilka scenariuszy, mając na uwadze przede wszystkim ich zrozumienie. Aby uzyskać szybką odpowiedź na pytanie w najkrótszej formie :

stat testfile < <($1)> outputfile

Powyższe wykona statystyki na pliku testowym, weź (przekieruje) to STDOUT i uwzględni to w następnej funkcji specjalnej (część <()), a następnie wyśle ​​ostateczne wyniki tego, co było, do nowego pliku (plik wyjściowy). Plik jest wywoływany, a następnie odwoływany do wbudowanych bash (1 $ za każdym razem, dopóki nie zaczniesz nowego zestawu instrukcji).

Twoje pytanie jest świetne i istnieje kilka odpowiedzi i sposobów na zrobienie tego, ale tak naprawdę zmienia się w zależności od tego, co robisz.

Na przykład możesz to również zapętlić, co jest dość przydatne. Typowym zastosowaniem tego jest w myśleniu psuedo-code:

run program < <($output_from_program)> my_own.log

Biorąc to i poszerzając tę ​​wiedzę, możesz tworzyć takie rzeczy jak:

ls -A; (while read line; do printf "\e[1;31mFound a file\e[0m: $line\n"; done) < <(/bin/grep thatword * | /usr/bin/tee -a files_that_matched_thatword)

Spowoduje to wykonanie prostej operacji ls -A w bieżącym katalogu, a następnie przekazanie instrukcji pętli do każdego wyniku od ls -A do (i tutaj jest to trudne!) Grep "to słowo" w każdym z tych wyników i wykonanie tylko poprzedniego printf (na czerwono), jeśli rzeczywiście znalazł plik z „tym słowem”. Loguje również wyniki grep do nowego pliku tekstowego files_that_matched_thatword.

Przykładowe dane wyjściowe:

ls -A; (while read line; do printf "\e[1;31mFound a file\e[0m: $line\n"; done) < <(/bin/grep thatword * | /usr/bin/tee -a files_that_matched_thatword)

index.html

Wszystko to po prostu wydrukowało wynik ls-A, nic specjalnego. Dodaj coś, aby tym razem grep:

echo "thatword" >> newfile

Teraz uruchom ponownie:

ls -A; (while read line; do printf "\e[1;31mFound a file\e[0m: $line\n"; done) < <(/bin/grep thatword * | /usr/bin/tee -a files_that_matched_thatword)

files_that_matched_thatword  index.html  newfile

Found a file: newfile:thatword

Chociaż być może jest to bardziej wyczerpująca odpowiedź, niż szukasz obecnie, uważam, że trzymanie takich przydatnych notatek przyniesie ci więcej korzyści w przyszłych przedsięwzięciach.

keypulse
źródło