Zbyt długa lista argumentów podczas kopiowania plików

Odpowiedzi:

36

cp *.prj ../prjshp/jest właściwym poleceniem, ale trafiłeś na rzadki przypadek, w którym dochodzi do ograniczenia rozmiaru. Drugie wypróbowane polecenie nie ma sensu.

Jedną z metod jest uruchamianie cpplików w porcjach. findPolecenia wie, jak to zrobić:

find -maxdepth 1 -name '*.prj' -exec mv -t ../prjshp {} +
  • find przegląda rekursywnie bieżący katalog i znajdujące się pod nim katalogi.
  • -maxdepth 1 oznacza zatrzymanie się na głębokości 1, tzn. nie wskakuj do podkatalogów.
  • -name '*.prj'oznacza działanie tylko na plikach, których nazwa pasuje do określonego wzorca. Zwróć uwagę na cytaty wokół wzoru: zostanie zinterpretowany przez findpolecenie, a nie przez powłokę.
  • -exec … {} +oznacza wykonanie określonej komendy dla wszystkich plików. W razie potrzeby wywołuje polecenie wiele razy, uważając, aby nie przekroczyć limitu wiersza poleceń.
  • mv -t ../prjshpprzenosi określone pliki do ../prjshp. -tOpcja jest tutaj stosowane ze względu na ograniczenia findkomendy: znalezione pliki (symbolizowane przez {}) są przekazywane jako ostatni argument polecenia, nie można dodać odbiorcę po niej.

Inną metodą jest użycie rsync.

rsync -r --include='*.prj' --exclude='*' . ../prjshp
  • rsync -r … . ../prjshpkopiuje bieżący katalog do ../prjshprekurencyjnie.
  • --include='*.prj' --exclude='*'oznacza kopiowanie pasujących plików *.prji wykluczanie wszystkiego innego (w tym podkatalogów, aby .prjpliki w podkatalogach nie zostały znalezione).
Gilles „SO- przestań być zły”
źródło
3
rsync, zdecydowanie najprostsze rozwiązanie tutaj.
ntk4
Aby być nieco podejrzanym, drugie polecenie cp * | grep '\.prj$' ../prjshp/ nie ma żadnego sensu, ale może być poprawne pod względem składniowym, jeśli *rozwija się do listy plików, a ostatni to katalog (aka cp SOURCE1 SOURCE2....DEST). Potok nie ma żadnego sensu, oczywiście, ale pozostaje poprawny pod względem składniowym, jeśli chodzi o powłokę - dobrze zrobi dup()deskryptory plików, po prostu koniec potoku czytnika nie otrzyma żadnych danych, ponieważ cpnie zapisuje żadnych .
Sergiy Kolodyazhnyy
Zarówno find, jak i rsync wygenerowały dla mnie tę samą listę argumentów za długi błąd. Pętla for była najprostszym obejściem.
Meezaan-ud-Din
Rzeczywiście rsync jest sposobem na masowe kopiowanie, chociaż jestem zdumiony, jak daleko zaszliśmy z Linuksem i mamy takie głupie błędy / błędy i tak, uważam to za błąd / błąd.
MitchellK
22

To polecenie kopiuje pliki jeden po drugim i będzie działać, nawet jeśli jest ich zbyt wiele, aby można je *było przekształcić w jedno cppolecenie:

for i in *; do cp "$i" ../prjshp/; done
ccshields
źródło
To działa dla mnie.
1rq3fea324wre
1
Prosty i skuteczny. Miałem podobny problem z usunięciem ~ 1/4 milionów plików JPEG, które wyodrębniłem z filmu dla projektu. Takie podejście zastosowałem.
Elder Geek
5

W obliczu Argument list too longbłędu należy pamiętać o 3 kluczowych kwestiach :

  • Długość argumentów wiersza poleceń jest ograniczona ARG_MAXzmienną, która według definicji POSIX to „... [m] maksymalna długość argumentu dla funkcji exec, w tym danych środowiska” (podkreślenie dodane) ”. To znaczy, gdy powłoka wykonuje polecenie inne niż -buduj-to polecenie, musi wywołać jedno z nich, exec()aby spawnować proces tego polecenia, i to właśnie tam ARG_MAXwchodzi w grę. Dodatkowo, nazwa lub ścieżka do samego polecenia (na przykład /bin/echo) odgrywa rolę.

  • Wbudowane polecenia powłoki są wykonywane przez powłokę, co oznacza, że ​​powłoka nie korzysta z exec()rodziny funkcji i dlatego ARG_MAXzmienna nie ma na nią wpływu .

  • Niektóre polecenia, takie jak xargsi, findsą świadome ARG_MAXzmiennych i wielokrotnie wykonują czynności poniżej tego limitu

