Dlaczego polecenie „ls | plik ”działa?

32

Uczyłem się o linii poleceń i dowiedziałem się, że |(potok) ma przekierowywać dane wyjściowe z polecenia do wejścia innego. Dlaczego więc to polecenie ls | filenie działa?

file input jest jedną z wielu nazw plików, takich jak file filename1 filename2

lswyjście to lista katalogów i plików w folderze, więc pomyślałem, że ls | filepowinien pokazać typ pliku każdego pliku w folderze.

Jednak gdy go używam, dane wyjściowe są następujące:

    Usage: file [-bcEhikLlNnprsvz0] [--apple] [--mime-encoding] [--mime-type]
        [-e testname] [-F separator] [-f namefile] [-m magicfiles] file ...
    file -C [-m magicfiles]
    file [--help]

Ponieważ wystąpił błąd podczas używania filepolecenia

IanC
źródło
2
Jeśli używasz zwykłego ls, oznacza to, że chcesz, aby wszystkie pliki w bieżącym katalogu były obsługiwane za pomocą filepolecenia. ... Dlaczego więc nie po prostu zrobić:, file *który odpowie linią dla każdego pliku, folderu.
Knud Larsen,
file *to najmądrzejszy sposób, po prostu zastanawiałem się, dlaczego użycie lswyjścia nie działa. Wątpliwości wyjaśnione :)
IanC,
6
Założenie jest wadliwe: „wejście pliku to jedna z większej liczby nazw plików, na przykład plik nazwa_pliku1 nazwa_pliku2” To nie jest dane wejściowe. Są to argumenty wiersza poleceń, jak zauważa poniżej John Kugelman.
Monty Harder
3
Stycznie parsowaniels jest generalnie złym pomysłem.
kojiro

Odpowiedzi:

71

Podstawową kwestią jest to, że fileoczekuje się , że nazwy plików będą argumentami wiersza poleceń, a nie standardowego wejścia. Kiedy piszesz, ls | filedane wyjściowe lssą przekazywane jako dane wejściowe do file. Nie jako argumenty, jako dane wejściowe.

Co za różnica?

  • Argumenty wiersza poleceń występują podczas pisania flag i nazw plików po poleceniu, jak w cmd arg1 arg2 arg3. W skryptach powłoki te argumenty są dostępne jako zmienne $1, $2, $3, itd. W C wy mieliście do nich dostęp za pośrednictwem char **argvi int argcargumenty main().

  • Standardowe wejście, stdin, to strumień danych. Niektóre programy podoba catlub wcodczytać z stdin gdy oni nie podano żadnych argumentów wiersza polecenia. W skrypcie powłoki można użyć readjednego wiersza danych wejściowych. W C możesz użyć scanf()lub getchar()spośród różnych opcji.

filezwykle nie odczytuje ze standardowego wejścia. Oczekuje, że co najmniej jedna nazwa pliku zostanie przekazana jako argument. Dlatego wypisuje użycie podczas pisania ls | file, ponieważ nie przekazałeś argumentu.

Możesz użyć xargsdo konwersji standardowego wejścia na argumenty, jak w ls | xargs file. Jednak, jak wspomina terdon , parsowanie lsjest złym pomysłem. Najbardziej bezpośrednim sposobem na to jest po prostu:

