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ができ、return0から255の整数のみを返すことができます。

何でも(モノのリスト)返すことができるシェルの場合は、次のようになりますes

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

Kornなどのシェルでは、bashいつでも事前に合意された変数でデータを返すことができます。変数は、シェルでサポートされている任意の型にすることができます。

の場合、bashスカラー希少配列(正の整数に制限されたキーを持つ連想配列)またはnull以外のキーを持つ連想配列(キーまたは値はすべてNUL文字を含めることはできません)のいずれかです。

zshこれらの制限のない一般的な配列と関連配列も参照してください。

上記の機能と同等の機能は、以下を介してf es実行できます。

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

クエリは通常mysql、2D配列であるテーブルを返します。私が知る限り、多次元配列を持つ唯一のシェルは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}"

出力を取得してそれをいくつかの変数に格納するには、テーブルの列をmysqlTAB文字で区切って行がNLで区切られたテキストである出力を解析し、値をいくつかのエンコードする必要があります。許可NLとタブの両方が含まれています。

それ以外の場合は、--rawNL mysqlas \n、TAB as \t、バックスラッシュas \\、NUL asが出力されます\0

ksh93read -C変数定義で書式設定されたテキストを読むことも可能なので(使用するevalものとあまり変わらないが)、次のようにすることができます。

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)は有効なシェル識別子でなければなりません。

あるいは、(zshとyashの配列インデックスは1から始まり、NUL文字のみを格納できますが)、列ごとに1つの配列を返すコードを生成することで常に定義bashできzshます。yashzshawk

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]}"

メソッドと同様に、オプションをローカル関数に設定する前に、bash4.4+をset -o localoptions使用または追加してください。zshlocal -set -o pipefailksh93

上記のすべての項目では、orがブロックされている\0ため、sを実際のNULに変換しないことに注意してください。 BLOBを使用したい場合は、これを実行できますが、すべての実装で機能するわけではありません。bashksh93zshgsub(/\\0/, "\0", s)awk

とにかく、ここではこの種の操作を実行するためにシェルよりも高いレベルの言語(PerlやPythonなど)を使用します。

答え2

まあ、必要な/必要な出力形式によって異なります。最も簡単な方法は、おそらく関数の出力を印刷して、関数が他のコマンドのように動作するようにすることです。別の方法は、関数内でいくつかの変数(おそらく連想配列)を設定することです。これの利点は、異なるプロジェクトをきれいに分離することができますが、いくつかの変数をハードコードする必要があるかもしれません。

最初の例の関数は電子を実行します。 mysql クライアントが関数から出力するものは何でも関数の標準出力に移動します。データはすでにバイトストリーム形式なので、そのままにしておくだけです。

しかし、ここで問題は出力をどのように処理するかです。for x in $(somecmd) ...出力はsomecmd単語に分割され、ファイル名の全体として処理されるため、それは良いことではありません。一般的に使用する方が良いですwhile read ...。参考ファイル(データストリーム、変数)を1行ずつ(および/またはフィールドごとに)読み取る方法は?

mysql出力を1行ずつ読み取るには:

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関数の戻り値は、最後のコマンドの戻り値と同じである必要はありません。パイプラインに供給するために関数を使用している場合、戻り値を確認するのは簡単ではありません。

関連情報