find + xargs: zbyt długa linia argumentów

21

Mam taką linię:

find /foo/bar -name '*.mp4' -print0 | xargs -i {} -0 mv -t /some/path {}

ale dostałem następujący błąd:

xargs: argument line too long

Jestem zdezorientowany. Czy użycie tego nie xargspowinno właśnie pomóc w rozwiązaniu tego problemu?

Uwaga: Wiem, że mogę technicznie użyć -execw find, ale chciałbym zrozumieć, dlaczego powyższe zawodzi, ponieważ rozumiem, że xargspowinienem wiedzieć, jak podzielić dane wejściowe na możliwy do zarządzania rozmiar na argument, który uruchamia. Czy to nie prawda?

To wszystko z Zsh.

Amelio Vazquez-Reina
źródło

Odpowiedzi:

11

Po pierwsze, -iprzełącznik jest przestarzały:

-i[replace-str]
     This  option  is a synonym for -Ireplace-str if replace-str is specified. 
     If the replace-str argument is missing, the effect is the same as -I{}. 
     This option is deprecated; use -I instead.

Kiedy zmieniłem twoje polecenie na to, zadziałało:

$ find /foo/bar -name '*.mp4' -print0 | xargs -I{} -0 mv -t /some/path {}

Przykład

$ find . -print0 | xargs -I{} -0 echo {}
.
./.sshmenu
./The GIT version control system.html
./.vim_SO
./.vim_SO/README.txt
./.vim_SO/.git
./.vim_SO/.git/objects
./.vim_SO/.git/objects/pack
./.vim_SO/.git/objects/pack/pack-42dbf7fe4a9b431a51da817ebf58cf69f5b7117b.idx
./.vim_SO/.git/objects/pack/pack-42dbf7fe4a9b431a51da817ebf58cf69f5b7117b.pack
./.vim_SO/.git/objects/info
./.vim_SO/.git/refs
./.vim_SO/.git/refs/tags
...

Zastosowanie -I{}

Nie należy stosować tego podejścia, ponieważ uruchomiono następującą konstrukcję polecenia:

$ find -print0 ... | xargs -I{} -0 ...

niejawnie włącza się do tych przełączników xargs, -xa -L 1. W -L 1konfiguruje xargstak, że dzwoni polecenia chcesz go uruchomić poprzez pliki w jednym modzie.

To przeczy celowi użycia xargstutaj, ponieważ jeśli dasz mu 1000 plików, uruchomi to mvpolecenie 1000 razy.

Więc jakiego podejścia powinienem użyć?

Możesz to zrobić za pomocą xargs:

$ find /foot/bar/ -name '*.mp4' -print0 | xargs -0 mv -t /some/path

Lub po prostu znajdź to wszystko:

$ find /foot/bar/ -name '*.mp4' -exec mv -t /some/path {} +
slm
źródło
Dzięki! Kiedy powiedziałeś, "This approach shouldn't be used"które podejście należy zastosować? Czy "find /foot/bar/ -name '*.csv' -print0 | xargs -0 mv -t some_dir'"byłoby lepszym rozwiązaniem? Jeśli tak, to w jaki sposób będzie xargswiedzieć w tym przypadku, gdy w mvkomendzie do paszy w argumentach, że dostaje od rury? (czy zawsze umieszcza je na końcu?)
Amelio Vazquez-Reina
@ user815423426 - Robi to po prostu find ... -exec ...lepszy sposób lub jeśli chcesz używać też jest w porządku. Tak, zawsze stawia je na końcu. Właśnie dlatego ta metoda potrzebuje . xargsfind ... | xargs ... mv -t ...-t
slm
5

Opcja -iprzyjmuje opcjonalny argument. Ponieważ wstawiłeś spację po -itej -iopcji , nie było argumentu dla opcji i dlatego następna -0nie była opcją, xargsale drugim z 6 operandów {} -0 mv -t /some/path {}.

Tylko z tą opcją -ixargs oczekiwał listy nazw plików oddzielonych znakiem nowej linii. Ponieważ prawdopodobnie nie było nowego wiersza na wejściu, xargs otrzymał coś, co wyglądało jak ogromna nazwa pliku (z osadzonymi bajtami zerowymi, ale xargs tego nie sprawdził). Ten pojedynczy ciąg zawierający całe wyjście findbył dłuższy niż maksymalna długość wiersza poleceń, stąd błąd „linia poleceń za długa”.

Twoje polecenie działałoby -i{}zamiast -i {}. Alternatywnie, mógłbyś użyć -I {}: -Ijest podobny do -i, ale przyjmuje argument obowiązkowy, więc następny argument przekazany do xargsjest używany jako argument -Iopcji. Następnie argument, -0który jest interpretowany jako opcja, i tak dalej.

Jednak nie powinieneś w ogóle używać -I {}. Korzystanie -Ima trzy efekty:

  • -Iwyłącza przetwarzanie ofert, co -0już działa.
  • -Izmienia ciąg do zastąpienia, ale {}jest wartością domyślną.
  • -Ipowoduje, że polecenie jest wykonywane osobno dla każdego rekordu wejściowego, co jest tutaj bezużyteczne, ponieważ twoje polecenie ( mv -t) jest specjalnie przeznaczone do obsługi wielu plików na jedno wywołanie.

Albo upuść -Ii -icałkowicie

find /foo/bar -name '*.mp4' -print0 | xargs -0 mv -t /some/path {}

lub upuść xargs i użyj -exec:

find /foo/bar -name '*.mp4' -exec mv -t /some/path {} +
Gilles „SO- przestań być zły”
źródło
0

Spróbuj użyć bash dla pętli:

for FILE in *.mp4 ; do rm $FILE ; done

lub jeśli chcesz zobaczyć, co się dzieje:

for FILE in *.mp4 ; do echo Removing $FILE ; rm $FILE ; done
C. Shamis
źródło
0

Jeśli widzisz to podczas używania skorupy ryby .
Odnosi się to do tego, jak ryba rozwija łańcuch zastępczy{}

Jeśli używasz ryb, musisz uciec z łańcucha zastępczego \{\}

| xargs -I \{\} echo \{\}

lub użyj innego ciągu zastępczego

| xargs -I ! echo !
nelaaro
źródło