具有数组输入和输出的Bash函数

时间:2017-02-22 01:34:59

标签: arrays bash function shell

如果我在bash shell中定义一个数组:

a=()
a+=("A")
a+=("B")
a+=("C")

我可以按预期与它互动:

echo "${a[0]}"
# Returns "A"

echo "${a[1]}"
# Returns "B"

但是当我通过函数运行相同的数组时,我必须做错事。首先,我将定义我的功能:

function sort_array {
  declare -a array=("${!1}")
  local sorted=()

  sorted+=("1")
  sorted+=("2")
  sorted+=("3")

  echo "${sorted[@]}"
}

现在让我们调用它并检查结果:

b=()
b=$(sort_array a[@])
echo "${b[0]}"

# Returns "1 2 3"
# But I'm expecting b[0] == 1

我做错了什么?我意识到我的示例可以完全删除函数参数,但我的最终目标是编写一个bash sort_array()函数,我可以传递一个数组并获取一个数组。

2 个答案:

答案 0 :(得分:4)

bash没有数组。语句echo "${sorted[@]}"不会“返回”数组值,它只是将数组的每个元素写入标准输出,由单个空格分隔。 (更具体地说,数组扩展产生一个单词序列,每个元素一个,然后作为参数传递给echo。)

bash中进行模拟有点困难。您必须创建一个全局数组参数,这是在bash 4.2之前无法在函数内部执行的操作。在bash 4.3。{/ p>中引入namerefs之前,使用所述数组很困难

sort_array () {
    declare -n input=$1     # Local reference to input array
    declare -ga "$2"        # Create the output array
    declare -n output="$2"  # Local reference to output array

    # As a simple example, just reverse the array instead
    # of sorting it.
    n=${#input[@]}
    for((i=n-1; i>=0; i--)); do
        echo "*** ${input[i]}"
        output+=( "${input[i]}" )
    done
}

现在,您分别传递sort_array两个参数,即输入和输出数组的名称。

$ a=("foo 1" "bar 2" "baz 3")
$ sort_array a b
$ echo "${b[0]}"
baz 3

答案 1 :(得分:2)

正如@chepner所说,bash没有数组值。将数组传递给函数时,您真正要做的是将数组的每个元素作为单独的参数传递给该函数。

所有shell函数都可以return是一个单字节的退出代码值,0-255。他们可以返回任何其他内容的唯一方法是输出,使用echoprintf或其他任何内容;然后,调用者必须以任何常用方式捕获该输出(命令替换,进程替换,重定向到要读取的文件等)。

也就是说,如果您刚刚在调用中添加了一些语法,那么您的原始代码将起作用:

b=($(sort_array "${a[@]}"))

但是,依赖于排序数组的元素是解析为单个单词的字符串。更安全的版本是更改sort_array函数以每行打印一个元素;然后,调用者可以使用mapfile内置函数(别名readarray将这些行读入数组;需要Bash 4.x)。看起来像这样:

function sort_array {
  declare -a array=("$@")
  local sorted=()

  sorted+=("1")
  sorted+=("2")
  sorted+=("3")

  printf '%s\n' "${sorted[@]}"
}
mapfile -t b < <(sort_array "${a[@]}")

这就是从b内的命令输出中读取数组<(...); -t告诉它不要在数组值中包含换行符。

更安全的是使用空字符而不是换行符;最简单的话,如果你有bash 4.4,它为mapfile添加了一个选项来代替换行使用不同的字符:

function sort_array {
  declare -a array=("$@")
  local sorted=()

  sorted+=("1")
  sorted+=("2")
  sorted+=("3")

  printf '%s\0' "${sorted[@]}"
}
mapfile -t -d '\0' b < <(sort_array "${a[@]}")