file *
John Kugelman wspiera Monikę
źródło
2
Lub wymuś filepobranie nazw plików z jego danych wejściowych, używając ls | file -f -. Nadal zły pomysł na c.
spectras
2
@Braiam> Właśnie o to chodzi. I to lswyjście rur do filestandardowego wejścia. Wypróbuj to.
spectras
4
@Braiam> Rzeczywiście, jest to marnotrawstwo i niebezpieczne. Ale działa i miło jest porównać go z lepszymi opcjami, jeśli PO uczy się korzystać z przekierowań. Dla kompletności mógłbym również wspomnieć file $(ls), co również działa, w jeszcze inny sposób.
spectras
2
Myślę, że po przeczytaniu wszystkich odpowiedzi mam szerszy obraz problemu, chociaż myślę, że będę potrzebować dalszej lektury, aby naprawdę wszystko zrozumieć. Po pierwsze, najwyraźniej użycie pipingu i przekierowania nie analizuje danych wyjściowych jako argumentów , ale jako STDIN . Które nadal muszę czytać, aby lepiej zrozumieć, ale podanie powierzchownych argumentów wyszukiwania wydaje się, że tekst jest analizowany w programie w tablicy, a STDIN jako sposób łączenia informacji o pliku lub pliku wyjściowym (nie wszystkie programy są zaprojektowane do praca z tym „
poolingiem
3
Po drugie, użycie ls do stworzenia listy nazw plików wydaje się złym pomysłem, ze względu na znaki specjalne, które są akceptowane w nazwach plików, ale mogą skończyć się mylącym wyjściem na ls . Ponieważ używa on nowej linii jako separatora między nazwami plików, a nazwy plików mogą zawierać znaki nowej linii i inne znaki specjalne, końcowy wynik może nie być dokładny.
IanC,
18

Ponieważ, jak mówisz, wejściem filemuszą być nazwy plików . Wynikiem jest lsjednak tylko tekst. To, że jest to lista nazw plików, nie zmienia faktu, że jest to po prostu tekst, a nie lokalizacja plików na dysku twardym.

Gdy zobaczysz wydruk wydrukowany na ekranie, zobaczysz tekst. To, czy ten tekst jest wierszem, czy listą nazw plików, nie ma znaczenia dla komputera. Wie tylko, że to tekst. Dlatego możesz przekazać dane wyjściowe lsdo programów, które przyjmują tekst jako dane wejściowe (chociaż tak naprawdę nie powinieneś ):

$ ls / | grep etc
etc

Tak więc, aby użyć danych wyjściowych polecenia, które wyświetla nazwy plików jako tekst (np. lsLub find) jako dane wejściowe dla polecenia, które przyjmuje nazwy plików, musisz użyć kilku sztuczek. Typowym narzędziem do tego jest xargs:

$ ls
file1 file2

$ ls | xargs wc
 9  9 38 file1
 5  5 20 file2
14 14 58 total

Jak już powiedziałem wcześniej, naprawdę nie chcesz analizować wyników ls. Coś takiego findjest lepsze ( print0wypisuje \0zamiast newilne po każdej nazwie pliku, a -0of xargspozwala na radzenie sobie z takimi danymi wejściowymi; jest to sztuczka, aby twoje polecenia działały z nazwami plików zawierającymi nowe linie):

$ find . -type f -print0 | xargs -0 wc
 9  9 38 ./file1
 5  5 20 ./file2
14 14 58 total

Który ma też swój własny sposób na to, bez potrzeby xargs:

$ find . -type f -exec wc {} +
 9  9 38 ./file1
 5  5 20 ./file2
14 14 58 total

Na koniec możesz także użyć pętli powłoki. Należy jednak pamiętać, że w większości przypadków xargsbędzie znacznie szybszy i bardziej wydajny. Na przykład:

$ for file in *; do wc "$file"; done
 9  9 38 file1
 5  5 20 file2
terdon
źródło
Po stronie problemem jest to, że filenie wydaje się właściwie odczytać stdin chyba że podano wyraźne -zastępczy: porównanie file foo, echo foo | fileoraz echo foo | file -; w rzeczywistości jest to prawdopodobnie powód komunikatu o użyciu w przypadku OP (tzn. nie jest tak naprawdę dlatego, że wynikiem lsjest „po prostu tekst”, ale raczej dlatego, że lista argumentów filejest pusta)
steeldriver
@steeldriver yeah. AFAIK dotyczy to wszystkich programów, które oczekują plików, a nie tekstu jako danych wejściowych. Po prostu domyślnie ignorują standardowe wejście. Zauważ, że echo foo | file -tak naprawdę nie działa filena pliku, fooale na strumieniu standardowym.
terdon
Cóż, są takie dziwne kaczki (?!) Z catwyjątkiem stdin bez wyjątku, -gdy wydaje mi się, że podane są argumenty pliku?
steeldriver,
3
Ta odpowiedź nie wyjaśnia różnicy między argumentami stdin a wierszem poleceń, a zatem, choć jest bardziej trafna niż odpowiedź zaakceptowana, nadal głęboko wprowadza w błąd z tego samego powodu.
zwolnienie
5
@terdon Myślę, że to poważny błąd w tej sprawie. „plik (1) przyjmuje listę plików do działania jako argumenty wiersza poleceń, a nie jako standardowe dane wejściowe” ma podstawowe znaczenie dla zrozumienia, dlaczego polecenie OP nie działało, a rozróżnienie jest fundamentalne dla skryptów powłoki w ogóle; nie robisz im żadnych przysług, połyskując nad tym.
zwolnienie
6

dowiedziałem się, że „|” (potok) ma na celu przekierowanie wyjścia z polecenia na wejście innego.

Nie „przekierowuje” danych wyjściowych, ale pobiera dane wyjściowe programu i używa ich jako danych wejściowych, podczas gdy plik nie przyjmuje danych wejściowych, ale nazwy plików jako argumenty , które są następnie testowane. Przekierowania nie przekazują tych nazw plików jako argumentów ani potoków, ani później, co robisz.

Co możesz zrobić, to odczytać nazwy plików z pliku z --files-fromopcją, jeśli masz plik z listą wszystkich plików, które chcesz przetestować, w przeciwnym razie po prostu przekaż ścieżki do swoich plików jako argumenty.

Braiam
źródło
6

Przyjęta odpowiedź wyjaśnia, dlaczego polecenie potoku nie działa od razu, a wraz z file *poleceniem oferuje proste, proste rozwiązanie.

Chciałbym zasugerować inną alternatywę, która może się kiedyś przydać. Sztuczka polega na użyciu (`)znaku wstecznego . Wstecz został szczegółowo wyjaśniony tutaj . W skrócie, pobiera dane wyjściowe polecenia zawartego w backticks i zastępuje je jako ciąg znaków w pozostałym poleceniu.

Tak więc find `ls`weźmie wynik lspolecenia i zastąpi go argumentem findpolecenia. Jest to dłuższe i bardziej skomplikowane niż przyjęte rozwiązanie, ale jego warianty mogą być pomocne w innych sytuacjach.

Schmuddi
źródło
Czytam książkę o używaniu wiersza poleceń w Linuksie (wątpliwość przyszła z moim eksperymentowaniem z nim) i przypadkiem właśnie przeczytałem o „zastępowaniu poleceń”. Możesz użyć $ (polecenie) lub command(nie mogę znaleźć kodu ukośnika odwrotnego na moim telefonie), aby rozwinąć wyjście polecenia w bashu i użyć go jako parametru do innych poleceń. Naprawdę przydatne, nawet jeśli użycie go w tym przypadku (z ls ) nadal spowoduje pewne problemy ze względu na znaki specjalne w niektórych nazwach plików.
IanC
@IanC Niestety, większość książek i samouczków na temat bash to śmieci, skażone złymi praktykami, nieaktualna składnia, subtelne błędy; (jedynymi) godnymi zaufania referencjami są programiści bash, czyli instrukcja i kanał IRC #bash na freenode (sprawdź także zasoby powiązane w temacie kanału).
ignis
1
Używanie podstawiania poleceń może być czasem bardzo pomocne, ale w tym kontekście jest dość przewrotne - szczególnie w przypadku ls.
Joe
powiązane również: unix.stackexchange.com/a/5782/107266
matth
5

Wyjście lsprzez potok jest solidnym blokiem danych z 0x0a oddzielającymi każdą linię - tj. Znakiem wysuwu linii - i fileotrzymuje to jako jeden parametr, w którym oczekuje się, że wiele znaków będzie działać na jednym na raz.

Zgodnie z ogólną zasadą nigdy nie używaj lsdo generowania źródła danych dla innych poleceń - pewnego dnia przepłynie on do… rmi wtedy będziesz miał kłopoty!

Lepiej użyć pętli, na przykład, for i in *; do file "$i" ; donektóra da oczekiwany wynik. Cytaty są dostępne w przypadku nazw plików ze spacjami.

Mark Williams
źródło
8
łatwiej: file *;-)
Wayne_Yux 06.07.16
3
@IanC Naprawdę nie mogę wystarczająco podkreślić, że analiza wyniku lsjest bardzo, bardzo złym pomysłem . Nie tylko dlatego, że możesz przekazać go do czegoś szkodliwego, na przykład rm, co ważniejsze, ponieważ psuje się każdą niestandardową nazwę pliku.
terdon
5
Pierwszy akapit znajduje się gdzieś pomiędzy wprowadzającymi w błąd a prostymi nonsensami. Kanały nie mają znaczenia. Drugi akapit jest właściwy z niewłaściwego powodu. Parsowanie ls jest złe, ale nie dlatego, że może być w jakiś sposób magicznie „przesyłane” do rm.
John Kugelman wspiera Monikę
1
Czy rmpobiera nazwy plików ze standardowego wejścia? Myślę, że nie. Ponadto, jako ogólna zasada, lsjest jednym z głównych przykładów źródła danych do korzystania z potoków uniksowych od początku jego istnienia. Dlatego domyślnie jest to prosta jedna nazwa pliku na wiersz bez atrybutów i ozdób, gdy jego wyjściem jest potok, w przeciwieństwie do zwykłego domyślnego formatowania, gdy wyjściem jest terminal.
davidbak
2
@DewiMorgan Ta strona jest skierowana głównie do nietechnicznych odbiorców, więc rozpowszechnianie / zachęcanie do złych nawyków tutaj szkodzi i nie robi nic dobrego. W unix.SE lub innej społeczności technologicznej, której użytkownicy mają wiedzę / środki, aby celować bardzo blisko swoich stóp, nie strzelając w same stopy, twój punkt może mieć rację (odnośnie innych praktyk), ale tutaj nie sprawia, że ​​Twój komentarz wygląda elegancko.
ignis
4

Jeśli chcesz użyć potoku do karmienia, fileużyj opcji, po -fktórej zwykle następuje nazwa pliku, ale możesz także użyć pojedynczego łącznika -do odczytu ze standardowego wejścia, więc

$ ls
cow.pdf  some.txt
$ ls | file -f -
cow.pdf:       PDF document, version 1.4
some.txt:        ASCII text

Sztuczka z łącznikiem -działa z wieloma standardowymi narzędziami wiersza poleceń (chociaż --czasami), więc zawsze warto spróbować.

Narzędzie xargjest znacznie potężniejsze i w większości przypadków potrzebne tylko, jeśli lista argumentów jest zbyt długa (zobacz ten post, aby uzyskać szczegółowe informacje).

deamentiaemundi
źródło
Kiedy jest --? Nigdy tego nie widziałem. --jest zwykle wskaźnikiem „końca flag”.
John Kugelman wspiera Monikę
Tak, ale znalazłem to w kilku przypadkach (ab) używanych w ten sposób przez programistę. Nie pamiętam, gdzie dokładnie (dodam komentarz, jeśli to zrobię), ale pamiętam przekleństwa, które wypowiedziałem, kiedy się dowiedziałem, i te przekleństwa były zdecydowanie NSFW ;-)
deamentiaemundi
2

Działa użyj polecenia jak poniżej

ls | xargs file

Będzie dla mnie lepiej

SuperKrish
źródło