How to return an array in bash without using globals?

后端 未结 18 2011
Happy的楠姐
Happy的楠姐 2020-11-29 21:36

I have a function that creates an array and I want to return the array to the caller:

create_array() {
  local my_list=(\"a\", \"b\", \"c\")
  echo \"${my_li         


        
相关标签:
18条回答
  • 2020-11-29 22:06

    This can also be done by simply passing array variable to the function and assign array values to this var then use this var outside of function. For example.

    create_array() {
      local  __resultArgArray=$1
      local my_list=("a" "b" "c")
      eval $__resultArgArray="("${my_list[@]}")"
    }
    
    my_algorithm() {
      create_array result
      echo "Total elements in the array: ${#result[@]}"
      for i in "${result[@]}"
      do
        echo $i
      done
    }
    
    my_algorithm
    
    0 讨论(0)
  • 2020-11-29 22:07

    What's wrong with globals?

    Returning arrays is really not practical. There are lots of pitfalls.

    That said, here's one technique that works if it's OK that the variable have the same name:

    $ f () { local a; a=(abc 'def ghi' jkl); declare -p a; }
    $ g () { local a; eval $(f); declare -p a; }
    $ f; declare -p a; echo; g; declare -p a
    declare -a a='([0]="abc" [1]="def ghi" [2]="jkl")'
    -bash: declare: a: not found
    
    declare -a a='([0]="abc" [1]="def ghi" [2]="jkl")'
    -bash: declare: a: not found
    

    The declare -p commands (except for the one in f() are used to display the state of the array for demonstration purposes. In f() it's used as the mechanism to return the array.

    If you need the array to have a different name, you can do something like this:

    $ g () { local b r; r=$(f); r="declare -a b=${r#*=}"; eval "$r"; declare -p a; declare -p b; }
    $ f; declare -p a; echo; g; declare -p a
    declare -a a='([0]="abc" [1]="def ghi" [2]="jkl")'
    -bash: declare: a: not found
    
    -bash: declare: a: not found
    declare -a b='([0]="abc" [1]="def ghi" [2]="jkl")'
    -bash: declare: a: not found
    
    0 讨论(0)
  • 2020-11-29 22:08

    I needed a similar functionality recently, so the following is a mix of the suggestions made by RashaMatt and Steve Zobell.

    1. echo each array/list element as separate line from within a function
    2. use mapfile to read all array/list elements echoed by a function.

    As far as I can see, strings are kept intact and whitespaces are preserved.

    #!bin/bash
    
    function create-array() {
      local somearray=("aaa" "bbb ccc" "d" "e f g h")
      for elem in "${somearray[@]}"
      do
        echo "${elem}"
      done
    }
    
    mapfile -t resa <<< "$(create-array)"
    
    # quick output check
    declare -p resa
    

    Some more variations…

    #!/bin/bash
    
    function create-array-from-ls() {
      local somearray=("$(ls -1)")
      for elem in "${somearray[@]}"
      do
        echo "${elem}"
      done
    }
    
    function create-array-from-args() {
      local somearray=("$@")
      for elem in "${somearray[@]}"
      do
        echo "${elem}"
      done
    }
    
    
    mapfile -t resb <<< "$(create-array-from-ls)"
    mapfile -t resc <<< "$(create-array-from-args 'xxx' 'yy zz' 't s u' )"
    
    sentenceA="create array from this sentence"
    sentenceB="keep this sentence"
    
    mapfile -t resd <<< "$(create-array-from-args ${sentenceA} )"
    mapfile -t rese <<< "$(create-array-from-args "$sentenceB" )"
    mapfile -t resf <<< "$(create-array-from-args "$sentenceB" "and" "this words" )"
    
    # quick output check
    declare -p resb
    declare -p resc
    declare -p resd
    declare -p rese
    declare -p resf
    
    0 讨论(0)
  • 2020-11-29 22:08

    I recently discovered a quirk in BASH in that a function has direct access to the variables declared in the functions higher in the call stack. I've only just started to contemplate how to exploit this feature (it promises both benefits and dangers), but one obvious application is a solution to the spirit of this problem.

    I would also prefer to get a return value rather than using a global variable when delegating the creation of an array. There are several reasons for my preference, among which are to avoid possibly disturbing an preexisting value and to avoid leaving a value that may be invalid when later accessed. While there are workarounds to these problems, the easiest is have the variable go out of scope when the code is finished with it.

    My solution ensures that the array is available when needed and discarded when the function returns, and leaves undisturbed a global variable with the same name.

    #!/bin/bash
    
    myarr=(global array elements)
    
    get_an_array()
    {
       myarr=( $( date +"%Y %m %d" ) )
    }
    
    request_array()
    {
       declare -a myarr
       get_an_array "myarr"
       echo "New contents of local variable myarr:"
       printf "%s\n" "${myarr[@]}"
    }
    
    echo "Original contents of global variable myarr:"
    printf "%s\n" "${myarr[@]}"
    echo
    
    request_array 
    
    echo
    echo "Confirm the global myarr was not touched:"
    printf "%s\n" "${myarr[@]}"
    

    Here is the output of this code:

    When function request_array calls get_an_array, get_an_array can directly set the myarr variable that is local to request_array. Since myarr is created with declare, it is local to request_array and thus goes out of scope when request_array returns.

    Although this solution does not literally return a value, I suggest that taken as a whole, it satisfies the promises of a true function return value.

    0 讨论(0)
  • 2020-11-29 22:10

    You can also use the declare -p method more easily by taking advantage of declare -a's double-evaluation when the value is a string (no true parens outside the string):

    # return_array_value returns the value of array whose name is passed in.
    #   It turns the array into a declaration statement, then echos the value
    #   part of that statement with parentheses intact.  You can use that
    #   result in a "declare -a" statement to create your own array with the
    #   same value.  Also works for associative arrays with "declare -A".
    return_array_value () {
      declare Array_name=$1  # namespace locals with caps to prevent name collision
      declare Result
    
      Result=$(declare -p $Array_name)  # dehydrate the array into a declaration
      echo "${Result#*=}"               # trim "declare -a ...=" from the front
    }
    
    # now use it.  test for robustness by skipping an index and putting a
    # space in an entry.
    declare -a src=([0]=one [2]="two three")
    declare -a dst="$(return_array_value src)"    # rehydrate with double-eval
    
    declare -p dst
    > declare -a dst=([0]="one" [2]="two three")  # result matches original
    

    Verifying the result, declare -p dst yields declare -a dst=([0]="one" [2]="two three")", demonstrating that this method correctly deals with both sparse arrays as well as entries with an IFS character (space).

    The first thing is to dehydrate the source array by using declare -p to generate a valid bash declaration of it. Because the declaration is a full statement, including "declare" and the variable name, we strip that part from the front with ${Result#*=}, leaving the parentheses with the indices and values inside: ([0]="one" [2]="two three").

    It then rehydrates the array by feeding that value to your own declare statement, one where you choose the array name. It relies on the fact that the right side of the dst array declaration is a string with parentheses that are inside the string, rather than true parentheses in the declare itself, e.g. not declare -a dst=( "true parens outside string" ). This triggers declare to evaluate the string twice, once into a valid statement with parentheses (and quotes in the value preserved), and another for the actual assignment. I.e. it evaluates first to declare -a dst=([0]="one" [2]="two three"), then evaluates that as a statement.

    Note that this double evaluation behavior is specific to the -a and -A options of declare.

    Oh, and this method works with associative arrays as well, just change -a to -A.

    Because this method relies on stdout, it works across subshell boundaries like pipelines, as others have noted.

    I discuss this method in more detail in my blog post

    0 讨论(0)
  • 2020-11-29 22:11

    With Bash version 4.3 and above, you can make use of a nameref so that the caller can pass in the array name and the callee can use a nameref to populate the named array, indirectly.

    #!/usr/bin/env bash
    
    create_array() {
        local -n arr=$1             # use nameref for indirection
        arr=(one "two three" four)
    }
    
    use_array() {
        local my_array
        create_array my_array       # call function to populate the array
        echo "inside use_array"
        declare -p my_array         # test the array
    }
    
    use_array                       # call the main function
    

    Produces the output:

    inside use_array
    declare -a my_array=([0]="one" [1]="two three" [2]="four")
    

    You could make the function update an existing array as well:

    update_array() {
        local -n arr=$1             # use nameref for indirection
        arr+=("two three" four)     # update the array
    }
    
    use_array() {
        local my_array=(one)
        update_array my_array       # call function to update the array
    }
    

    This is a more elegant and efficient approach since we don't need command substitution $() to grab the standard output of the function being called. It also helps if the function were to return more than one output - we can simply use as many namerefs as the number of outputs.


    Here is what the Bash Manual says about nameref:

    A variable can be assigned the nameref attribute using the -n option to the declare or local builtin commands (see Bash Builtins) to create a nameref, or a reference to another variable. This allows variables to be manipulated indirectly. Whenever the nameref variable is referenced, assigned to, unset, or has its attributes modified (other than using or changing the nameref attribute itself), 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. For instance, if a variable name is passed to a shell function as its first argument, running

    declare -n ref=$1 inside the function creates a nameref variable ref whose value is the variable name passed as the first argument. References and assignments to ref, and changes to its attributes, are treated as references, assignments, and attribute modifications to the variable whose name was passed as $1.

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