Я создаю приложение-оболочку, которое задает много вопросов, и использую read -p "
несколько раз. Проблема в том, что я также хочу проверить, пусты ли ответы. Итак, мне захотелось создать универсальную функцию, чтобы спрашивать и проверять, пуста она или нет. Если это так, вызовите саму функцию рекурсивно, пока пользователь что-то не предоставит.
Когда я «зафиксировал» имя переменной, скажем, «база данных пользователя», все заработало на удивление хорошо. Следует за объявлением и использованием функции:
ask() {
read -p "$1" $2
if [[ -z $userdatabase ]]; then
echo Empty is not allowed
ask "$1" $2
else
echo $userdatabase
fi
}
ask "Provides the user database: " userdatabase
Конечно, я не хочу указывать «базу данных пользователя» в качестве имени переменной для всех вопросов, которые будет задавать приложение. Итак, я заметил, что мне нужна своего рода «динамическая» переменная. Если позволить мыслить немного более динамично, она становится такой:
ask() {
read -p "$1" $2
if [[ -z $$var ]]; then
echo Empty is not allowed
ask "$1" $2
else
echo $$var
fi
}
ask "Provides the user database: " $var
Но когда я использую эту утилиту, я получаю что-то вроде SOMENUMBERvar. Очевидно, я неправильно использую «динамическую переменную» в оболочке.
Итак, как мне создать функцию, которая получает вопросник и имя переменной, которое будет заполнено переменной из команды read -p
?
Я хотел бы поделиться тем, что обнаружил с опозданием. Я пытался добавить еще один шаг в проверку ответа: проверить, существует ли путь, и не мог приспособить для этого решение Ракеша Шармы. Наконец, я нашел именно то, что искал, - способ работы с "динамической переменной", и реальный способ сделать это - использовать ${!var}. Вот окончательная версия моей функции и ее использование:
ask_validate() {
read -p "$1" $2
if [ -z ${!2} ]; then
echo Empty answer is not allowed.
ask_validate "$1" $2
return
fi
if ! [ -d ${!2} ]; then
echo You need to provides an existing path
ask_validate "$1" $2
return
fi
echo The var name is: $2
echo The var value is: ${!2}
}
ask_validate "Please, provides the host path: " host_path
ask_validate "Please, provides the virtual host configuration path: " virtualhost_path
echo The host path is $host_path
echo The virtual host configuration path is $virtualhost_path
Начните с простейшего возможного случая.Создание функции:
f() { read -p "Type smthng: " $1 ; }
Вызов функции, присвоение переменной $ p для удержания ее ввода, затем отображение ввода после выхода функции:
f p ; echo $p
It запросов, я набираю «woof», и echo $ p
выводит то же самое:
Type smthng: woof
woof
Один из способов проверить переменную - заключить эту функцию в другую:
g() { unset $1 ; until f "$1" && eval [ \"\$"$1"\" ] ; do echo wrong ; done ; }
Сложность заключается в том, что OP хочет чтобы назначить имя переменной как параметр функции, для анализа которого я использую "evil" eval
. Передача имен переменных в качестве параметров функции выполняется редко (или требуется), но здесь показан один из способов сделать это.
Протестируйте:
g p ; echo $p
Появляется запрос, я нажимаю Enter , выводится сообщение об ошибке; затем во втором приглашении я набираю «foo»:
Type smthng:
wrong
Type smthng: foo
foo
Эта часть кода OP не будет работать:
if [[ -z $$var ]]; then
$$
- это переменная bash
, которая возвращает текущий PID:
man bash | grep -A 28 "Special Parameters$" | sed -n '1,3p;28,$p'
Special Parameters
The shell treats several parameters specially. These parameters may only
be referenced; assignment to them is not allowed.
$ Expands to the process ID of the shell. In a () subshell, it expands
to the process ID of the current shell, not the subshell.
ask() {
# $1 ---> message to user
# $2 ---> "VARIABLE NAME" in which read will store response
case ${2-} in
'' | *[!a-zA-Z0-9_]* | [0-9]* )
echo >&2 "Oops you've selected an invalid variable name: \`$2'"
exit 1;;
esac
read -p "$1" "$2"
eval "
case \${$2:++} in
'' )
echo >&2 'Empty response not allowed.'
ask '$1' '$2';;
* )
echo \"\${$2}\";;
esac
"
}
VarName=${1-'humpty_dumpty'}
ask "Provide the user database: " "$VarName"
Итак, я подумал о создании общей функции, чтобы спрашивать и проверять, пуста она или нет.
Это, наверное, хорошая идея.
Если это так, вызовите саму функцию рекурсивно, пока пользователь что-то не предоставит.
Это ... не такая уж хорошая идея. Просто используйте петлю.
В языках функционального программирования может быть обычным делом использовать рекурсию вместо простых циклов, но оболочка не такова, и, вероятно, она тоже не очень умна, поэтому я считаю маловероятным, что она может оптимизировать хвостовые вызовы. Вместо этого вы увеличите использование памяти, если рекурсия станет глубокой. Это может не быть проблемой, поскольку вы, вероятно, не получите так много итераций, но в целом лучше использовать рекурсию только в случае необходимости, например при прохождении древовидной структуры данных.
Кроме того, вместо того, чтобы передавать имя переменной в качестве аргумента функции, вы можете просто выполнить присваивание в основной программе:
ask() {
read -p "$1: " a
while [ -z "$a" ] ; do
echo "Please give a value" >&2
read -p "$1: " a
done
echo "$a"
}
var=$(ask "please enter some value")
echo "you gave $var"
Конечно, теперь мы должны перенаправить напоминание на stderr, потому что stdout назначен на переменная. Это то же самое, что read
делает с подсказкой.
Это также работает, скажем, в тире
, который, похоже, не поддерживает косвенную ссылку $ {! X}
.