Spraw, aby xargs obsługiwał nazwy plików zawierające spacje

252
$ ls *mp3 | xargs mplayer  

Playing Lemon.  
File not found: 'Lemon'  
Playing Tree.mp3.  
File not found: 'Tree.mp3'  

Exiting... (End of file)  

Moje polecenie kończy się niepowodzeniem, ponieważ plik „Lemon Tree.mp3” zawiera spacje, więc xargs myśli, że to dwa pliki. Czy mogę sprawić, by find + xargs działał z takimi nazwami plików?

Pokaż klucz
źródło
Zamiast ls |grep mp3 |sed -n "7p"ciebie możesz po prostu użyć echo "Lemon Tree.mp3".
Micha Wiedenmann
Odpowiedzi na to pytanie
udziela

Odpowiedzi:

255

xargsKomenda bierze białe znaki (spacje, tabulatory, nowych linii) jako ograniczniki. Możesz zawęzić listę tylko do nowych znaków wiersza („\ n”) za pomocą -dopcji takiej jak ta:

ls *.mp3 | xargs -d '\n' mplayer

Działa tylko z GNU xargs. W przypadku systemów BSD użyj takiej -0opcji:

ls *.mp3 | xargs -0 mplayer

Ta metoda jest prostsza i działa również z GNU xargs.

Promień
źródło
6
Najlepsza odpowiedź do ogólnego użytku! Działa to nawet wtedy, gdy poprzednie polecenie nie brzmi „znajdź”
nexayq
28
Niestety ta opcja nie jest dostępna w systemie OS X.
Thomas Tempelmann
25
@Thomas W przypadku OS X flagą jest -E, np .:xargs -E '\n'
30
W OS X -E '\ n' nie miało dla mnie żadnego efektu, ani nie spodziewałbym się, że zmieni to eofstr, a nie separator rekordów. Byłem jednak w stanie wykorzystać flagę -0 jako rozwiązanie, nawet jeśli poprzednie polecenie nie brzmiało „znajdź”, symulując efekt flagi -print0 find w moich danych wejściowych, np .: ls * mp3 | tr '\ n' '\ 0' | xargs -0 mplayer
biomiker
10
Dla OS X, można „piwny zainstalować Findutils”, który daje „gxargs” polecenie, które robi mieć przełącznik -d.
Tom De Leu,
213

Narzędzie xargs odczytuje ze standardowego wejścia łańcuchy rozdzielone spacją, tabulatorem, znakiem nowej linii i końca pliku i wykonuje narzędzie z łańcuchami jako argumentami.

Chcesz uniknąć używania spacji jako separatora. Można to zrobić, zmieniając ogranicznik dla xargs. Zgodnie z instrukcją:

 -0      Change xargs to expect NUL (``\0'') characters as separators,
         instead of spaces and newlines.  This is expected to be used in
         concert with the -print0 function in find(1).

Jak na przykład:

 find . -name "*.mp3" -print0 | xargs -0 mplayer

Aby odpowiedzieć na pytanie dotyczące odtwarzania siódmego mp3; łatwiej jest uruchomić

 mplayer "$(ls *.mp3 | sed -n 7p)"
Jens
źródło
10
To używa GNU findi GNU xargs; nie wszystkie wersje tych programów obsługują te opcje (choć należy uzasadnić, że powinny).
Jonathan Leffler
1
@JonathanLeffler s / GNU / FreeBSD / g; POSIX niestety boi się znaków NUL w plikach tekstowych i nie ma jeszcze wystarczająco dużo terapii :-) Moja rada w rzeczywistości dotyczy opcji nieprzenośnych.
Jens
6
I Mac OS X (pochodna BSD) ma findz -print0i xargsz -0. Jednak AFAIK, HP-UX, AIX i Solaris nie (ale mam nadzieję, że będę poprawiony: HP-UX 11i nie; Solaris 10 nie; AIX 5.x nie; ale to nie są bieżące wersje ). Na przykład nie byłoby trudno zmienić sed„linie” kończące się '\0'na '\n', a POSIX 2008 ułatwiłby getdelim()zarządzanie.
Jonathan Leffler
2
+1 + 1 trik za korzystanie ze ścieżek plików zawierających pliki list: cat $ file_paths_list_file | perl -ne 's | \ n | \ 000 | g; print' | xargs -0 zip $ zip_package
Yordan Georgiev
2
Dobry pomysł, aby zastąpić nowe wiersze wartością NUL - musiałem to zrobić w systemie osadzonym, który nie miał GNU find ani GNU xargs ani perl - ale można użyć polecenia tr, aby zrobić to samo: cat $ file_paths_list_file | tr '\ n' '\ 0' | xargs -0 du-hms
joensson
28

