有没有办法避免 bash 中的位置参数?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12128296/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 22:35:54  来源:igfitidea点击:

Is there a way to avoid positional arguments in bash?

bashshellscripting

提问by Bhushan

I have to write a function in bash. The function will take about 7 arguments. I know that I can call a function like this:

我必须在 bash 中编写一个函数。该函数将采用大约 7 个参数。我知道我可以调用这样的函数:

To call a function with parameters:

调用带参数的函数:

function_name $arg1 $arg2

And I can refer my parameters like this inside the function:

我可以在函数中像这样引用我的参数:

function_name () {
   echo "Parameter #1 is "
}

My question is, is there a better way refer to the parameters inside the function? Can I avoid the $1, $2, $3, .... thing and simply use the $arg1, $arg2, ...?

我的问题是,有没有更好的方法来引用函数内部的参数?我可以避免 $1, $2, $3, .... 事情并简单地使用 $arg1, $arg2, ... 吗?

Is there a proper method for this or do I need to re-assign these parameters to some other variables inside the function? E.g.:

是否有适当的方法,或者我是否需要将这些参数重新分配给函数内的其他一些变量?例如:

function_name () {
   $ARG1=
   echo "Parameter #1 is $ARG1"
}

Any example would be much appreciated.

任何例子将不胜感激。

回答by Micha? Górny

The common way of doing that is assigning the arguments to local variables in the function, i.e.:

这样做的常用方法是将参数分配给函数中的局部变量,即:

copy() {
    local from=
    local to=

    # ...
}


Another solution may be getopt-style option parsing.

另一种解决方案可能是getopt风格的选项解析。

copy() {
    local arg from to
    while getopts 'f:t:' arg
    do
        case ${arg} in
            f) from=${OPTARG};;
            t) to=${OPTARG};;
            *) return 1 # illegal option
        esac
    done
}

copy -f /tmp/a -t /tmp/b


Sadly, bash can't handle long options which wouldbe more readable, i.e.:

可悲的是,庆典不能处理长选项,这些选项是更具可读性,即:

copy --from /tmp/a --to /tmp/b

For that, you either need to use the external getoptprogram (which I think has long option support only on GNU systems) or implement the long option parser by hand, i.e.:

为此,您要么需要使用外部getopt程序(我认为它仅在 GNU 系统上支持长选项)或手动实现长选项解析器,即:

copy() {
    local from to

    while [[  ]]; do
        case "" in
            --from)
                from=
                shift
                ;;
            --to)
                to=
                shift
                ;;
            *)
                echo "Unknown parameter: " >&2
                return 1
        esac

        if ! shift; then
            echo 'Missing parameter argument.' >&2
            return 1
        fi
    done
}

copy --from /tmp/a --to /tmp/b

Also see: using getopts in bash shell script to get long and short command line options

另请参阅:在 bash shell 脚本中使用 getopts 获取长短命令行选项



You can also be lazy, and just pass the 'variables' as arguments to the function, i.e.:

您也可以懒惰,只需将“变量”作为参数传递给函数,即:

copy() {
    local "${@}"

    # ...
}

copy from=/tmp/a to=/tmp/b

and you'll have ${from}and ${to}in the function as local variables.

并且您将在函数中将${from}${to}作为局部变量。

Just note that the same issue as below applies — if a particular variable is not passed, it will be inherited from parent environment. You may want to add a 'safety line' like:

请注意,下面同样适用——如果未传递特定变量,它将从父环境继承。您可能想要添加“安全线”,例如:

copy() {
    local from to    # reset first
    local "${@}"

    # ...
}

to ensure that ${from}and ${to}will be unset when not passed.

以确保${from}${to}将在未通过时取消设置。



And if something very badis of your interest, you could also assign the arguments as global variables when invoking the function, i.e.:

如果您对某些非常糟糕的事情感兴趣,您还可以在调用函数时将参数分配为全局变量,即:

from=/tmp/a to=/tmp/b copy

