定义在 bash 中接受参数的子命令

Defining subcommands that take arguments in bash

提问人:DiogoSaraiva 提问时间:5/16/2016 最后编辑:Charles DuffyDiogoSaraiva 更新时间:5/17/2016 访问量:9366

问:

所以我想制作一个“程序”来促进 yum 命令和其他命令......程序完成后,我想把它放在 /usr/bin 中,名称为“dafs”

我用这个例子进行了测试,文件名为DAFS

#!/bin/bash

$1 $2 $3

function yum {
    function maintenance {
        yum -y update
        yum -y upgrade
        yum clean all
    }

    function download {
        yum -y install --downloadonly $3
    }

}

但是当我运行或它不起作用时,我猜是因为语法不正确。../dafs yum maintenance./dafs yum download http

那么,如何将参数传递给函数或子函数,如上面的例子所示?

Linux Bash

评论

2赞 Charles Duffy 5/16/2016
你从哪里得到“子功能”是存在的东西的想法?
0赞 DiogoSaraiva 5/16/2016
@CharlesDuffy 所以其他函数内部的函数不存在?
0赞 Cyrus 5/16/2016
请看一下: shellcheck.net
0赞 Charles Duffy 5/16/2016
@DiogoSaraiva,您可以从其他函数中定义函数,但它们仍然只是常规函数,而不是“子函数”,并且它们位于主命名空间中(它们的作用域不限于定义它们的函数)。没有内置特殊的子命令魔法来自动调用它们,就像你在这里做的那样;如果你愿意,你需要自己写这个魔法。
2赞 Charles Duffy 5/16/2016
此外,关键字是 bashism - 它使您的函数与更严格兼容 POSIX 的 shell 不兼容,但没有其他价值。function

答:

15赞 Charles Duffy 5/16/2016 #1

定义子命令的最佳做法是使用前缀命名空间和“启动器”函数。例如,它就是这样做的(使用 and 命令 和 )。gitgit-foogit-bargit foogit bar

在这里,我使用双下划线而不是单破折号作为分隔符,因为下划线(与破折号不同)在 POSIX sh 标准中定义为在函数名称中有效。

yum__maintenance() {
  command yum -y update
  command yum -y upgrade
  command yum clean all
}

yum__download() {
  command yum -y install --downloadonly "$@"
}

yum() {
  local cmdname=$1; shift
  if type "yum__$cmdname" >/dev/null 2>&1; then
    "yum__$cmdname" "$@"
  else
    command yum "$cmdname" "$@" # call the **real** yum command
  fi
}

# if the functions above are sourced into an interactive interpreter, the user can
# just call "yum download" or "yum maintenance" with no further code needed.

# if invoked as a script rather than sourced, call function named on argv via the below;
# note that this must be the first operation other than a function definition
# for $_ to successfully distinguish between sourcing and invocation:
[[ $_ != $0 ]] && return

# make sure we actually *did* get passed a valid function name
if declare -f "$1" >/dev/null 2>&1; then
  # invoke that function, passing arguments through
  "$@" # same as "$1" "$2" "$3" ... for full argument list
else
  echo "Function $1 not recognized" >&2
  exit 1
fi

注意事项:

  • "$@"扩展到传递给作用域中当前项的参数的完整列表,保留参数边界并避免 glob 扩展(与未加引号和未引号不同)。$*$@
  • shift将第一个参数 () 从列表前面弹出,使新值 1 比旧列表短。$1"$@"
  • 当不存在子命令时,内置命令会导致调用实际命令,而不是简单地再次递归到函数中。commandyumyum
  • declare -f funcname如果确实传递了函数,则返回 true(并打印该函数的定义)。相反,如果传递了任何类型的可运行命令,则返回 true。因此,using 允许定义为外部脚本或任何其他类型的命令,而不仅仅是一个函数,而 done later 只允许运行函数。typetype "yum__$cmdname"yum__foodeclare -f "$1"

最后要考虑的是,如果您不打算支持源,则可以省略该函数,而是扩展您的启动器以识别子命令本身:yum

if declare -f "${1}__$2" >/dev/null; then
  func="${1}__$2"
  shift; shift    # pop $1 and $2 off the argument list
  "$func" "$@"    # invoke our named function w/ all remaining arguments
elif declare -f "$1" >/dev/null 2>&1; then
  "$@"
else
  echo "Neither function $1 nor subcommand ${1}__$2 recognized" >&2
  exit 1
fi

在这种情况下,始终搜索由前两个参数命名的子命令,后跟仅由第一个参数命名的函数。

评论

2赞 Charles Duffy 5/16/2016
@DiogoSaraiva,我不关注 pastebin.com 链接。试着在没有广告拦截器的情况下查看该网站,你就会明白为什么——任何努力赚钱的人都不应该被信任,如果他们得到适当的报酬,就不会托管恶意软件。考虑 gist.github.comix.io
1赞 Charles Duffy 5/16/2016
@DiogoSaraiva,......因为实际上没有任何东西调用这些函数,如果这是您定义的所有代码。而且,就此而言,在它被调用之前,是唯一被定义的函数;它只在被调用时定义和起作用(而且,同样,它们是名为 or 的函数,而不是“子函数”或;正如我在对这个问题的评论中所说;没有子函数这样的东西)。yumdownloadinstalldownloadinstallyum downloadyum install
1赞 Charles Duffy 5/17/2016
@DiogoSaraiva,......因此,您的函数定义了另外两个函数,但它实际上并没有调用它们。如果你想马虎,在定义之后,你可以放在函数中,但那将是非常非常草率的(例如,充满了安全漏洞;这将使授予某人运行的权限等同于完全不受限制)。我回答中的额外代码是一堆额外的代码,但它有一个有用的目的。yum"$@"yumsudo dafssudo
2赞 Jahid 5/16/2016 #2

你也可以做这样的事情:

#!/bin/sh
yum() {
    if [ "$1" = "maintenance" ]; then
        command yum -y update
        command yum -y upgrade
        command yum clean all
    elif [ "$1" = "download" ]; then
        command yum -y install --downloadonly "$2"
    else
       echo "Invalid arg..."
    fi

}

if [ "$1" = "yum" ];then
    shift
    yum "$@"
fi

现在你可以做或使用它。./dafs yum maintenance./dafs yum download http

评论

1赞 Charles Duffy 5/16/2016
这个成语很古老——自从 Bourne shell(不是 1990 年代的 POSIX sh,而是 1970 年代的 Bourne)以来就不需要了,只要你不使用超过三个参数的测试。x$1
1赞 Charles Duffy 5/16/2016
也就是说,这确实做到了它所说的;我更喜欢更灵活的方法,因为这样可以用非 shell 语言编写子命令,如果愿意的话(例如,就是这样)。git
0赞 Jahid 5/17/2016
@CharlesDuffy : 我用这个成语来提醒我我正在使用 ,并且它还可以防止由于某种原因可能为空的无意的未引号(单个单词)字符串。sh
0赞 Charles Duffy 5/17/2016
(在 >3 参数测试用例中,即当使用 或 组合测试时,分组运算符在任意位置变得有效,并且命令行的总长度不再足以区分是使用一元运算符还是二进制运算符——但对于 ,解析是明确的)。-o-a[ "$foo" = "$bar" ]
0赞 Charles Duffy 5/17/2016
如果你有未加引号的扩展,你就会遇到更大的问题。