idioms for returning multiple values in shell scripting

后端 未结 10 871
佛祖请我去吃肉
佛祖请我去吃肉 2020-12-29 20:56

Are there any idioms for returning multiple values from a bash function within a script?

http://tldp.org/LDP/abs/html/assortedtips.html describes how to echo multipl

相关标签:
10条回答
  • 2020-12-29 21:28

    Shell script functions can only return the exit status of last command executed or the exit status of that function specified explicitly by a return statement.

    To return some string one way may be this:

    function fun()
    {
      echo "a+b"
    }
    
    var=`fun` # Invoke the function in a new child shell and capture the results
    echo $var # use the stored result
    

    This may reduce your discomfort although it adds the overhead of creation of a new shell and hence would be marginally slower.

    0 讨论(0)
  • 2020-12-29 21:29

    In the special case where your values never contain spaces, this read trick can be a simple solution:

    get_vars () {
      #...
      echo "value1" "value2"
    }
    
    read var1 var2 < <(get_vars)
    echo "var1='$var1', var2='$var2'"
    

    But of course, it breaks as soon as there is a space in one of the values. You could modify IFS and use a special separator in your function's echo, but then the result is not really simpler than the other suggested solutions.

    0 讨论(0)
  • 2020-12-29 21:30

    In order version of Bash which doesn't support nameref (introduced in Bash 4.3-alpha) I may define helper function in which the return value is assigned to the given variable. It's sort of like using eval to do the same kind of variable assignment.

    Example 1

    ##  Add two complex numbers and returns it.
    ##  re: real part, im: imaginary part.
    ##
    ##  Helper function named by the 5th positional parameter
    ##  have to have been defined before the function is called.
    complexAdd()
    {
        local re1="$1" im1="$2" re2="$3" im2="$4" fnName="$5" sumRe sumIm
    
        sumRe=$(($re1 + $re2))
        sumIm=$(($im1 + $im2))
    
        ##  Call the function and return 2 values.
        "$fnName" "$sumRe" "$sumIm"
    }
    
    main()
    {
        local fooRe='101' fooIm='37' barRe='55' barIm='123' bazRe bazIm quxRe quxIm
    
        ##  Define the function to receive mutiple return values
        ##  before calling complexAdd().
        retValAssign() { bazRe="$1"; bazIm="$2"; }
        ##  Call comlexAdd() for the first time.
        complexAdd "$fooRe" "$fooIm" "$barRe" "$barIm" 'retValAssign'
    
        ##  Redefine the function to receive mutiple return values.
        retValAssign() { quxRe="$1"; quxIm="$2"; }
        ##  Call comlexAdd() for the second time.
        complexAdd "$barRe" "$barIm" "$bazRe" "$bazIm" 'retValAssign'
    
        echo "foo = $fooRe + $fooIm i"
        echo "bar = $barRe + $barIm i"
        echo "baz = foo + bar = $bazRe + $bazIm i"
        echo "qux = bar + baz = $quxRe + $quxIm i"
    }
    
    main
    

    Example 2

    ##  Add two complex numbers and returns it.
    ##  re: real part, im: imaginary part.
    ##
    ##  Helper functions
    ##      getRetRe(), getRetIm(), setRetRe() and setRetIm()
    ##  have to have been defined before the function is called.
    complexAdd()
    {
        local re1="$1" im1="$2" re2="$3" im2="$4"
    
        setRetRe "$re1"
        setRetRe $(($(getRetRe) + $re2))
    
        setRetIm $(($im1 + $im2))
    }
    
    main()
    {
        local fooRe='101' fooIm='37' barRe='55' barIm='123' bazRe bazIm quxRe quxIm
    
        ##  Define getter and setter functions before calling complexAdd().
        getRetRe() { echo "$bazRe"; }
        getRetIm() { echo "$bazIm"; }
        setRetRe() { bazRe="$1"; }
        setRetIm() { bazIm="$1"; }
        ##  Call comlexAdd() for the first time.
        complexAdd "$fooRe" "$fooIm" "$barRe" "$barIm"
    
        ##  Redefine getter and setter functions.
        getRetRe() { echo "$quxRe"; }
        getRetIm() { echo "$quxIm"; }
        setRetRe() { quxRe="$1"; }
        setRetIm() { quxIm="$1"; }
        ##  Call comlexAdd() for the second time.
        complexAdd "$barRe" "$barIm" "$bazRe" "$bazIm"
    
        echo "foo = $fooRe + $fooIm i"
        echo "bar = $barRe + $barIm i"
        echo "baz = foo + bar = $bazRe + $bazIm i"
        echo "qux = bar + baz = $quxRe + $quxIm i"
    }
    
    main
    
    0 讨论(0)
  • 2020-12-29 21:32

    I would go for the solution I suggested here, but using an array variable instead. Older bash:es don't support associative arrays. E.g.,

    function some_func() # ARRVAR args...
    {
        local _retvar=$1 # I use underscore to avoid clashes with return variable names
        local -a _out
        # ... some processing ... (_out[2]=xxx etc.)
        eval $_retvar='("${_out[@]}")'
    }
    

    Calling site:

    function caller()
    {
        local -a tuple_ret # Do not use leading '_' here.
        # ...
        some_func tuple_ret "arg1"
        printf "  %s\n" "${tuple_ret[@]}" # Print tuple members on separate lines
    }
    
    0 讨论(0)
  • 2020-12-29 21:33

    Later version of Bash supports nameref. Use declare -n var_name to give var_name the nameref attribute. nameref gives your function the ability to "pass by reference" which is commonly used in C++ functions to return multiple values. According to Bash man page:

    A variable can be assigned the nameref attribute using the -n option to the declare or local builtin commands to create a nameref, or a reference to another variable. This allows variables to be manipulated indirectly. Whenever the nameref variable is referenced or assigned to, the operation is actually performed on the variable specified by the nameref variable's value. A nameref is commonly used within shell functions to refer to a variable whose name is passed as an argument to the function.

    The following are some interactive command line examples.

    Example 1:

    $ unset xx yy
    $ xx=16
    $ yy=xx
    $ echo "[$xx] [$yy]"
    [16] [xx]
    $ declare -n yy
    $ echo "[$xx] [$yy]"
    [16] [16]
    $ xx=80
    $ echo "[$xx] [$yy]"
    [80] [80]
    $ yy=2016
    $ echo "[$xx] [$yy]"
    [2016] [2016]
    $ declare +n yy # Use -n to add and +n to remove nameref attribute.
    $ echo "[$xx] [$yy]"
    [2016] [xx]
    

    Example 2:

    $ func()
    > {
    >     local arg1="$1" arg2="$2"
    >     local -n arg3ref="$3" arg4ref="$4"
    > 
    >     echo ''
    >     echo 'Local variables:'
    >     echo "    arg1='$arg1'"
    >     echo "    arg2='$arg2'"
    >     echo "    arg3ref='$arg3ref'"
    >     echo "    arg4ref='$arg4ref'"
    >     echo ''
    > 
    >     arg1='1st value of local assignment'
    >     arg2='2st value of local assignment'
    >     arg3ref='1st return value'
    >     arg4ref='2nd return value'
    > }
    $ 
    $ unset foo bar baz qux
    $ 
    $ foo='value of foo'
    $ bar='value of bar'
    $ baz='value of baz'
    $ qux='value of qux'
    $ 
    $ func foo bar baz qux
    
    Local variables:
        arg1='foo'
        arg2='bar'
        arg3ref='value of baz'
        arg4ref='value of qux'
    
    $ 
    $ {
    >     echo ''
    >     echo '2 values are returned after the function call:'
    >     echo "    foo='$foo'"
    >     echo "    bar='$bar'"
    >     echo "    baz='$baz'"
    >     echo "    qux='$qux'"
    > }
    
    2 values are returned after the function call:
        foo='value of foo'
        bar='value of bar'
        baz='1st return value'
        qux='2nd return value'
    
    0 讨论(0)
  • 2020-12-29 21:35

    Much as I love shell, it's probably the case that as soon as you're throwing arbitrary structured data around, Unix bourne/posix shell is not the right choice.

    If there are characters which do not occur inside fields, then separate with one of those. The classic example is /etc/passwd, /etc/group and various other files which use a colon as a field separator.

    If using a shell which can handle a NUL character inside strings, then joining on the NUL and separating on it (via $IFS or whatever) can work well. But several common shells, including bash, break on NUL. A test would be an old .sig of mine:

    foo=$'a\0b'; [ ${#foo} -eq 3 ] && echo "$0 rocks"
    

    Even if that would work for you, you've just reached one of the warning signs that it's time to switch to a more structured language (Python, Perl, Ruby, Lua, Javascript ... pick your preferred poison). Your code is likely to become hard to maintain; even if you can, there's a smaller pool of people who'll understand it well enough to maintain it.

    0 讨论(0)
提交回复
热议问题