Używanie danych odczytanych z potoku zamiast z pliku w opcjach polecenia

18

Według definicji man to polecenie pobiera dane wejściowe z pliku.

$ command -r FILENAME

Załóżmy, że FILENAMEjest to plik zawierający listę nazw plików, tak jak został wygenerowany przy użyciu ls > FILENAME.

Jak zamiast tego mogę podać polecenie z wynikiem ls bezpośrednio? W mojej głowie powinno być możliwe coś takiego:

$ ls | command -r

Ale tak nie jest, wynik ls nie jest uzależniony od argumentu. Wynik:

Usage: command -r FILENAME
error: -r option requires an argument

Jak mogę uzyskać pożądany efekt?

Paolo
źródło
Więcej odpowiedzi na to pytanie w pokrewnym pytaniu: Jak przekazać ciąg do polecenia oczekującego pliku?
ilkkachu

Odpowiedzi:

31

Zależy to od polecenia. Niektóre polecenia, które czytają z pliku, oczekują, że plik będzie zwykłym plikiem, którego rozmiar jest znany z góry i który można odczytać z dowolnej pozycji i przewinąć do tyłu. Jest to mało prawdopodobne, jeśli zawartość pliku jest listą nazw plików: wówczas polecenie prawdopodobnie będzie zawierało potok, który będzie czytał sekwencyjnie od początku do końca. Istnieje kilka sposobów przesyłania danych za pomocą potoku do polecenia oczekującego nazwy pliku.

  • Wiele poleceń traktuje -jako specjalną nazwę, co oznacza odczyt ze standardowego wejścia zamiast otwierania pliku. To konwencja, a nie obowiązek.

    ls | command -r -
  • Wiele wariantów uniksowych udostępnia specjalne pliki, /devktóre wyznaczają standardowe deskryptory. Jeśli /dev/stdinistnieje, otwarcie go i odczytanie z niego jest równoważne odczytowi ze standardowego wejścia; podobnie /dev/fd/0jeśli istnieje.

    ls | command -r /dev/stdin
    ls | command -r /dev/fd/0
  • Jeśli twoją powłoką jest ksh, bash lub zsh, możesz sprawić, że powłoka zajmie się przydzielaniem deskryptora pliku. Główną zaletą tej metody jest to, że nie jest ona powiązana ze standardowym wejściem, więc możesz użyć standardowego wejścia do czegoś innego i możesz użyć go więcej niż jeden raz.

    command -r <(ls)
  • Jeśli polecenie oczekuje, że nazwa ma określoną formę (zazwyczaj określone rozszerzenie), możesz spróbować oszukać ją za pomocą dowiązania symbolicznego.

    ln -s /dev/fd/0 list.foo
    ls | command -r list.foo

    Lub możesz użyć nazwanego potoku.

    mkfifo list.foo
    ls >list.foo &
    command -r list.foo

Zauważ, że generowanie listy plików lsjest problematyczne, ponieważ lsma tendencję do zmieniania nazw plików, gdy zawierają one niedrukowalne znaki. printf '%s\n' *jest bardziej niezawodny - wypisze każdy bajt dosłownie w nazwach plików. Nazwy plików zawierające znaki nowej linii nadal będą powodować problemy, ale jest to nieuniknione, jeśli polecenie oczekuje listy nazw plików oddzielonych znakami nowej linii.

Gilles „SO- przestań być zły”
źródło
+1 za wyliczenie wszystkich możliwości. Zastąpienie procesu IMO jest prawidłową odpowiedzią na tę sytuację.
glenn jackman
7

Powinno być:

ls | xargs -n 1 command -r

Edycja: dla nazw z pustymi spacjami:

ls | xargs -d '\n' -n 1 command -r
ghm1014
źródło
Może to być problematyczne, jeśli commanduważa, że ​​ma wszystkie nazwy plików potrzebne w wierszu poleceń jednocześnie. Niektóre wersje xargs dają commandjednocześnie kilka nazw plików (10, jak mi się wydaje). Wygląda na to, że GNU xargs daje wszystko naraz.
Bruce Ediger,
2

Właściwie jedynym znanym mi niezawodnym rozwiązaniem, które może obsłużyć wszystkie nazwy plików, w tym te z nową linią, są:

find . -maxdepth 1 -print0 | xargs -n 1 -0 command -r

W tym przypadku jedynym niedozwolonym znakiem w nazwie pliku jest znak null, który i tak nie jest dozwolony w nazwach plików.

Linus Swälas
źródło
1

Wiele poleceń akceptuje -jako „nazwę pliku”, co oznacza „użyj standardowego wejścia”, ale ta konwencja nie jest uniwersalna. Przeczytaj stronę podręcznika.

dmckee
źródło
1

Powinno to zadziałać dla Twojego celu: ls | command -r /dev/stdin

alex
źródło