W obliczu Argument list too long
błędu należy pamiętać o 3 kluczowych kwestiach :
Długość argumentów wiersza poleceń jest ograniczona ARG_MAX
zmienną, 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_MAX
wchodzi 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_MAX
zmienna nie ma na nią wpływu .
Niektóre polecenia, takie jak xargs
i, find
są świadome ARG_MAX
zmiennych 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 long
moż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_MAX
ograniczenia, to znaczy takie jak for
pętla, while
pętla, wbudowane echo
i 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, bash
nie 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 python
lub python3
któ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ż: