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. bashPowł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 ( nullglobjest bashspecyficzny)
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 continuewznawiamy z powrotem do forpę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ż bashwewnętrznie używa Ccią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 printfopcji powłoki do rozdzielenia plików tym bajtem NULL za pomocą -dopcji readpolecenia, możemy to zrobić poniżej
( shopt -s nullglob; printf '%s\0' ./* ) | while read -rd '' file; do
cmdToRun [option] -- "$file"
done
nullglobI printfsą owinięte wokół (..)co oznacza, że są w zasadzie prowadzone w sub-shell (powłoka dziecko), ponieważ aby uniknąć nullglobmożliwości zastanowienia się na powłoce macierzystej, raz wyjść sterujących. -d ''Opcja readpolecenia jest nie POSIX zgodne, więc potrzebuje bashskorupę, aby to zrobić. Za pomocą findpolecenia można to zrobić jako
while IFS= read -r -d '' file; do
cmdToRun [option] -- "$file"
done < <(find -maxdepth 1 -type f -print0)
W przypadku findimplementacji, 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 findwersja 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