Aktualizacja 2020 dla użytkowników Linuksa:
Jeśli masz wersję up-to-date bash (4,4-alfa lub lepsza), jak zapewne zrobić, jeśli jesteś na Linuksie, to powinieneś być w użyciu odpowiedź Benjamin W. .
Jeśli korzystasz z Mac OS, który - ostatnio, jak sprawdziłem - nadal używa basha 3.2 lub w inny sposób używasz starszego basha, przejdź do następnej sekcji.
Odpowiedź dla basha 4.3 lub wcześniejszego
Oto jedno rozwiązanie dla uzyskania wyjścia find
do bash
tablicy:
array=()
while IFS= read -r -d $'\0'; do
array+=("$REPLY")
done < <(find . -name "${input}" -print0)
Jest to trudne, ponieważ ogólnie nazwy plików mogą zawierać spacje, nowe wiersze i inne znaki wrogie dla skryptów. Jedynym sposobem bezpiecznego używania find
i oddzielania nazw plików od siebie jest użycie polecenia, -print0
które wypisuje nazwy plików oddzielone znakiem null. Nie stanowiłoby to dużej niedogodności, gdyby funkcje readarray
/ basha mapfile
obsługiwały ciągi znaków oddzielone znakiem null, ale tak nie jest. Bash read
robi i to prowadzi nas do powyższej pętli.
[Ta odpowiedź została pierwotnie napisana w 2014 r. Jeśli masz najnowszą wersję basha, zapoznaj się z aktualizacją poniżej.]
Jak to działa
Pierwsza linia tworzy pustą tablicę: array=()
Za każdym razem, gdy read
wykonywana jest instrukcja, ze standardowego wejścia odczytywana jest nazwa pliku oddzielona znakiem null. -r
Opcja nakazuje read
, aby zostawić znaki BACKSLASH. -d $'\0'
Mówi read
, że wkład będzie zerowa rozdzielono. Ponieważ pomijamy nazwę na read
, powłoka daje wejście do domyślnej nazwy: REPLY
.
array+=("$REPLY")
Oświadczenie dołącza nową nazwę pliku do tablicy array
.
Ostatnia linia łączy przekierowanie i podstawianie poleceń, aby zapewnić wyjście find
na standardowe wejście while
pętli.
Dlaczego warto korzystać z zastępowania procesów?
Gdybyśmy nie używali podstawiania procesów, pętla mogłaby zostać zapisana jako:
array=()
find . -name "${input}" -print0 >tmpfile
while IFS= read -r -d $'\0'; do
array+=("$REPLY")
done <tmpfile
rm -f tmpfile
W powyższym przykładzie dane wyjściowe programu find
są przechowywane w pliku tymczasowym i ten plik jest używany jako standardowe wejście do pętli while. Ideą podstawiania procesów jest uczynienie takich plików tymczasowych niepotrzebnymi. Więc zamiast while
pobierać stdin z pętli tmpfile
, możemy sprawić, by pobierała stdin z <(find . -name ${input} -print0)
.
Zastępowanie procesów jest bardzo przydatne. W wielu miejscach, w których polecenie chce czytać z pliku <(...)
, zamiast nazwy pliku można określić podstawianie procesu . Istnieje analogiczna forma, >(...)
której można użyć zamiast nazwy pliku, w którym polecenie chce zapisać do pliku.
Podobnie jak w przypadku tablic, podstawianie procesów jest funkcją powłoki bash i innych zaawansowanych powłok. Nie jest częścią standardu POSIX.
Alternatywa: lastpipe
W razie potrzeby lastpipe
można użyć zamiast zastępowania procesu (końcówka kapelusza: Cezar ):
set +m
shopt -s lastpipe
array=()
find . -name "${input}" -print0 | while IFS= read -r -d $'\0'; do array+=("$REPLY"); done; declare -p array
shopt -s lastpipe
mówi bashowi, aby uruchomił ostatnie polecenie w potoku w bieżącej powłoce (nie w tle). W ten sposób array
pozostaje po zakończeniu rurociągu. Ponieważ lastpipe
działa tylko wtedy, gdy kontrola zadań jest wyłączona, uruchamiamy set +m
. (W skrypcie, w przeciwieństwie do wiersza poleceń, kontrola zadań jest domyślnie wyłączona).
Dodatkowe uwagi
Następujące polecenie tworzy zmienną powłoki, a nie tablicę powłoki:
array=`find . -name "${input}"`
Jeśli chciałbyś stworzyć tablicę, musiałbyś umieścić parens wokół wyniku find. Można więc naiwnie:
array=(`find . -name "${input}"`)
Problem polega na tym, że powłoka dokonuje podziału na słowa na wynikach programu, find
tak że elementy tablicy nie są gwarantowane, że będą zgodne z oczekiwaniami.
Zaktualizuj 2019
Począwszy od wersji 4.4-alpha, bash obsługuje teraz -d
opcję, dzięki czemu powyższa pętla nie jest już potrzebna. Zamiast tego można użyć:
mapfile -d $'\0' array < <(find . -name "${input}" -print0)
Aby uzyskać więcej informacji na ten temat można znaleźć (i upvote) odpowiedź Benjamin W. .