Then you could just use ${from}and ${to}within the copy()function. Just note that you should then alwayspass all parameters. Otherwise, a random variable may leak into the function.

然后你就可以在函数内使用${from}${to}copy()。请注意,您应该始终传递所有参数。否则,随机变量可能会泄漏到函数中。

from= to=/tmp/b copy   # safe
to=/tmp/b copy         # unsafe: ${from} may be declared elsewhere


If you have bash 4.1 (I think), you can also try using associative arrays. It will allow you to pass named arguments but it willbe ugly. Something like:

如果你有 bash 4.1(我认为),你也可以尝试使用关联数组。它将允许您传递命名参数,但它很丑陋。就像是:

args=( [from]=/tmp/a [to]=/tmp/b )
copy args

And then in copy(), you'd need to grab the array.

然后在 中copy(),您需要获取数组

回答by William Pursell

You can always pass things through the environment:

你总是可以通过环境传递东西:

#!/bin/sh
foo() {
  echo arg1 = $arg1
  echo arg2 = $arg2
}

arg1=banana arg2=apple foo

回答by thkala

Shell functions have full access to any variable available in their calling scope, exceptfor those variable names that are used as local variables inside the function itself. In addition, any non-local variable set within a function is available on the outside after the function has been called. Consider the following example:

Shell 函数可以完全访问其调用范围内可用的任何变量,在函数本身内部用作局部变量的那些变量名称除外。此外,函数内的任何非局部变量集在函数被调用后在外部可用。考虑以下示例:

A=aaa
B=bbb

echo "A=$A B=$B C=$C"

example() {
    echo "example(): A=$A B=$B C=$C"

    A=AAA
    local B=BBB
    C=CCC

    echo "example(): A=$A B=$B C=$C"
}

example

echo "A=$A B=$B C=$C"

This snippet has the following output:

此代码段具有以下输出:

A=aaa B=bbb C=
example(): A=aaa B=bbb C=
example(): A=AAA B=BBB C=CCC
A=AAA B=bbb C=CCC

The obvious disadvantage of this approach is that functions are not self-contained any more and that setting a variable outside a function may have unintended side-effects. It would also make things harder if you wanted to pass data to a function without assigning it to a variable first, since this function is not using positional parameters any more.

这种方法的明显缺点是函数不再是自包含的,并且在函数外部设置变量可能会产生意想不到的副作用。如果您想将数据传递给函数而不先将其分配给变量,这也会使事情变得更加困难,因为该函数不再使用位置参数。

The most common way to handle this is to use local variablesfor arguments and any temporary variable within a function:

最常见的处理方法是使用局部变量作为参数和函数内的任何临时变量:

example() {
   local A="" B="" C="" TMP="/tmp"

   ...
}

This avoids polluting the shell namespace with function-local variables.

这避免了函数局部变量污染 shell 命名空间。

回答by Hashbrown

All you have to do is name variables on the way in to the function call.

您所要做的就是在进入函数调用的过程中命名变量。

function test() {
    echo $a
}

a='hello world' test
#prove variable didnt leak
echo $a .

enter image description here

在此处输入图片说明

This isn't just a feature of functions, you could have that function in it's own script and call a='hello world' test.shand it would work just the same

这不仅仅是函数的一个特性,你可以在它自己的脚本中使用该函数并调用a='hello world' test.sh它,它的工作原理是一样的



