Этот ответ имеет решения для любого типа файлов. С переводом строки или пробелами.
Есть решения для недавнего bash, а также древнего bash и даже старых posix-оболочек.
Дерево, перечисленное ниже в этом ответе [1] , используется для тестов.
Выбрать
select
Работать с массивом легко :
$ dir='deep/inside/a/dir'
$ arr=( "$dir"/* )
$ select var in "${arr[@]}"; do echo "$var"; break; done
Или с позиционными параметрами:
$ set -- "$dir"/*
$ select var; do echo "$var"; break; done
Таким образом, единственной реальной проблемой является получение «списка файлов» (правильно разделенных) внутри массива или внутри Позиционных Параметров. Продолжай читать.
удар
Я не вижу проблемы, о которой вы сообщаете с bash. Bash может искать внутри заданного каталога:
$ 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>
Или, если вам нравится цикл:
$ 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>
Обратите внимание, что приведенный выше синтаксис будет работать правильно с любой (разумной) оболочкой (по крайней мере, не csh).
Единственный предел, который имеет синтаксис выше, это спуск в другие каталоги.
Но Bash может сделать это:
$ 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>
Чтобы выбрать только некоторые файлы (например, те, которые заканчиваются на файле), просто замените *:
$ 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>
крепкий
Когда вы поместите в заголовок « безопасный для космоса », я буду предполагать, что вы имели в виду « надежный ».
Самый простой способ быть уверенным в пробелах (или символах новой строки) - отказаться от обработки ввода, в котором есть пробелы (или символы новой строки). Очень простой способ сделать это в оболочке - это выйти с ошибкой, если имя какого-либо файла расширяется пробелом. Есть несколько способов сделать это, но самый компактный (и posix) (но ограниченный одним содержимым каталога, включая имена вспомогательных каталогов и избегая точечных файлов):
$ 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.
Если используемое решение является надежным в любом из этих пунктов, удалите тест.
В bash подкаталоги можно было проверить сразу с помощью **, описанного выше.
Есть несколько способов включить точечные файлы, решение Posix:
set -- "$dir"/* "$dir"/.[!.]* "$dir"/..?*
находить
Если по какой-либо причине необходимо использовать find, замените разделитель на NUL (0x00).
Баш 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>
Баш 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
Чтобы сделать правильное решение POSIX, где find не имеет разделителя NUL и нет -d
(или -a
) для чтения, нам нужен совершенно иной подход.
Нам нужно использовать комплекс -exec
от find с вызовом оболочки:
find "$dir" -type f -exec sh -c '
for f do
echo "<$f>"
done
' sh {} +
Или, если вам нужен выбор (select - это часть bash, а не 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] Это дерево (\ 012 - новые строки):
$ tree
.
└── deep
└── inside
└── a
└── dir
├── directory
│ ├── file
│ ├── file name
│ └── file with a \012newline
├── file
├── file name
├── otherfile
├── with a\012newline
└── zz last file
Может быть построен с помощью этих двух команд:
$ mkdir -p deep/inside/a/dir/directory/
$ touch deep/inside/a/dir/{,directory/}{file{,\ {name,with\ a$'\n'newline}},zz\ last\ file}