Próbować

find . -name \*.mp3 -print0 | xargs -0 mplayer

zamiast

ls | grep mp3 
Scott C. Wilson
źródło
16

xargs w systemie MacOS nie ma opcji -d, dlatego w tym rozwiązaniu zastosowano opcję -0.

Poproś ls, aby wyprowadzał jeden plik w wierszu, a następnie przetłumacz znaki nowej linii na wartości null i powiedz xargs, aby używał wartości null jako separatora:

ls -1 *mp3 | tr "\n" "\0" | xargs -0 mplayer

Graeme Pyle
źródło
8
find . -name 'Lemon*.mp3' -print0 | xargs 0 -i mplayer '{}' 

Pomogło to w moim przypadku usunąć różne pliki ze spacjami. Powinno też działać z mplayerem. Niezbędną sztuczką są cytaty. (Testowany na Linux Xubuntu 14.04.)

Bendel
źródło
7

Odpowiedź Dick.Guertina [1] sugeruje, że można uciec ze spacji w nazwie pliku, jest cenną alternatywą dla innych sugerowanych tu rozwiązań (takich jak użycie znaku pustego zamiast separatora zamiast białych znaków). Ale może być prostsze - tak naprawdę nie potrzebujesz wyjątkowej postaci. Możesz po prostu dodać dodane spacje bezpośrednio:

ls | grep ' ' | sed 's| |\\ |g' | xargs ...

Ponadto grep jest konieczny tylko wtedy, gdy chcesz tylko pliki ze spacjami w nazwach. Bardziej ogólnie (np. Podczas przetwarzania partii plików, z których niektóre mają spacje, niektóre nie), po prostu pomiń grep:

ls | sed 's| |\\ |g' | xargs ...

Wtedy oczywiście nazwa pliku może zawierać inne białe znaki niż puste (np. Tabulator):

ls | sed -r 's|[[:blank:]]|\\\1|g' | xargs ...

Zakłada się, że masz sed, który obsługuje -r (rozszerzone wyrażenia regularne), takie jak GNU sed lub najnowsze wersje bsd sed (np. FreeBSD, który oryginalnie przeliterował opcję „-E” przed FreeBSD 8 i obsługuje zarówno -r & -E dla kompatybilności przynajmniej przez FreeBSD 11). W przeciwnym razie możesz użyć podstawowego wyrażenia wyrażenia regularnego w klasie wyrażeń regularnych i ręcznie wprowadzić znaki spacji i tabulatorów w []ogranicznikach.

[1] Być może jest to bardziej odpowiednie jako komentarz lub edycja tej odpowiedzi, ale w tej chwili nie mam wystarczającej reputacji, aby komentować i mogę jedynie sugerować zmiany. Ponieważ powyższe formularze powyżej (bez grep) zmieniają zachowanie oryginalnej odpowiedzi Dicka. Guertin, bezpośrednia edycja może i tak nie jest odpowiednia.

Juan
źródło
1
szaleni faceci unixowi, którzy uruchamiają skrypty, które nazywają pliki bez uwzględnienia ich wyników, to kto
andrew lorien 11.04.17
4

ls | grep mp3 | sed -n "7p" | xargs -i mplayer {}

Zauważ, że w powyższym poleceniu xargsbędzie wywoływał mplayerponownie dla każdego pliku. Może to być niepożądane mplayer, ale może być w porządku dla innych celów.

Acumenus
źródło
1
Przydatny dodatek do istniejących odpowiedzi, ale warto zauważyć, że spowoduje mplayerto wywołanie nowego dla każdego pliku. Ma to znaczenie, jeśli spróbujesz np . ... | xargs -I{} mplayer -shuffle {}: zagra to w całkowicie deterministycznej kolejności -shuffle.
1
Prawdopodobnie zwykle nie jest to zamierzenie. xargsjest najczęściej używany z poleceniami, które akceptują listę nazw plików (prosty przykład rm:) i próbuje przekazać tyle nazw plików, ile może zmieścić się w każdym wywołaniu, w razie potrzeby dzieląc je na wiele wywołań. Możesz zobaczyć różnicę, gdy używasz polecenia, w którym widoczne jest każde wywołanie, takiego jak echo(domyślnie): seq 0 100000 | xargsdrukuje wszystkie liczby od 0 do 23695 (specyficzne dla platformy, ale tak się dzieje w moim systemie) w pierwszym wierszu, do 45539 na linii 2 itd. I masz rację, w przypadku większości poleceń nie będzie to miało znaczenia.
4

W systemie macOS 10.12.x (Sierra), jeśli masz spacje w nazwach plików lub podkatalogach, możesz użyć następujących opcji:

