web-dev-qa-db-ja.com

シェルスクリプトで複数の値を返すためのイディオム

スクリプト内のbash関数から複数の値を返す慣用句はありますか?

http://tldp.org/LDP/abs/html/assortedtips.html は、複数の値をエコーし​​て結果を処理する方法(例35-17など)を説明しますが、戻り値はスペースを含む文字列です。

戻るためのより構造化された方法は、次のようにグローバル変数に割り当てることです

foo () {
    FOO_RV1="bob"
    FOO_RV2="bill"
}

foo
echo "foo returned ${FOO_RV1} and ${FOO_RV2}"

シェルスクリプトで再入が必要な場合、おそらくそれを間違っていると思いますが、それでも、戻り値を保持するためだけにグローバル変数を投げるのは非常に不快に感じます。

もっと良い方法はありますか?私は移植性を好みますが、#!/bin/bashを指定する必要がある場合、それはおそらく実際の制限ではありません。

26
Wang

私がシェルを愛しているのと同じように、任意の構造化データを投げかけたらすぐに、Unix bourne/posix Shellが適切な選択ではない場合がおそらくあります。

フィールド内に出現しない文字がある場合は、それらのいずれかで区切ります。古典的な例は/etc/passwd/etc/groupおよびフィールド区切り文字としてコロンを使用するその他のさまざまなファイル。

文字列内でNUL文字を処理できるシェルを使用している場合は、NULに参加して($ IFSなどを介して)NULで分離するとうまく機能します。しかし、bashを含むいくつかの一般的なシェルは、NULを破ります。テストは私の古い.sigです:

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

それでうまくいくとしても、より構造化された言語(Python、Perl、Ruby、Lua、Javascript ...好みの毒を選ぶ)に切り替えるときがきたという警告の兆候に達しました。あなたのコードは維持するのが難しくなる可能性があります。たとえできたとしても、それを維持するのに十分理解している人々の小さなプールがあります。

9
Phil P

値にスペースが含まれないの特殊なケースでは、このreadトリックは簡単な解決策になる可能性があります。

get_vars () {
  #...
  echo "value1" "value2"
}

read var1 var2 < <(get_vars)
echo "var1='$var1', var2='$var2'"

しかし、もちろん、いずれかの値にスペースがあるとすぐに壊れます。 IFSを変更し、関数のechoで特別な区切り記号を使用することもできますが、その場合、結果は他の推奨されるソリューションよりも単純ではありません。

14
mivk

この質問は5年前に投稿されましたが、興味深い回答がいくつかあります。私はbashを学び始めたばかりで、あなたと同じ問題に遭遇しました。私はこのトリックが役立つかもしれないと思います:

#!/bin/sh

foo=""
bar=""

my_func(){
    echo 'foo="a"; bar="b"'
}

eval $(my_func)
echo $foo $bar
# result: a b

このトリックは、子プロセスが親プロセスに値を返送できない場合の問題の解決にも役立ちます。

13
fronthem

さらに別の方法:

function get_Tuple()
{
  echo -e "Value1\nValue2"
}

IFS=$'\n' read -d '' -ra VALUES < <(get_Tuple)
echo "${VALUES[0]}" # Value1
echo "${VALUES[1]}" # Value2
2
NuSkooler

namerefをサポートしないバージョンのBash(Bash 4.3-alphaで導入)では、戻り値が指定された変数に割り当てられるヘルパー関数を定義できます。 evalを使用して同じ種類の変数を割り当てるようなものです。

例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

例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
2
Justin

それ以降のバージョンのBashはnamerefをサポートしています。使用する declare -n var_nameを与えるvar_namenameref属性。 namerefは、C++関数で複数の値を返すために一般的に使用される「参照渡し」機能を関数に提供します。 Bashのmanページによると:

-nオプションをdeclareまたはlocalに使用して、変数にnameref属性を割り当てることができます=namerefを作成するための組み込みコマンド、または別の変数への参照これにより、変数を間接的に操作できます。 nameref変数が参照または割り当てられるときは常に、nameref変数の値で指定された変数に対して実際に操作が実行されます。 namerefは、通常、名前が関数の引数として渡される変数を参照するためにシェル関数内で使用されます。

以下は、インタラクティブなコマンドラインの例です。

例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]

例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'
1
Justin

あなたは連想配列を利用することができますbash 4があります

declare -A ARR
function foo(){
  ...
  ARR["foo_return_value_1"]="VAR1"
  ARR["foo_return_value_2"]="VAR2"
}

それらを文字列として組み合わせることができます。

function foo(){
  ...
  echo "$var1|$var2|$var3"
}

次に、これらの戻り値を使用する必要があるときはいつでも、

ret="$(foo)"
IFS="|"
set -- $ret
echo "var1 one is: $1"
echo "var2 one is: $2"
echo "var3 one is: $3"
1
ghostdog74

私は私がソリューションに行くでしょう ここで提案 が、代わりに配列変数を使用しています。古いbash:は連想配列をサポートしていません。例えば。、

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[@]}")'
}

呼び出し元サイト:

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
}
1
Markarian451

シェルスクリプト関数は、最後に実行されたコマンドの終了ステータスまたはreturnステートメントで明示的に指定されたその関数の終了ステータスのみを返すことができます。

文字列を返す方法の1つは次のとおりです。

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

これにより、新しいシェルの作成のオーバーヘッドが追加されるため、不快感が軽減される可能性があるため、わずかに遅くなります。

0
sud03r