Z powyższych punktów i jak pokazano w doskonałej odpowiedzi Kusalanandy na powiązane pytanie, Argument list too longmoże to również nastąpić, gdy środowisko jest duże. Biorąc pod uwagę, że środowisko każdego użytkownika może się różnić, a wielkość argumentu w bajtach jest istotna, trudno jest wymyślić jedną liczbę plików / argumentów.

Jak poradzić sobie z takim błędem?

Najważniejsze jest, aby nie skupiać się na liczbie plików, ale skupić się na tym, czy polecenie, którego zamierzasz użyć, obejmuje exec()rodzinę funkcji, a stycznie - przestrzeń stosu.

Użyj wbudowanych powłok

Jak wspomniano wcześniej, wbudowane powłoki są odporne na ARG_MAXograniczenia, to znaczy takie jak forpętla, whilepętla, wbudowane echoi wbudowane printf- wszystkie te będą działać wystarczająco dobrze.

for i in /path/to/dir/*; do cp "$i" /path/to/other/dir/; done

Na pokrewne pytanie dotyczące usuwania plików istniało takie rozwiązanie:

printf '%s\0' *.jpg | xargs -0 rm --

Zauważ, że używa to wbudowanej powłoki printf. Jeśli dzwonimy do zewnętrznego printf, będzie się to wiązać exec(), a zatem nie powiedzie się z dużą liczbą argumentów:

$ /usr/bin/printf "%s\0" {1..7000000}> /dev/null
bash: /usr/bin/printf: Argument list too long

tablice bash

Zgodnie z odpowiedzią jlliagre, bashnie nakłada ograniczeń na tablice, więc można również budować tablicę nazw plików i używać wycinków na iterację pętli, jak pokazano w odpowiedzi danjprerona :

files=( /path/to/old_dir/*.prj )
for((I=0;I<${#files[*]};I+=1000)); do 
    cp -t /path/to/new_dir/ "${files[@]:I:1000}" 
done

Ogranicza to jednak specyficzność bash i brak POSIX.

Zwiększ przestrzeń stosu

Czasami można zobaczyć ludzi sugerują, zwiększając przestrzeń stosu z ulimit -s <NUM>; w systemie Linux wartość ARG_MAX wynosi 1/4 miejsca na stosie dla każdego programu, co oznacza, że ​​zwiększenie miejsca na stosie proporcjonalnie zwiększa miejsce na argumenty.

# getconf reports value in bytes, ulimit -s in kilobytes
$ getconf ARG_MAX
2097152
$ echo $((  $(getconf ARG_MAX)*4 ))
8388608
$ printf "%dK\n" $(ulimit -s) | numfmt --from=iec --to=none
8388608
# Increasing stack space results in increated ARG_MAX value
$ ulimit -s 16384
$ getconf ARG_MAX
4194304

Zgodnie z odpowiedzią Francka Dernoncourta , która cytuje Linux Journal, można również ponownie skompilować jądro Linuksa z większą wartością dla maksymalnej liczby stron pamięci dla argumentów, jednak jest to więcej pracy niż to konieczne i otwiera potencjał dla exploitów, jak stwierdzono w cytowanym artykule Linux Journal.

Unikaj muszli

Innym sposobem jest użycie pythonlub python3które są domyślnie dostarczane z Ubuntu. Poniższy przykład Python + tutaj-doc jest czymś, czego osobiście użyłem do skopiowania dużego katalogu plików gdzieś w zakresie 40 000 pozycji:

$ python <<EOF
> import shutil
> import os
> for f in os.listdir('.'):
>    if os.path.isfile(f):
>         shutil.copy(f,'./newdir/')
> EOF

Do przechodzenia rekurencyjnego możesz użyć os.walk .

Zobacz też:

Sergiy Kolodyazhnyy
źródło
2

IMHO, optymalnymi narzędziami do radzenia sobie z hordami plików są findi xargs. Zobaczyć man find. Zobaczyć man xargs. find, z jego -print0przełącznikiem, tworzy rozdzieloną NULlistę nazw plików (nazwy plików mogą zawierać dowolny znak execpt NULlub /), który xargsrozumie, używając -0przełącznika. xargsnastępnie buduje najdłuższe dozwolone polecenie (najwięcej nazw plików, bez połowy nazwy pliku na końcu) i wykonuje je. xargspowtarza to, dopóki findnie poda więcej nazw plików. Uruchom, xargs --show-limits </dev/nullaby zobaczyć limity.

Aby rozwiązać problem (i po sprawdzeniu, man cpaby znaleźć --target-directory=):

find . -maxdepth 1 -type f -name '*.prj' -print0 | xargs -0 cp --target-directory=../prjshp/
waltinator
źródło