bash 函数如何返回多个值?

bash 函数如何返回多个值?

退货的最佳做法是什么许多来自 bash 函数的值?

示例1:

函数脚本:

function mysqlquery {
    local dbserver='localhost'
    local dbuser='user'
    local dbpass='pass'
    local db='mydb'
    mysql -h "$dbserver" -u "$dbuser" -p "$dbpass" --skip-column-names --raw -e "$*" "$db"
    if [ $? -ne 0 ]; then
        return 1
    fi
}

源脚本:

for XY in $(mysqlquery "select XY from ABC where DEF = 123" 2>/dev/null);do
    dosomethingwith $XY
done
if mysqlquery "select XY from ABC where DEF = 123" 2>/dev/null; then
    echo true
fi

示例2:

函数脚本:

function mysqlquery {
    local dbserver='localhost'
    local dbuser='user'
    local dbpass='pass'
    local db='mydb'
    result=$(mysql -h "$dbserver" -u "$dbuser" -p "$dbpass" -e "$*" "$db" 2>/dev/null)
    if [ $? -ne 0 -o -z "$result" ]; then
        return 1
    fi
}

源脚本:

result=$(mysqlquery "select XY from ABC where DEF = 123" 2>/dev/null)
for XY in $result;do
    dosomethingwith $XY
done
if mysqlquery "select XY from ABC where DEF = 123" 2>/dev/null; then
    echo true
fi

或者是否有更多方法来返回多条信息(比单个 int 值多得多)?

答案1

是的,bash只能return返回数字,而且只能返回 0 到 255 之间的整数。

对于可以返回任何内容(事物列表)的 shell,您可以查看es

$ es -c "fn f {return (a 'b c' d \$*)}; printf '%s\n' <={f x y}"
a
b c
d
x
y

现在,在类似 Korn 的 shell 中bash,您始终可以在预先商定的变量中返回数据。该变量可以是 shell 支持的任何类型。

对于bash,它可以是标量稀疏数组(键仅限于正整数的关联数组)或具有非空键的关联数组(键和值都不能包含 NUL 字符)。

另请参阅zsh没有这些限制的普通数组和关联数组。

与上述功能等效的功能f es可以通过以下方式完成:

f() {
  reply=(a 'b c' d "$@")
}
f
printf '%s\n' "${reply[@]}"

现在,mysql查询通常返回表,即二维数组。据我所知,唯一具有多维数组的 shell 是ksh93bash尽管它不支持变量中的 NUL 字符)。

ksh也支持化合物可以方便地返回带有标题的表的变量。

它还支持通过引用传递变量。

所以,你可以这样做:

function f {
  typeset -n var=$1
  var=(
    (foo bar baz)
    (1 2 3)
  }
}
f reply
printf '%s\n' "${reply[0][1]}" "${reply[1][2]}"

或者:

function f {
  typeset -n var=$1
  var=(
    (firstname=John lastname=Smith)
    (firstname=Alice lastname=Doe)
  )
}

f reply
printf '%s\n' "${reply[0].lastname}"

现在,为了获取输出mysql并将其存储在某些变量中,我们需要解析该输出,该输出是文本,其中表的列由 TAB 字符分隔,行由 NL 分隔,并对值进行某种编码以允许它们同时包含 NL和选项卡。

如果没有--raw,mysql将输出 NL as \n、TAB as \t、反斜杠 as\\和 NUL as \0

ksh93read -C可以读取格式化为变量定义的文本(eval虽然与 using 没有太大不同),所以你可以这样做:

function mysql_to_narray {
  awk -F '\t' -v q="'" '
    function quote(s) {
      gsub(/\\n/, "\n", s)
      gsub(/\\t/, "\t", s)
      gsub(/\\\\/, "\\", s)
      gsub(q, q "\\" q q, s)
      return q s q
    }
    BEGIN{print "("}
    {
      print "("
      for (i = 1; i <= NF; i++)
        print " " quote($i)
      print ")"
    }
    END {print ")"}'
}

function query {
  typeset -n var=$1
  typeset db=$2
  shift 2

  typeset -i n=0
  typeset IFS=' '
  typeset credentials=/path/to/file.my # not password on the command line!
  set -o pipefail

  mysql --defaults-extra-file="$credentials" --batch \
        --skip-column-names -e "$*" "$db" |
    mysql_to_narray |
    read -C var
}

用作

query myvar mydb 'select * from mytable' || exit
printf '%s\n' "${myvar[0][0]}"...

