创建通用函数来提出问题并验证答案是否为空

创建通用函数来提出问题并验证答案是否为空

我正在创建一个 shell 应用程序,它可以解决很多问题,并且我已经使用了好read -p "<my_question>" <myvar>几次。问题是我还想验证答案是否为空。因此,我想创建一个通用函数来询问并验证它是否为空。如果是这样,则递归调用函数本身,直到用户提供某些内容。当我将变量名称“固定”为“userdatabase”时,一切都运行得非常好。遵循函数声明和用法:

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

当然,我不想将“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 的信息。显然我没有以正确的方式在 shell 中使用“动态变量”。

那么,如何创建一个函数来接收问题语句和一个将用命令中的变量填充的变量名read -p

答案1

从最简单的情况开始。做一个函数:

f() { read -p "Type smthng: " $1 ; }

调用函数,分配变量$p保存其输入,然后显示输入 函数退出:

f p ; echo $p

它提示我输入“woof”,并echo $p输出相同的内容:

Type smthng: woof
woof

检查变量的一种方法是将函数包装在另一个函数中:

g() { unset $1 ; until f "$1" && eval [ \"\$"$1"\" ] ; do echo wrong ; done ; }

棘手的部分是OP想要将变量名称分配为函数参数,为此我使用“邪恶的”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.

答案2

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"

答案3

我想分享一些我最近发现的东西。我尝试在答案验证中添加进一步的步骤:还验证路径是否存在,但我无法为此调整 Rakesh Sharma 解决方案。最后,我找到了我正在寻找的东西,那就是一种处理“动态变量”的方法,而真正的方法是使用 ${!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

答案4

因此,我想创建一个通用函数来询问并验证它是否为空。

这可能是个好主意。

如果是这样,则递归调用函数本身,直到用户提供某些内容。

这……可不是什么好主意。只需使用一个循环即可。

在函数式编程语言中,使用递归代替简单循环可能很常见,但 shell 不是这样,而且它可能也不是很智能,所以我发现它不太可能优化尾部调用。相反,如果递归深入,您将获得更多的内存使用。这里可能不是问题,因为您可能不会得到那么多迭代,但一般来说,最好仅在需要时才使用递归,例如在遍历树状数据结构时。

另外,您可以在主程序中进行赋值,而不是将变量名称作为参数传递给函数:

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也是提示的作用。这也适用于,例如dash,它似乎不支持${!x}间接引用。

相关内容