getopt
и getopts
поддерживайте любой порядок на данные аргументы - Они просты в использовании, и, кажется, канонические решения парсинга аргумента.
Два различия, которые часто упоминаются:
getopt
поддержки долгие опции как --help
.getopts
встроенная оболочка Bash, а не автономная программа.Упрощенный обширный пример:
# Process parameters
params="$(getopt -o e:hv \
-l exclude:,help,verbose \
--name "$0" -- "$@")"
if [ $? -ne 0 ]
then
usage
fi
eval set -- "$params"
unset params
while true
do
case $1 in
-e|--exclude)
excludes+=("${2-}")
shift 2
;;
-h|--help)
usage
exit
;;
-v|--verbose)
verbose='--verbose'
shift
;;
--)
shift
if [ -z "${1:-}" ]
then
error "Missing targets." "$help_info" $EX_USAGE
fi
if [ -z "${2:-}" ]
then
error "Missing directory." "$help_info" $EX_USAGE
fi
targets=(${@:1:$(($#-1))})
source_dir="${@:$#}"
break
;;
*)
usage
;;
esac
done
Еще одно использование для safe find
:
while IFS= read -r -d '' -u 9
do
[Do something with "$REPLY"]
done 9< <( find . -type f -exec printf '%s\0' {} + )
(Это работает с любыми POSIX find
, но часть оболочки требует bash. С помощью *BSD и GNU find можно использовать -print0
вместо -exec printf '%s\0' {}. +
, будет немного быстрее.)
Это позволяет использовать стандартный вход в цикле, и работает с любым путем.
.Делать это так же просто, как:
find -exec sh -c 'inline script "$0"' {} \;
Или...
find -exec executable_script {} \;
Самый простой (но безопасный) подход - это использование shell-глобуса:
$ for f in *; do printf ":%s:\n" "$f"; done
:a b:
:c
d:
:-e:
:e f:
h:
Для того, чтобы сделать вышеуказанный рекурсив в подкаталогах (в bash), вы можете использовать опцию globstar
; также установите dotglob
для сопоставления файлов, чье имя начинается с .
:
$ shopt -s globstar dotglob
$ for f in **/*; do printf ":%s:\n" "$f"; done
:a b:
:c
d:
:-e:
:e f:
:foo:
:foo/file1:
:foo/file two:
h:
Остерегайтесь, что до bash 4.2, **/
возвращается в символические ссылки на каталоги. Начиная с версии 4.3 bash, **/
возвращается только в каталоги, как find
.
Другое распространенное решение - использовать find -print0
с xargs -0
:
$ touch -- 'a b' $'c\nd' $'e\tf' $'g\rh' '-e'
$ find . -type f -print0 | xargs -0 -I{} printf ":%s:\n" {}
h:/g
:./e f:
:./a b:
:./-e:
:./c
d:
Обратите внимание, что h:/g
на самом деле корректен, так как имя файла содержит \r
.
Немного сложно сделать цикл чтения портативно, но для bash в частности можно попробовать что-то вроде this.
Соответствующая часть:
while IFS= read -d $'\0' -r file ; do
printf 'File found: %s\n' "$file"
done < <(find . -iname 'foo*' -print0)
, которая инструктирует найти
, чтобы распечатать его вывод, разделенный NUL-символами (0x00), и прочитать
, чтобы получить NUL-разделенные строки (-d $'\0'
), не обращаясь к обратным слешам как к экранированию для других символов (-r
) и не делайте никакого разделения слов на строках (IFS=
). Так как 0x00 - это байт, который не может встречаться в именах файлов или путях в Unix, это должно решить все ваши странные проблемы с именами файлов.