或者对于复合变量:

function mysql_to_array_of_compounds {
  awk -F '\t' -v q="'" '
    function quote(s) {
      gsub(/\\n/, "\n", s)
      gsub(/\\t/, "\t", s)
      gsub(/\\\\/, "\\", s)
      gsub(q, q "\\" q q, s)
      return q s q
    }
    BEGIN{print "("}
    NR == 1 {
      for (i = 1; i<= NF; i++) header[i] = $i
      next
    }
    {
      print "("
      for (i = 1; i <= NF; i++)
        print " " header[i] "=" quote($i)
      print ")"
    }
    END {print ")"}'
}

function query {
  typeset -n var=$1
  typeset db=$2
  shift 2

  typeset -i n=0
  typeset IFS=' '
  typeset credentials=/path/to/file.my # not password on the command line!
  set -o pipefail

  mysql --defaults-extra-file="$credentials" --batch \
        -e "$*" "$db" |
    mysql_to_array_of_compounds |
    read -C var
}

用作:

query myvar mydb 'select "First Name" as firstname, 
                         "Last Name" as lastname from mytable' || exit

printf '%s\n' "${myvar[0].firstname"

请注意,标头名称(firstname上面lastname的 )必须是有效的 shell 标识符。

bashor zshor中yash (尽管在 zsh 和 yash 中数组索引从 1 开始并且只能zsh存储 NUL 字符),您始终可以通过awk生成代码来定义它们,从而每列返回一个数组:

query() {
  typeset db="$1"
  shift

  typeset IFS=' '
  typeset credentials=/path/to/file.my # not password on the command line!
  set -o pipefail

  typeset output
  output=$(
    mysql --defaults-extra-file="$credentials" --batch \
          -e "$*" "$db" |
      awk -F '\t' -v q="'" '
        function quote(s) {
          gsub(/\\n/, "\n", s)
          gsub(/\\t/, "\t", s)
          gsub(/\\\\/, "\\", s)
          gsub(q, q "\\" q q, s)
          return q s q
        }
        NR == 1 {
          for (n = 1; n<= NF; n++) column[n] = $n "=("
          next
        }
        {
          for (i = 1; i < n; i++)
            column[i] = column[i] " " quote($i)
        }
        END {
          for (i = 1; i < n; i++)
            print column[i] ") "
        }'
  ) || return
  eval "$output"
}

用作:

query mydb 'select "First Name" as firstname, 
                         "Last Name" as lastname from mytable' || exit

printf '%s\n' "${firstname[1]}"

在 之前添加set -o localoptionswithzsh或with bash4.4+以便将该选项设置为本地函数,就像方法一样。local -set -o pipefailksh93

请注意,在上述所有内容中,我们不会将\0s 转换回真正的 NUL,因为bashorksh93会被它们阻塞。如果您希望zsh能够使用 BLOB,则可能需要这样做,但请注意,这gsub(/\\0/, "\0", s)并不适用于所有awk实现。

无论如何,在这里,我会使用比 shell 更高级的语言(如 perl 或 python)来完成这种事情。

答案2

好吧,这取决于您想要/需要哪种输出格式。最简单的方法可能是只打印函数的输出,这样函数的行为就像任何其他命令一样。另一种方法是从函数内部设置一些变量(可能是关联数组)。这样做的好处是不同的项目可以干净地分开,但您可能需要对一些变量进行硬编码。

第一个示例中的函数实现了前者:无论 mysql 客户端从该函数打印什么内容,都会转到该函数的标准输出。鉴于数据已经以字节流的形式出现,保持原样就可以了。

但在这里,问题变成了如何处理输出。for x in $(somecmd) ...不好,因为 的输出somecmd被分割为单词并针对文件名全局进行处理。通常使用它会更好while read ...,请参阅如何逐行(和/或逐字段)读取文件(数据流、变量)?

mysql要逐行读取输出,您可以这样做

mysql -h "$dbserver" etc. etc. | while read -r line ; do
    dosomethingwith "$line"
done

或者用一个函数

mysqlquery() {
    ...
    mysql -h "$dbserver" etc. etc. 2>/dev/null
}
mysqlquery | while read -r line ; do ...

请注意,您不需要if [ $? -ne 0 ]; then return 1:函数的返回值与最后一个命令的返回值相同。如果您使用该函数为管道提供数据,那么查看返回值并不容易。

相关内容