jak przekazać wynik `find` jako listę plików?


11

Sytuacja jest taka, że ​​mam odtwarzacz MP3, mpg321który przyjmuje listę plików jako argument. Trzymam moją muzykę w katalogu o nazwie „muzyka”, w którym jest jeszcze kilka katalogów. Chcę po prostu grać we wszystkie, więc uruchamiam program

mpg321 $(find /music -iname "*\.mp3")

. Problem polega na tym, że niektóre nazwy plików mają spacje, a program dzieli te nazwy na mniejsze części i narzeka na brakujące pliki. Zawijanie wyniku findw cudzysłów

mpg321 "$(find /music -iname "*\.mp3")"

nie pomaga, ponieważ wszystko stanie się jedną wielką „nazwą pliku”, której oczywiście nie można znaleźć.

Jak mam to zrobić? Jeśli to ma znaczenie, używam bash, ale wkrótce przejdę do zsh.

Odpowiedzi:


17

Spróbuj użyć find's -print0lub -printfopcji w połączeniu z xargspodobnym:

find /music -iname "*\.mp3" -print0 | xargs -0 mpg321

Jak to działa, wyjaśniono na stronie podręcznika find :

-print0

Prawdziwe; wypisuje pełną nazwę pliku na standardowym wyjściu, a następnie znak null (zamiast znaku nowej linii, którego używa -print). Dzięki temu nazwy plików zawierające znaki nowej linii lub inne białe znaki są poprawnie interpretowane przez programy przetwarzające dane wyjściowe wyszukiwania. Ta opcja odpowiada opcji -0 xargs.


Przepraszamy za całą edycję. Wydaje mi się, że pamiętam, że wzorzec -print0 xarg -0 nie działa na innych typach białych znaków, ale nie mogłem znaleźć przykładu.
Steven D

o tak, to działa! ale wciąż zastanawiam się, dlaczego mpg321 $(find /music -iname "*\.mp3" -print0)nie?
phunehehe

1
Cóż, uważam, że to nie działa z dwóch powodów: (1) nazwy plików są oddzielone znakami null, co nie jest tym, czego w ogóle oczekuje mpg321, i (2) xargs pracuje nad ucieczką od innych białych znaków, a nie odnaleźć.
Steven D

2
@phunehehe, @Steven: mpg321nie ma z tym nic wspólnego, to powłoka, która finddzieli dane wyjściowe na osobne argumenty. I -print0 | xargs -0będzie działać z każdą możliwą nazwą pliku.
Gilles „SO- przestań być zły”

8
find /music -iname "*\.mp3" -exec mpg123 {} +

Dzięki GNU find możesz także używać -print0ixargs -0 , ale nauka nowego narzędzia nie ma sensu. -exec ... {} +Składnia pobiera niewielką wzmiankę, ponieważ Linux nabył go najpóźniej -print0, ale nie ma powodu, aby nie używać go teraz.

W przypadku Zsh lub Bash 4 jest to o wiele prostsze:

mpg123 **/*.[Mm][Pp]3

Tylko w Zsh możesz ustawić (część) wzorca bez rozróżniania wielkości liter:

mpg123 (#i)**/*.mp3

+1 do tego, „find -print0” to brzydki hack.
p-statyczny

2

Myślę, że rozwiązanie Stevena jest najlepsze, ale innym sposobem jest użycie -Iflagi xargs , która pozwala określić ciąg, który zostanie następnie zastąpiony w poleceniu argumentem (zamiast po prostu dołączać argument na końcu polecenia). Możesz użyć tego do zacytowania argumentu:

find /music -iname "*\.mp3" | xargs -0 -Ifoo mpg321 "foo"

Nie rozumiem tej odpowiedzi ... Używasz -0flagi xargs bez find's -print0. Również -Iflaga xargs ma szereg konsekwencji. W przeciwieństwie do zwykłego, xargsktóry skompresuje wszystkie linie ze standardowego wejścia w jedno polecenie, xargs -Iwykona się mpg321potencjalnie setki lub tysiące razy (raz dla każdego pliku), co z pewnością nie jest zamierzone. Należy również pamiętać, że cytowanie foo nie jest konieczne, ponieważ xargsrobi to wewnętrznie. Zauważ, że jeśli skompresujesz wszystkie nazwy plików do linii i wyślesz je xargs -I, nie zostaną one otwarte.
Sześć

1

Zazwyczaj najlepiej jest bezpośrednio, -exec ${tgt_process} \{\} +ale jeśli z jakiegoś powodu potrzebujesz uzyskać listę nazw plików w pliku lub strumieniu find, której potrzebujesz, możesz:

find -exec sh -c 'printf "///%s///\n" "$@"' -- \{\} +

Z tego możesz wyciągnąć dwa unikalne ciągi. Na początku każdej nazwy pliku znajduje się ciąg, \n///a na końcu każdej nazwy pliku jest ciąg ///\n. Te dwa ciągi nie występują nigdzie indziej na findwyjściu, z wyjątkiem tych pozycji, niezależnie od znaków zawartych w nazwach plików.

Ponadto powyższe użycie jest bazowe, przenośne POSIX i można na nim polegać, działając na prawie każdym systemie uniksowym. Nie dotyczy to stosowania ogranicznika zerowego bajtu - pomimo jego wygody - zalecanego przez niektóre inne.

Ale znowu jest to konieczne tylko wtedy, gdy nie możesz bezpośrednio skierować -execswojego $tgt_processz jakiegokolwiek powodu, ponieważ taki powinien być twój cel. Po pierwsze, powyższa metoda nadal wymaga analizy. Na przykład, jeśli chcesz cytować każdą nazwę powłoki, najpierw musisz upewnić się, że wszystkie twarde cudzysłowy w nazwie pliku zostały pominięte:

find ... + | sed 's|'\''|&"&"&|g;s|///|'\''|g'

To powoduje, że tablica nazw plików jest poprawnie uciekana przez powłokę, niezależnie od tego, jakie mogą być ich znaki składowe. Teraz musisz tylko mieć nadzieję, że twoja aplikacja po stronie odbierającej go nie zmieni.


0

Innym sposobem jest uniknięcie wszystkich znaków specjalnych, które występują w nazwach plików. Na przykład:

find /music -iname "*\.mp3" | sed 's!\([] \*\$\/&[]\)!\\\1!g' | xargs mpg321

Spowoduje to w zasadzie przekazanie poprawnie nazw plików do xargs w celu wykonania i nie będzie żadnych problemów.


1
To wciąż psuje nowe znaki w nazwach plików. Równie dobrze możesz sed 's|.|\\&|g'- właśnie to zaleca POSIX.
mikeserv
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.