As an extra little bit of fun, you can combine this method with positional arguments (say you were making a script and some users mightn't know the variable names).
Heck, why not let it have defaults for those arguments too? Well sure, easy peasy!

作为额外的一点乐趣,您可以将此方法与位置参数结合使用(假设您正在制作一个脚本,而某些用户可能不知道变量名称)。
哎呀,为什么不让它也为这些参数设置默认值呢?好吧,很简单!

function test2() {
    [[ -n "" ]] && local a=""; [[ -z "$a" ]] && local a='hi'
    [[ -n "" ]] && local b=""; [[ -z "$b" ]] && local b='bye'
    echo $a $b
}

#see the defaults
test2

#use positional as usual
test2 '' there
#use named parameter
a=well test2
#mix it up
b=one test2 nice

#prove variables didnt leak
echo $a $b .

enter image description here

在此处输入图片说明

Note that if testwas its own script, you don't need to use the localkeyword.

请注意,如果test是它自己的脚本,则不需要使用local关键字。

回答by niieani

I think I have a solution for you. With a few tricks you can actually pass named parameters to functions, along with arrays.

我想我有一个解决方案。通过一些技巧,您实际上可以将命名参数与数组一起传递给函数。

The method I developed allows you to access parameters passed to a function like this:

我开发的方法允许您访问传递给函数的参数,如下所示:

testPassingParams() {

    @var hello
    l=4 @array anArrayWithFourElements
    l=2 @array anotherArrayWithTwo
    @var anotherSingle
    @reference table   # references only work in bash >=4.3
    @params anArrayOfVariedSize

    test "$hello" = "" && echo correct
    #
    test "${anArrayWithFourElements[0]}" = "" && echo correct
    test "${anArrayWithFourElements[1]}" = "" && echo correct
    test "${anArrayWithFourElements[2]}" = "" && echo correct
    # etc...
    #
    test "${anotherArrayWithTwo[0]}" = "" && echo correct
    test "${anotherArrayWithTwo[1]}" = "" && echo correct
    #
    test "$anotherSingle" = "" && echo correct
    #
    test "${table[test]}" = "works"
    table[inside]="adding a new value"
    #
    # I'm using * just in this example:
    test "${anArrayOfVariedSize[*]}" = "${*:10}" && echo correct
}

fourElements=( a1 a2 "a3 with spaces" a4 )
twoElements=( b1 b2 )
declare -A assocArray
assocArray[test]="works"

testPassingParams "first" "${fourElements[@]}" "${twoElements[@]}" "single with spaces" assocArray "and more... " "even more..."

test "${assocArray[inside]}" = "adding a new value"

In other words, not only you can call your parameters by their names (which makes up for a more readable core), you can actually pass arrays (and references to variables - this feature works only in bash 4.3 though)! Plus, the mapped variables are all in the local scope, just as $1 (and others).

换句话说,您不仅可以通过名称调用参数(这构成了更具可读性的核心),您实际上还可以传递数组(以及对变量的引用——尽管此功能仅适用于 bash 4.3)!另外,映射的变量都在本地范围内,就像 $1(和其他)一样。

The code that makes this work is pretty light and works both in bash 3 and bash 4 (these are the only versions I've tested it with). If you're interested in more tricks like this that make developing with bash much nicer and easier, you can take a look at my Bash Infinity Framework, the code below was developed for that purpose.

使这项工作的代码非常简单,并且可以在 bash 3 和 bash 4 中运行(这些是我测试过的唯一版本)。如果您对更多这样的技巧感兴趣,这些技巧使使用 bash 开发变得更好更容易,您可以查看我的Bash Infinity Framework,下面的代码就是为此目的开发的。

Function.AssignParamLocally() {
    local commandWithArgs=(  )
    local command="${commandWithArgs[0]}"

    shift

    if [[ "$command" == "trap" || "$command" == "l="* || "$command" == "_type="* ]]
    then
        paramNo+=-1
        return 0
    fi

    if [[ "$command" != "local" ]]
    then
        assignNormalCodeStarted=true
    fi

    local varDeclaration="${commandWithArgs[1]}"
    if [[ $varDeclaration == '-n' ]]
    then
        varDeclaration="${commandWithArgs[2]}"
    fi
    local varName="${varDeclaration%%=*}"

    # var value is only important if making an object later on from it
    local varValue="${varDeclaration#*=}"

    if [[ ! -z $assignVarType ]]
    then
        local previousParamNo=$(expr $paramNo - 1)

        if [[ "$assignVarType" == "array" ]]
        then
            # passing array:
            execute="$assignVarName=( \"${@:$previousParamNo:$assignArrLength}\" )"
            eval "$execute"
            paramNo+=$(expr $assignArrLength - 1)

            unset assignArrLength
        elif [[ "$assignVarType" == "params" ]]
        then
            execute="$assignVarName=( \"${@:$previousParamNo}\" )"
            eval "$execute"
        elif [[ "$assignVarType" == "reference" ]]
        then
            execute="$assignVarName=\"$$previousParamNo\""
            eval "$execute"
        elif [[ ! -z "${!previousParamNo}" ]]
        then
            execute="$assignVarName=\"$$previousParamNo\""
            eval "$execute"
        fi
    fi

    assignVarType="$__capture_type"
    assignVarName="$varName"
    assignArrLength="$__capture_arrLength"
}

Function.CaptureParams() {
    __capture_type="$_type"
    __capture_arrLength="$l"
}

alias @trapAssign='Function.CaptureParams; trap "declare -i \"paramNo+=1\"; Function.AssignParamLocally \"$BASH_COMMAND\" \"$@\"; [[ $assignNormalCodeStarted = true ]] && trap - DEBUG && unset assignVarType && unset assignVarName && unset assignNormalCodeStarted && unset paramNo" DEBUG; '
alias @param='@trapAssign local'
alias @reference='_type=reference @trapAssign local -n'
alias @var='_type=var @param'
alias @params='_type=params @param'
alias @array='_type=array @param'

回答by Braden Best

I was personally hoping to see some sort of syntax like

我个人希望看到某种语法,例如

func(a b){
    echo $a
    echo $b
}

But since that's not a thing, and a I see quite a few references to global variables (not without the caveat of scoping and naming conflicts), I'll share my approach.

但由于这不是一回事,而且我看到很多对全局变量的引用(并非没有范围和命名冲突的警告),我将分享我的方法。

Using the copyfunction from Michal's answer:

使用Michal's answer 中copy函数:

copy(){
    cp $from $to
}
from=/tmp/a
to=/tmp/b
copy

This is bad, because fromand toare such broad words that any number of functions could use this. You could quickly end up with a naming conflict or a "leak" on your hands.

这很糟糕,因为fromto是一个非常宽泛的词,任何数量的函数都可以使用它。您可能很快就会遇到命名冲突或手上的“泄漏”。

letter(){
    echo "From: $from"
    echo "To:   $to"
    echo
    echo ""
}

to=Emily
letter "Hello Emily, you're fired for missing two days of work."

# Result:
#   From: /tmp/a
#   To:   Emily

#   Hello Emily, you're fired for missing two days of work.

So my approach is to "namespace" them. I name the variable after the function and delete it after the function is done with it. Of course, I only use it for optional values that have default values. Otherwise, I just use positional args.

所以我的方法是“命名空间”它们。我在函数后命名变量并在函数完成后将其删除。当然,我只将它用于具有默认值的可选值。否则,我只使用位置参数。

copy(){
    if [[ $copy_from ]] && [[ $copy_to ]]; then
        cp $copy_from $copy_to
        unset copy_from copy_to
    fi
}
copy_from=/tmp/a
copy_to=/tmp/b
copy # Copies /tmp/a to /tmp/b
copy # Does nothing, as it ought to
letter "Emily, you're 'not' re-hired for the 'not' bribe ;)"
# From: (no /tmp/a here!)
# To:

# Emily, you're 'not' re-hired for the 'not' bribe ;)

I would make a terrible boss...

我会成为一个可怕的老板...



In practice, my function names are more elaborate than "copy" or "letter".

在实践中,我的函数名称比“copy”或“letter”更详细。

The most recent example to my memory is get_input(), which has gi_no_sortand gi_prompt.

我记忆中最近的例子是get_input(),它有gi_no_sortgi_prompt

  • gi_no_sortis a true/false value that determines whether the completion suggestions are sorted or not. Defaults to true
  • gi_promptis a string that is...well, that's self-explanatory. Defaults to "".
  • gi_no_sort是一个 true/false 值,用于确定是否对完成建议进行了排序。默认为真
  • gi_prompt是一个字符串……嗯,这是不言自明的。默认为“”。

The actual arguments the function takes are the source of the aforementioned 'completion suggestions' for the input prompt, and as said list is taken from $@in the function, the "named args" are optional[1], and there's no obvious way to distinguish between a string meant as a completion and a boolean/prompt-message, or really anything space-separated in bash, for that matter[2]; the above solution ended up saving me a lotof trouble.

函数采用的实际参数是上述输入提示的“完成建议”的来源,并且由于所述列表取自$@函数中,“命名参数”是可选的[1],并且没有明显的区分方法在表示完成的字符串和布尔值/提示消息之间,或者在 bash 中真正以空格分隔的任何内容之间,就此而言[2];上述解决方案最终为我省去了很多麻烦。

notes:

笔记:

  1. So a hard-coded shiftand $1, $2, etc. are out of the question.

  2. E.g. is "0 Enter a command: {1..9} $(ls)"a value of 0, "Enter a command:", and a set of 1 2 3 4 5 6 7 8 9 <directory contents>? Or are "0", "Enter", "a", and "command:"part of that set as well? Bash will assume the latter whether you like it or not.

  1. 所以硬编码的shiftand $1,$2等是不可能的。

  2. 例如,是"0 Enter a command: {1..9} $(ls)"0"Enter a command:"和一组1 2 3 4 5 6 7 8 9 <directory contents>?或者"0""Enter", "a", 和"command:"也是该集合的一部分?无论您喜欢与否,Bash 都会假设后者。

回答by mkrzewic

this is an older topic, but still i'd like to share the function below (requires bash 4). It parses named arguments and sets the variables in the scripts environment. Just make sure you have sane default values for all parameters you need. The export statement at the end could also just be an eval. It's great in combination with shift to extend existing scripts which already take a few positional parameters and you dont want to change the syntax, but still add some flexibility.

这是一个较旧的主题,但我仍然想分享下面的功能(需要 bash 4)。它解析命名参数并在脚本环境中设置变量。只需确保您需要的所有参数都有合理的默认值。最后的导出语句也可以只是一个 eval。与 shift 结合使用扩展现有脚本非常有用,这些脚本已经采用了一些位置参数并且您不想更改语法,但仍然增加了一些灵活性。

parseOptions()
{
  args=("$@")
  for opt in "${args[@]}"; do
    if [[ ! "${opt}" =~ .*=.* ]]; then
      echo "badly formatted option \"${opt}\" should be: option=value, stopping..."
      return 1
    fi
    local var="${opt%%=*}"
    local value="${opt#*=}"
    export ${var}="${value}"
  done
  return 0
}

回答by Frank Thomas

Arguments get sent to functions as an tuple of individual items, so they have no names as such, just positions. this allows some interesting possibilities like below, but it does mean that you are stuck with $1. $2, etc. as to whether to map them to better names, the question comes down to how big the function is, and how much clearer it will make reading the code. if its complex, then mapping meaningful names ($BatchID, $FirstName, $SourceFilePath) is a good idea. for simple stuff though, it probably isn't necessary. I certianly wouldn't bother if you are using names like $arg1.

参数作为单个项目的元组发送到函数,因此它们没有名称,只有位置。这允许一些有趣的可能性,如下所示,但这确实意味着你被 1 美元困住了。$2 等,关于是否将它们映射到更好的名称,问题归结为函数有多大,以及它会使阅读代码更清晰。如果它很复杂,那么映射有意义的名称($BatchID、$FirstName、$SourceFilePath)是一个好主意。不过对于简单的东西,它可能没有必要。如果你使用像 $arg1 这样的名字,我当然不会打扰。

now, if you just want to echo back the parameters, you can iterate over them:

现在,如果您只想回显参数,您可以遍历它们:

for $arg in "$@"
do
  echo "$arg"
done

just a fun fact; unless you are processing a list, you are probably interested in somthing more useful

只是一个有趣的事实;除非您正在处理列表,否则您可能对更有用的东西感兴趣