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

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

这是我的代码:

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
    }
}

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

答案1

是的,这是可能的。

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

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.

来源:Linux 文档项目

答案2

您可以在 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在从函数返回之前调用。不过,与做简单的事情并全局定义所有函数相比,这样做并不会真正获得任何好处。

答案3

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

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

  • 2.9.5功能 定义 命令

    • A功能是一个用户定义的名称,用作简单的命令打电话给复合命令与新的位置参数。函数是用“函数定义命令“... 如下:

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

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

    • 论据复合命令代表一个复合命令,如中所述复合命令

      • 当。。。的时候功能被宣布,没有一个扩展单词扩展应根据compound-command或者<<&io-redirect&>;每次调用该函数时,所有${expansions}操作都应正常执行。同样,可选的<<&io-redirect&>重定向和任何variable=assignments之内compound-command应在函数本身执行期间执行,而不是在函数定义期间执行。看Shell 错误的后果这些操作在交互式和非交互式 shell 上失败的后果。

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

fn() {
    command; list;
    fn() { : redefines itself upon first execution; }
}

...和...

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
}

但这只是一个小例子。如果你考虑的含义复合命令你可能会开始发现传统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

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

 fname() compound list

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

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

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

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

  1. in更新read受 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"

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

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

Enter option: 1

Enter name: yo mama
yo mama

答案4

我在这里重新添加杰夫·沙勒 (Jeff Schaller) 已删除的答案,以确保有一个答案表明确实存在用例,并且接受的答案暗示说“这不是很有用”本质上是错误的。

这里再次出现(修复以显示我的答案的意思):

它“不是很有用”吗?我认为这实际上很酷。

我的bashrc/中有特定于项目的初始化函数bash_aliases,它将我的环境初始化为给定的项目。

在此之前,我只是全局定义了所有别名和函数(诸如此类的快捷方式cdprojectname) 。modulename_build这样做的问题是,在处理很多项目后,您可以轻松地使用很长的名称来避免名称冲突。

所以我所做的是,我采取了cdprojectname(就像cdsd项目一样sd),这是一个函数,不仅可以转到项目目录,而且可以在涉及项目时初始化我的所有开发环境。

在我看来,你可以创建一个定义 shell 函数的函数,这真是太酷了。当我从内部使用我的别名时,需要测试这是否有效vim

  • 是的,我查了一下,我的技巧不起作用
  • jaja 不管怎样,如果我现在从 vim 中发出它甚至不起作用cdsd- 仍然很酷的功能,因为它是!

至于对答案的评论

“请不要添加“谢谢”作为答案。一旦您拥有足够的声誉,您将能够投票选出您认为有帮助的问题和答案。 – Jeff Schaller”

抱歉,我无法写信给您,但我没有写我所写的内容作为感谢,但我从之前的评论/答案中看到,这些评论/答案并没有真正突出显示此功能的可能用例,有些人甚至说“甚至可以将一个函数嵌套在另一个函数中,虽然这不是很有用。

这不是感恩节,而是想指出,这实际上不是“这不是很有用”,而是“非常非常有用且简洁的功能”,对于其他脚本/shell 语言来说实际上拥有它会很棒!

我发布了我的答案,因为非常不同意这不是很有用,但当前接受的答案暗示了这种方式......请编辑您想要的方式或只是保留它,否则我认为接受的答案中有错误信息/提示。

相关内容