Czy nie ma sposobu na ochronę przestrzeni w rozszerzeniu wstecznym (lub $ (...))?
Nie, nie ma. Dlaczego?
Bash nie ma pojęcia, co należy chronić, a co nie.
W pliku / potoku unix nie ma tablic. To tylko strumień bajtów. Polecenie wewnątrz ``
lub $()
wyprowadza strumień, który bash połyka i traktuje jak pojedynczy ciąg. W tym momencie masz tylko dwie możliwości: umieść go w cudzysłowie, aby zachować go jako jeden ciąg lub umieść go nagiego, aby bash podzielił go zgodnie ze skonfigurowanym zachowaniem.
Więc jeśli chcesz, aby tablica była zdefiniowana, musisz zdefiniować format bajtów, który ma tablicę, a takie narzędzia lubią xargs
i find
robią: jeśli uruchomisz je z -0
argumentem, będą działać zgodnie z formatem tablicy binarnej, który kończy elementy za pomocą bajt zerowy, dodając semantykę do inaczej nieprzezroczystego strumienia bajtów.
Niestety bash
nie można skonfigurować podziału ciągów w bajcie zerowym. Dzięki /unix//a/110108/17980 za pokazanie nam, że zsh
może.
xargs
Chcesz, aby polecenie wykonało się raz, i powiedziałeś, że to xargs -0 -n 10000
rozwiązuje twój problem. Nie zapewnia, że jeśli masz więcej niż 10000 parametrów, twoje polecenie uruchomi się więcej niż raz.
Jeśli chcesz, aby był ściśle uruchamiany raz lub nie powiódł się, musisz podać -x
argument i -n
argument większy niż -s
argument (naprawdę: wystarczająco duży, aby cała wiązka argumentów o zerowej długości plus nazwa polecenia nie pasowały -s
wielkości). ( man xargs , patrz fragment daleko poniżej)
System, na którym aktualnie pracuję, ma ograniczony stos do około 8 milionów, więc oto mój limit:
$ printf '%s\0' -- {1..1302582} | xargs -x0n 2076858 -s 2076858 /bin/true
xargs: argument list too long
$ printf '%s\0' -- {1..1302581} | xargs -x0n 2076858 -s 2076858 /bin/true
(no output)
grzmotnąć
Jeśli nie chcesz włączać zewnętrznego polecenia, pętla podczas odczytu zasilająca tablicę, jak pokazano na /unix//a/110108/17980 , jest jedynym sposobem na podzielenie rzeczy przez bash bajt zerowy.
Pomysł na źródło skryptu, ( . ... "$@" )
aby uniknąć limitu wielkości stosu, jest fajny (próbowałem, działa!), Ale prawdopodobnie nie jest ważny w normalnych sytuacjach.
Użycie specjalnego fd dla potoku procesu jest ważne, jeśli chcesz przeczytać coś innego ze standardowego wejścia, ale w przeciwnym razie nie będziesz go potrzebował.
Zatem najprostszy „rodzimy” sposób na codzienne potrzeby gospodarstwa domowego:
files=()
while IFS= read -rd '' file; do
files+=("$file")
done <(find ... -print0)
myscriptornonscript "${files[@]}"
Jeśli podoba ci się drzewo procesu czyste i ładne, ta metoda pozwala to zrobić exec mynonscript "${files[@]}"
, co usuwa proces bash z pamięci, zastępując go wywoływanym poleceniem. xargs
zawsze pozostanie w pamięci podczas działania wywoływanej komendy, nawet jeśli komenda uruchomi się tylko raz.
To, co przemawia przeciwko natywnej metodzie bash, to:
$ time { printf '%s\0' -- {1..1302581} | xargs -x0n 2076858 -s 2076858 /bin/true; }
real 0m2.014s
user 0m2.008s
sys 0m0.172s
$ time {
args=()
while IFS= read -rd '' arg; do
args+=( "$arg" )
done < <(printf '%s\0' -- $(echo {1..1302581}))
/bin/true "${args[@]}"
}
bash: /bin/true: Argument list too long
real 107m51.876s
user 107m38.532s
sys 0m7.940s
bash nie jest zoptymalizowany do obsługi tablicy.
człowiek xargs :
-n maks. argumenty
Użyj maksymalnie argumentów max-args w wierszu poleceń. W przypadku przekroczenia rozmiaru (patrz opcja -s) zostanie użytych mniej niż argumentów max-args, chyba że podano opcję -x, w którym to przypadku xargs zakończy działanie.
-s maks. znaki
Należy użyć maksymalnie znaków o maksymalnej liczbie znaków w wierszu polecenia, w tym argumentu polecenia i początkowych argumentów oraz kończących wartości zerowych na końcach ciągów argumentów. Największa dozwolona wartość zależy od systemu i jest obliczana jako limit długości argumentu dla exec, pomniejszony o wielkość środowiska, mniej 2048 bajtów nadmiaru. Jeśli ta wartość jest większa niż 128 kB, jako wartość domyślną stosuje się 128 kib; w przeciwnym razie wartością domyślną jest maksimum. 1 kB ma 1024 bajty.
-x
Wyjdź, jeśli rozmiar (patrz opcja -s) zostanie przekroczony.
IFS="
nowa linia"
). Ale czy istnieje potrzeba wykonania skryptu dla wszystkich nazw plików? Jeśli nie, rozważ użycie polecenia find, aby wykonać skrypt dla każdego pliku.