Przyjęte / wysoko głosowane odpowiedzi są świetne, ale brakuje im kilku drobiazgowych szczegółów. Ten post omawia przypadki, w których lepiej radzić sobie, gdy rozszerzenie nazwy ścieżki powłoki (glob) kończy się niepowodzeniem, gdy nazwy plików zawierają osadzone symbole nowego wiersza / myślnika i przeniesienie wyjścia polecenia z pętli for podczas zapisywania wyników do plik.
Podczas uruchamiania rozszerzenia globu powłoki za pomocą *
istnieje możliwość niepowodzenia rozszerzenia, jeśli w katalogu nie ma żadnych plików, a nierozwinięty ciąg globu zostanie przekazany do polecenia do uruchomienia, co może mieć niepożądane skutki. bash
Powłoka zapewnia rozszerzoną opcję powłoki dla tego użyciem nullglob
. Pętla zasadniczo wygląda następująco w katalogu zawierającym pliki
shopt -s nullglob
for file in ./*; do
cmdToRun [option] -- "$file"
done
Pozwala to bezpiecznie wyjść z pętli for, gdy wyrażenie ./*
nie zwraca żadnych plików (jeśli katalog jest pusty)
lub w sposób zgodny z POSIX ( nullglob
jest bash
specyficzny)
for file in ./*; do
[ -f "$file" ] || continue
cmdToRun [option] -- "$file"
done
Pozwala to wejść do pętli, gdy wyrażenie nie powiedzie się raz i warunek [ -f "$file" ]
sprawdzi, czy nierozwinięty ciąg ./*
jest prawidłową nazwą pliku w tym katalogu, co nie byłoby. Tak więc w tym przypadku błąd, przy użyciu continue
wznawiamy z powrotem do for
pętli, która nie będzie działać później.
Zwróć także uwagę na użycie --
tuż przed przekazaniem argumentu nazwy pliku. Jest to konieczne, ponieważ, jak wspomniano wcześniej, nazwy plików powłoki mogą zawierać myślniki w dowolnym miejscu w nazwie pliku. Niektóre polecenia powłoki interpretują to i traktują je jako opcję polecenia, gdy nazwa nie jest poprawnie cytowana, i wykonują polecenie, zastanawiając się, czy flaga jest podana.
W takim przypadku --
sygnalizuje koniec opcji wiersza poleceń, co oznacza, że polecenie nie powinno analizować żadnych ciągów poza tym punktem jako flag poleceń, a jedynie jako nazwy plików.
Podwójne cytowanie nazw plików prawidłowo rozwiązuje przypadki, gdy nazwy zawierają znaki globalne lub białe znaki. Ale nazwy plików * nix mogą również zawierać w nich znaki nowej linii. Dlatego ograniczamy nazwy plików za pomocą jedynego znaku, który nie może być częścią prawidłowej nazwy pliku - null byte ( \0
). Ponieważ bash
wewnętrznie używa C
ciągów stylów, w których do wskazania końca łańcucha używane są bajty zerowe, jest to odpowiedni kandydat na to.
Tak więc używając printf
opcji powłoki do rozdzielenia plików tym bajtem NULL za pomocą -d
opcji read
polecenia, możemy to zrobić poniżej
( shopt -s nullglob; printf '%s\0' ./* ) | while read -rd '' file; do
cmdToRun [option] -- "$file"
done
nullglob
I printf
są owinięte wokół (..)
co oznacza, że są w zasadzie prowadzone w sub-shell (powłoka dziecko), ponieważ aby uniknąć nullglob
możliwości zastanowienia się na powłoce macierzystej, raz wyjść sterujących. -d ''
Opcja read
polecenia jest nie POSIX zgodne, więc potrzebuje bash
skorupę, aby to zrobić. Za pomocą find
polecenia można to zrobić jako
while IFS= read -r -d '' file; do
cmdToRun [option] -- "$file"
done < <(find -maxdepth 1 -type f -print0)
W przypadku find
implementacji, które nie obsługują -print0
(innych niż implementacje GNU i FreeBSD), można to emulować za pomocąprintf
find . -maxdepth 1 -type f -exec printf '%s\0' {} \; | xargs -0 cmdToRun [option] --
Inną ważną poprawką jest przeniesienie zmiany kierunku poza pętlę for, aby zmniejszyć dużą liczbę operacji we / wy pliku. Gdy używana jest w pętli, powłoka musi wykonywać wywołania systemowe dwa razy dla każdej iteracji pętli for, raz dla otwarcia i raz dla zamknięcia deskryptora pliku skojarzonego z plikiem. Stanie się to wąskim gardłem w wydajności podczas wykonywania dużych iteracji. Zalecaną sugestią byłoby przeniesienie go poza pętlę.
Rozszerzając powyższy kod o te poprawki, możesz to zrobić
( shopt -s nullglob; printf '%s\0' ./* ) | while read -rd '' file; do
cmdToRun [option] -- "$file"
done > results.out
który po prostu umieści zawartość polecenia dla każdej iteracji wejścia pliku na standardowe wyjście, a gdy pętla się zakończy, otwórz plik docelowy jeden raz, aby zapisać zawartość standardowego wejścia i zapisać go. Równoważna find
wersja tego samego byłaby
while IFS= read -r -d '' file; do
cmdToRun [option] -- "$file"
done < <(find -maxdepth 1 -type f -print0) > results.out
ls <directory> | xargs cmd [options] {filenames put in here automatically by xargs} [more arguments] > results.out