是否可以在函数中添加函数?

Zac*_*Zac 8 shell bash function

这是我的代码:

function update_profile
{
    echo "1. Update Name"
    echo "2. Update Age"
    echo "3. Update Gender"
    echo "Enter option: "
    read option

    case $option in
         1) update_name ;;
         2) update_age ;;
         3) update_gender ;;
    esac

    function update_name
    {
        echo "Enter new name: "
        read name
    }
}
Run Code Online (Sandbox Code Playgroud)

只是想确定是否可以这样做。我确实知道我可以将所有代码都放入案例中,但这会很混乱,所以我想在一个函数中创建一个独立的函数,并在需要执行其命令时调用。

jhe*_*ran 13

是的,这是可能的。

甚至可以将一个函数嵌套在另一个函数中,尽管这不是很有用。

f1 ()
{

  f2 () # nested
  {
    echo "Function \"f2\", inside \"f1\"."
  }

}  

f2  #  Gives an error message.
    #  Even a preceding "declare -f f2" wouldn't help.

echo    

f1  #  Does nothing, since calling "f1" does not automatically call "f2".
f2  #  Now, it's all right to call "f2",
    #+ since its definition has been made visible by calling "f1".

    # Thanks, S.C.
Run Code Online (Sandbox Code Playgroud)

来源:Linux 文档项目


mik*_*erv 5

是的,当您考虑 shell 函数的真正含义时,这一点就会变得更加清楚。

对于 POSIX 兼容的 shell,函数定义是标准化的:

  • 2.9.5功能定义命令

    • 函数是用户定义的名称,用作简单命令来调用具有新位置参数的复合命令。函数是用“函数定义命令”定义的......如下:

    fname() compound-command[io-redirect ...]

    • 该函数被命名为fname... 实现应为函数变量维护单独的名称空间。

    • 参数compound-command表示复合命令,如复合命令中所述。

      • 声明函数时,不得对或中的文本执行字扩展中的任何扩展;每次调用该函数时,所有操作都应正常执行。同样,可选的重定向和任何内部重定向都应在函数本身执行期间执行,而不是在函数定义期间执行。有关交互式和非交互式 shell 上这些操作失败的后果,请参阅Shell 错误的后果。compound-command<<&io-redirect&>${expansions}<<&io-redirect&>variable=assignmentscompound-command

因此,从本质上讲,名为 的 shell 函数fname是一个由至少一个复合命令组成的文字字符串,shell 将从内存中调用该复合命令,并在fname它出现在命令位置的输入中时执行- 这意味着无论cmd将在何处执行做。这个定义为在 POSIX shell 中使用函数提供了很多可能性。以下任一情况均可接受:

fn() {
    command; list;
    fn() { : redefines itself upon first execution; }
}
Run Code Online (Sandbox Code Playgroud)

...和...

fn() {
    helper1() { : defines another function which it can call; }
    helper2() { : and another; }
    helper1 "$@" | helper2 "$@"     #processes args twice over pipe
    command "$@"; list;             #without altering its args
}
Run Code Online (Sandbox Code Playgroud)

但这只是一个小例子。如果您考虑复合命令的含义,您可能会开始发现传统fn() { : cmds; }形式只是函数工作的一种方式。考虑一些不同类型的复合命令:

 { compound; list; of; commands;} <>i/o <i >o
 (subshelled; compound; list; of; commands) <>i/o <i >o
 if ...; then ...; fi <>i/o <i >o
 case ... in (...) ...;; esac <>i/o <i >o
 for ... [in ... ;] do ...; done <>i/o <i >o
 (while|until) ...; do ....; done <>i/o <i >o
Run Code Online (Sandbox Code Playgroud)

除此之外还有其他人。上述任何一项都应该像...

 fname() compound list
Run Code Online (Sandbox Code Playgroud)

...并且其中任何可以在未分配为函数时嵌套的内容仍然可以嵌套,即使定义为命令也是如此。

这是我编写函数的一种方法:

update_prof(){
    cat >&3
    read "${2-option}" <&3
    case "${1-$option}" in
    1) update_prof '' name      ;;
    2) update_prof '' age       ;;
    3) update_prof '' gender    ;;
    *) unset option             ;;
    esac
} <<-PROMPT 3<>/dev/tty
${1-
    1. Update Name
    2. Update Age
    3. Update Gender
}
    Enter ${2:-option}: $(
        printf '\033%s' \[A @
)
PROMPT
Run Code Online (Sandbox Code Playgroud)

关于上述内容的一些注意事项:

  1. inread更新受 IFS 和反斜杠解释的约束。很可能是这样IFS= read -r "$1",但我不确定你希望如何解释这些事情。在此网站上查找其他答案,以获得有关该分数的更多更好的信息。
  2. 此处printf '\033%s...文档中的 假设/dev/tty链接到 VT100 兼容终端,在这种情况下,使用的转义应该阻止此处文档的最终换行符显示在屏幕上。鲁棒地tput会被使用。在那里获取man termcap更多信息。
    • 最好的是 VT100 假设是正确的,您可以不使用任何一个printf,也tput可以直接在此处文档中输入转义字符,例如^V{esc}[A^V{esc}@where是表示组合键的^V一种方式,并且是键盘的键。CONTROL+V{esc}ESC

上面的函数将根据其参数集执行双重任务 - 它将执行两次并仅根据需要重新评估其提示 - 因此它不需要第二个函数 - 因为只要最初调用它,它就可以执行这两项操作首先没有参数。

所以如果我跑...

update_prof; printf %s\\n "$name"
Run Code Online (Sandbox Code Playgroud)

随后的终端活动如下所示:

1. Update Name
2. Update Age
3. Update Gender

Enter option: 1

Enter name: yo mama
yo mama
Run Code Online (Sandbox Code Playgroud)


Gil*_*il' 5

您可以在 shell 需要命令的任何地方定义函数,包括在函数中。请注意,该函数是在 shell 执行其定义时定义的,而不是在 shell 解析文件时定义的。所以如果用户第一次update_profile执行时选择选项1,你的代码将无法工作,因为update_name在语句中调用when时case,函数的定义update_name还没有被执行。一旦该函数update_profile被执行一次,该函数update_name也将被定义。

您需要将 的定义移到update_name使用它的位置之前。

定义update_nameinsideupdate_profile并不是特别有用。这确实意味着update_name在第一次执行之前不会被定义update_profile,但之后它将保持可用。如果您update_name只想在内部可用update_profile,请在其中定义函数并unset -f update_name在从函数返回之前调用。不过,与做简单的事情并全局定义所有函数相比,这样做并不会真正获得任何好处。

  • `unset -f update_name` 不仅比 `update_name () ( : ... ; )` 或不那么奇怪的 `update_name () { :; 更简单。}` 但更重要的是它做了它应该做的事情,即取消定义函数。`update_name () (…)` 定义了一个不执行任何操作的函数,因此运行 `update_name` 将不会执行任何操作,而是通过该名称调用外部命令。 (2认同)