find . -name '*.swift' -exec echo '"{}"' \; |xargs wc -l
Byron Formwalt
źródło
2

Zależy to od (a) stopnia przywiązania do liczby 7, w przeciwieństwie do, powiedzmy, cytryn i (b) tego, czy którakolwiek z twoich nazw plików zawiera znaki nowej linii (i czy chcesz zmienić ich nazwy, jeśli tak jest).

Jest na to wiele sposobów, ale niektóre z nich to:

mplayer Lemon*.mp3

find . -name 'Lemon*.mp3' -exec mplayer {} ';'

i=0
for mp3 in *.mp3
do
    i=$((i+1))
    [ $i = 7 ] && mplayer "$mp3"
done

for mp3 in *.mp3
do
    case "$mp3" in
    (Lemon*) mplayer "$mp3";;
    esac
done

i=0
find . -name *.mp3 |
while read mp3
do
    i=$((i+1))
    [ $i = 7 ] && mplayer "$mp3"
done

readPętla nie działa, jeśli nazwy plików zawierają znaki nowej linii; pozostałe działają poprawnie nawet z nowymi liniami w nazwach (nie mówiąc już o spacjach). Za moje pieniądze, jeśli masz nazwy plików zawierające nową linię, powinieneś zmienić nazwę pliku bez nowej linii. Używanie podwójnych cudzysłowów wokół nazwy pliku jest kluczem do prawidłowego działania pętli.

Jeśli masz GNU findi GNU xargs(lub FreeBSD (* BSD?) Lub Mac OS X), możesz także użyć opcji -print0i -0, jak w:

find . -name 'Lemon*.mp3' -print0 | xargs -0 mplayer

Działa to niezależnie od zawartości nazwy (jedynymi dwoma znakami, które nie mogą pojawić się w nazwie pliku, są ukośnik i NUL, a ukośnik nie powoduje problemów na ścieżce pliku, więc użycie NUL jako ogranicznika nazwy obejmuje wszystko). Jeśli jednak chcesz odfiltrować pierwsze 6 pozycji, potrzebujesz programu, który obsługuje „linie” zakończone przez NUL zamiast nowego wiersza ... i nie jestem pewien, czy istnieją.

Pierwszy jest zdecydowanie najprostszy dla konkretnego przypadku; może jednak nie uogólniać się na inne scenariusze, których jeszcze nie wymieniono.

Jonathan Leffler
źródło
2

Wiem, że nie odpowiadam xargsbezpośrednio na pytanie, ale warto wspomnieć findo -execopcji.

Biorąc pod uwagę następujący system plików:

