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. .
IFS=
unikać usuwania białych znaków na początku lub na końcu linii wejściowych. Możesz to łatwo przetestować, porównując dane wyjściowe programuread var <<<' abc '; echo ">$var<"
z danymi wyjściowymi programuIFS= read var <<<' abc '; echo ">$var<"
. W pierwszym przypadku spacje przed i poabc
są usuwane. W tym drugim przypadku tak nie jest. Nazwy plików, które zaczynają się lub kończą białymi znakami, mogą być nietypowe, ale jeśli istnieją, chcemy, aby były poprawnie przetwarzane.<'
wykonano <<(find aaa / -not -newermt "$ last_build_timestamp_v" -type f -print0) '''
można użyć zamiast$'\0'
:n=0; while IFS= read -r -d '' line || [ "$line" ]; do echo "$((++n)):$line"; done < <(printf 'first\nstill first\0second\0third')
BLAH=$(find . -name '*.php')
. Jak omówiono w odpowiedzi, to podejście będzie działać w ograniczonych przypadkach, ale ogólnie nie będzie działać ze wszystkimi nazwami plików i nie tworzy, zgodnie z oczekiwaniami OP, tablicy .Bash 4.4 wprowadził
-d
opcjęreadarray
/mapfile
, więc można to teraz rozwiązać za pomocąreadarray -d '' array < <(find . -name "$input" -print0)
dla metody, która działa z dowolnymi nazwami plików, w tym spacjami, znakami nowej linii i znakami globowania. Wymaga to twojego
find
wsparcia-print0
, jak na przykład GNU find.Z instrukcji (pomijając inne opcje):
I
readarray
to tylko synonimmapfile
.źródło
Jeśli używasz wersji
bash
4 lub nowszej, możesz zastąpić korzystaniefind
zshopt -s globstar nullglob array=( **/*"$input"* )
**
Wzór włączonaglobstar
meczów 0 lub więcej katalogów, dzięki czemu wzór pasuje do dowolnej głębokości w bieżącym katalogu. Bez tejnullglob
opcji wzorzec (po rozwinięciu parametrów) jest traktowany dosłownie, więc bez dopasowań powstałaby tablica z pojedynczym ciągiem zamiast pustej tablicy.Dodaj tę
dotglob
opcję również do pierwszego wiersza, jeśli chcesz przechodzić przez ukryte katalogi (takie jak.ssh
) i dopasowywać również ukryte pliki (takie jak.bashrc
).źródło
nullglob
też…dotglob
jest ustawione (może to być pożądane lub nie, ale warto o tym wspomnieć).możesz spróbować czegoś takiego
, a żeby wydrukować wartości tablic, możesz spróbować czegoś takiego jak echoarray=(`find . -type f | sort -r | head -2`)
"${array[*]}"
źródło
Wydaje się, że poniższe działa zarówno dla Bash, jak i Z Shell w systemie MacOS.
#! /bin/sh IFS=$'\n' paths=($(find . -name "foo")) unset IFS printf "%s\n" "${paths[@]}"
źródło
W bashu
$(<any_shell_cmd>)
pomaga uruchomić polecenie i przechwycić dane wyjściowe. Przekazanie tego do zaIFS
pomocą\n
jako separatora pomaga przekonwertować to na tablicę.IFS='\n' read -r -a txt_files <<< $(find /path/to/dir -name "*.txt")
źródło
find
do tablicy.Możesz zrobić tak:
#!/bin/bash echo "input : " read input echo "searching file with this pattern '${input}' under present directory" array=(`find . -name '*'${input}'*'`) for i in "${array[@]}" do : echo $i done
źródło
Dla mnie to działało dobrze na cygwin:
declare -a names=$(echo "("; find <path> <other options> -printf '"%p" '; echo ")") for nm in "${names[@]}" do echo "$nm" done
Działa to ze spacjami, ale nie z podwójnymi cudzysłowami (") w nazwach katalogów (które i tak są niedozwolone w środowisku Windows).
Uważaj na spację w opcji -printf.
źródło