Ta odpowiedź zawiera rozwiązania dla każdego rodzaju plików. Z nowymi liniami lub spacjami.
Istnieją rozwiązania dla ostatniego basha, a także starożytnego basha, a nawet starych powłok posix.
Do testów użyto drzewa wymienionego poniżej w tej odpowiedzi [1] .
Wybierz
select
Praca z tablicą jest łatwa :
$ dir='deep/inside/a/dir'
$ arr=( "$dir"/* )
$ select var in "${arr[@]}"; do echo "$var"; break; done
Lub z parametrami pozycyjnymi:
$ set -- "$dir"/*
$ select var; do echo "$var"; break; done
Tak więc jedynym prawdziwym problemem jest umieszczenie „listy plików” (poprawnie rozdzielonej) w tablicy lub w parametrach pozycyjnych. Czytaj dalej.
grzmotnąć
Nie widzę problemu, który zgłaszasz za pomocą bash. Bash może wyszukiwać w danym katalogu:
$ dir='deep/inside/a/dir'
$ printf '<%s>\n' "$dir"/*
<deep/inside/a/dir/directory>
<deep/inside/a/dir/file>
<deep/inside/a/dir/file name>
<deep/inside/a/dir/file with a
newline>
<deep/inside/a/dir/zz last file>
Lub, jeśli lubisz pętlę:
$ set -- "$dir"/*
$ for f; do printf '<%s>\n' "$f"; done
<deep/inside/a/dir/directory>
<deep/inside/a/dir/file>
<deep/inside/a/dir/file name>
<deep/inside/a/dir/file with a
newline>
<deep/inside/a/dir/zz last file>
Zauważ, że powyższa składnia będzie działać poprawnie z dowolną (rozsądną) powłoką (przynajmniej csh).
Jedynym ograniczeniem powyższej składni jest zejście do innych katalogów.
Ale bash może to zrobić:
$ shopt -s globstar
$ set -- "$dir"/**/*
$ for f; do printf '<%s>\n' "$f"; done
<deep/inside/a/dir/directory>
<deep/inside/a/dir/directory/file>
<deep/inside/a/dir/directory/file name>
<deep/inside/a/dir/directory/file with a
newline>
<deep/inside/a/dir/directory/zz last file>
<deep/inside/a/dir/file>
<deep/inside/a/dir/file name>
<deep/inside/a/dir/file with a
newline>
<deep/inside/a/dir/zz last file>
Aby wybrać tylko niektóre pliki (takie jak te, które kończą się plikiem), po prostu zamień *:
$ set -- "$dir"/**/*file
$ printf '<%s>\n' "$@"
<deep/inside/a/dir/directory/file>
<deep/inside/a/dir/directory/zz last file>
<deep/inside/a/dir/file>
<deep/inside/a/dir/zz last file>
krzepki
Kiedy w tytule umieścisz słowo „ bezpieczny dla przestrzeni”, założę , że to, co miałeś na myśli, było „ solidne ”.
Najprostszym sposobem na solidne podejście do spacji (lub znaków nowej linii) jest odrzucenie przetwarzania danych wejściowych zawierających spacje (lub znaki nowej linii). Bardzo prostym sposobem na wykonanie tego w powłoce jest wyjście z błędem, jeśli dowolna nazwa pliku rozwija się spacją. Można to zrobić na kilka sposobów, ale najbardziej kompaktowy (i posiks) (ale ograniczony do jednej zawartości katalogu, w tym nazw suddirectories i unikania plików kropkowych):
$ set -- "$dir"/file* # read the directory
$ a="$(printf '%s' "$@" x)" # make it a long string
$ [ "$a" = "${a%% *}" ] || echo "exit on space" # if $a has an space.
$ nl='
' # define a new line in the usual posix way.
$ [ "$a" = "${a%%"$nl"*}" ] || echo "exit on newline" # if $a has a newline.
Jeśli zastosowane rozwiązanie jest solidne w którymkolwiek z tych elementów, usuń test.
W bash podkatalogi mogą być testowane jednocześnie z ** wyjaśnionym powyżej.
Istnieje kilka sposobów dołączania plików kropek, rozwiązaniem Posix jest:
set -- "$dir"/* "$dir"/.[!.]* "$dir"/..?*
odnaleźć
Jeśli z jakiegoś powodu należy użyć find, zamień separator na NUL (0x00).
bash 4.4+
$ readarray -t -d '' arr < <(find "$dir" -type f -name file\* -print0)
$ printf '<%s>\n' "${arr[@]}"
<deep/inside/a/dir/file name>
<deep/inside/a/dir/file with a
newline>
<deep/inside/a/dir/directory/file name>
<deep/inside/a/dir/directory/file with a
newline>
<deep/inside/a/dir/directory/file>
<deep/inside/a/dir/file>
bash 2.05+
i=1 # lets start on 1 so it works also in zsh.
while IFS='' read -d '' val; do
arr[i++]="$val";
done < <(find "$dir" -type f -name \*file -print0)
printf '<%s>\n' "${arr[@]}"
POSIXLY
Aby stworzyć prawidłowe rozwiązanie POSIX, w którym find nie ma separatora NUL i nie ma -d
(ani -a
) do odczytu, potrzebujemy zupełnie innego podejścia.
Musimy użyć kompleksu -exec
z find z wywołaniem powłoki:
find "$dir" -type f -exec sh -c '
for f do
echo "<$f>"
done
' sh {} +
Lub, jeśli potrzebna jest opcja select (select jest częścią bash, a nie sh):
$ find "$dir" -type f -exec bash -c '
select f; do echo "<$f>"; break; done ' bash {} +
1) deep/inside/a/dir/file name
2) deep/inside/a/dir/zz last file
3) deep/inside/a/dir/file with a
newline
4) deep/inside/a/dir/directory/file name
5) deep/inside/a/dir/directory/zz last file
6) deep/inside/a/dir/directory/file with a
newline
7) deep/inside/a/dir/directory/file
8) deep/inside/a/dir/file
#? 3
<deep/inside/a/dir/file with a
newline>
[1] To drzewo (\ 012 to nowe linie):
$ tree
.
└── deep
└── inside
└── a
└── dir
├── directory
│ ├── file
│ ├── file name
│ └── file with a \012newline
├── file
├── file name
├── otherfile
├── with a\012newline
└── zz last file
Można go zbudować za pomocą tych dwóch poleceń:
$ mkdir -p deep/inside/a/dir/directory/
$ touch deep/inside/a/dir/{,directory/}{file{,\ {name,with\ a$'\n'newline}},zz\ last\ file}