[root@localhost bokeh]# tree --charset assci bands
bands
|-- Dream\ Theater
|-- King's\ X
|-- Megadeth
`-- Rush

0 directories, 4 files

Polecenie find można wykonać, aby obsłużyć przestrzeń w Dream Theatre i King's X. Aby znaleźć perkusistów każdego zespołu za pomocą grep:

[root@localhost]# find bands/ -type f -exec grep Drums {} +
bands/Dream Theater:Drums:Mike Mangini
bands/Rush:Drums: Neil Peart
bands/King's X:Drums:Jerry Gaskill
bands/Megadeth:Drums:Dirk Verbeuren

W -execopcji {}oznacza nazwę pliku wraz ze ścieżką. Pamiętaj, że nie musisz uciekać od niego ani umieszczać go w cudzysłowie.

Różnica między -execterminatorami ( +i \;) polega na tym +, że grupuje jak najwięcej nazw plików w jednym wierszu poleceń. Natomiast \;wykona polecenie dla każdej nazwy pliku.

Tak, find bands/ -type f -exec grep Drums {} +spowoduje:

grep Drums "bands/Dream Theater" "bands/Rush" "bands/King's X" "bands/Megadeth"

i find bands/ -type f -exec grep Drums {} \;spowoduje:

grep Drums "bands/Dream Theater"
grep Drums "bands/Rush"
grep Drums "bands/King's X"
grep Drums "bands/Megadeth"

W takim przypadku grepefektem ubocznym jest wydrukowanie nazwy pliku lub nie.

[root@localhost bokeh]# find bands/ -type f -exec grep Drums {} \;
Drums:Mike Mangini
Drums: Neil Peart
Drums:Jerry Gaskill
Drums:Dirk Verbeuren

[root@localhost bokeh]# find bands/ -type f -exec grep Drums {} +
bands/Dream Theater:Drums:Mike Mangini
bands/Rush:Drums: Neil Peart
bands/King's X:Drums:Jerry Gaskill
bands/Megadeth:Drums:Dirk Verbeuren

Oczywiście, grepopcje -hi -Hbędą kontrolować, czy nazwa pliku jest drukowana, niezależnie od tego, jak grepzostanie wywołana.


xargs

xargs może również kontrolować, jak pliki man są w wierszu poleceń.

xargsdomyślnie grupuje wszystkie argumenty w jednym wierszu. Aby zrobić to samo, -exec \;co używa xargs -l. Zauważ, że -topcja nakazuje xargswydrukowanie polecenia przed jego wykonaniem.

[root@localhost bokeh]# find ./bands -type f  | xargs -d '\n' -l -t grep Drums
grep Drums ./bands/Dream Theater 
Drums:Mike Mangini
grep Drums ./bands/Rush 
Drums: Neil Peart
grep Drums ./bands/King's X 
Drums:Jerry Gaskill
grep Drums ./bands/Megadeth 
Drums:Dirk Verbeuren

Zobacz, że -lopcja mówi xargsowi, aby wykonał grep dla każdej nazwy pliku.

W porównaniu do wartości domyślnej (tj. Brak -lopcji):

[root@localhost bokeh]# find ./bands -type f  | xargs -d '\n'  -t grep Drums
grep Drums ./bands/Dream Theater ./bands/Rush ./bands/King's X ./bands/Megadeth 
./bands/Dream Theater:Drums:Mike Mangini
./bands/Rush:Drums: Neil Peart
./bands/King's X:Drums:Jerry Gaskill
./bands/Megadeth:Drums:Dirk Verbeuren

xargsma lepszą kontrolę nad liczbą plików w wierszu poleceń. Podaj -lopcję maksymalnej liczby plików na polecenie.

[root@localhost bokeh]# find ./bands -type f  | xargs -d '\n'  -l2 -t grep Drums
grep Drums ./bands/Dream Theater ./bands/Rush 
./bands/Dream Theater:Drums:Mike Mangini
./bands/Rush:Drums: Neil Peart
grep Drums ./bands/King's X ./bands/Megadeth 
./bands/King's X:Drums:Jerry Gaskill
./bands/Megadeth:Drums:Dirk Verbeuren
[root@localhost bokeh]# 

Zobacz, że grepzostało wykonane z dwoma nazwami plików z powodu -l2.

ryjówka
źródło
1

Biorąc pod uwagę konkretny tytuł tego postu, oto moja sugestia:

ls | grep ' ' | tr ' ' '<' | sed 's|<|\\ |g'

Chodzi o to, aby zamienić spacje na dowolne unikalne znaki, takie jak „<”, a następnie zmienić je na „\”, odwrotny ukośnik, a następnie spację. Następnie możesz potokować to dowolne polecenie, takie jak:

ls | grep ' ' | tr ' ' '<' | sed 's|<|\\ |g' | xargs -L1 GetFileInfo

Kluczem tutaj są komendy „tr” i „sed”; i możesz użyć dowolnego znaku oprócz „<”, takiego jak „?” lub nawet znak tabulacji.

Dick.Guertin
źródło
Jaki jest cel objazdu przez tr? Dlaczego nie tylko ls *.mp3 | sed -n '7!b;s/\([[:space:]]\)/\\\1/g;p'?
tripleee
1
Znalazłem, że „tr” „?” „Eliminuje potrzebę„ sed ”. Pojedynczy "?" znak nie jest pusty, ale pasuje do KAŻDEGO pojedynczego znaku, w tym przypadku: pusty. Szanse na to, że jest to coś innego, są dość niewielkie i dopuszczalne, ponieważ próbujesz przetwarzać WSZYSTKIE pliki z rozszerzeniem .mp3: "ls | grep '' | tr '' '?' | xargs -L1 GetFileInfo "
Dick Guertin
Możesz także obsługiwać „tab” w tym samym czasie: tr '\ t' '??' obsługuje oba.
Dick Guertin
1

Pomocne mogą być alternatywne rozwiązania ...

Możesz także dodać znak zerowy na końcu linii za pomocą Perla, a następnie użyć -0opcji w xargs. W przeciwieństwie do xargs -d '\ n' (w zatwierdzonej odpowiedzi) - działa to wszędzie, łącznie z OS X.

Na przykład, aby rekurencyjnie wyświetlać (wykonywać, przenosić itp.) Pliki MPEG3, które mogą zawierać spacje lub inne zabawne znaki - użyłbym:

find . | grep \.mp3 | perl -ne 'chop; print "$_\0"' | xargs -0  ls

(Uwaga: Do filtrowania wolę łatwiejszą do zapamiętania składnię "| grep" niż argumenty "find's" --name).

Charlie Dalsass
źródło