web-dev-qa-db-ja.com

Bash関数から文字列値を返す方法

Bash関数から文字列を返したいのですが。

私がやりたいことを示すために、Javaで例を書きます。

public String getSomeString() {
  return "tadaa";
}

String variable = getSomeString();

以下の例はbashで動作しますが、これを実行するためのより良い方法はありますか?

function getSomeString {
   echo "tadaa"
}

VARIABLE=$(getSomeString)
414
Tomas F

私が知っているより良い方法はありません。 Bashは、標準出力に書き込まれたステータスコード(整数)と文字列だけを知っています。

264
Philipp

関数に最初の引数として変数を取り、返させたい文字列で変数を修正することができます。

#!/bin/bash
set -x
function pass_back_a_string() {
    eval "$1='foo bar rab oof'"
}

return_var=''
pass_back_a_string return_var
echo $return_var

"foo bar rab oof"を印刷します。

編集 :文字列内の空白が@Luca Borrioneのコメントに対応できるように適切な場所に引用符を追加.

編集 :デモとして、次のプログラムを参照してください。これは汎用的な解決策です。ローカル変数に文字列を受け取ることさえ可能にします。

#!/bin/bash
set -x
function pass_back_a_string() {
    eval "$1='foo bar rab oof'"
}

return_var=''
pass_back_a_string return_var
echo $return_var

function call_a_string_func() {
     local lvar=''
     pass_back_a_string lvar
     echo "lvar='$lvar' locally"
}

call_a_string_func
echo "lvar='$lvar' globally"

これは印刷します:

+ return_var=
+ pass_back_a_string return_var
+ eval 'return_var='\''foo bar rab oof'\'''
++ return_var='foo bar rab oof'
+ echo foo bar rab oof
foo bar rab oof
+ call_a_string_func
+ local lvar=
+ pass_back_a_string lvar
+ eval 'lvar='\''foo bar rab oof'\'''
++ lvar='foo bar rab oof'
+ echo 'lvar='\''foo bar rab oof'\'' locally'
lvar='foo bar rab oof' locally
+ echo 'lvar='\'''\'' globally'
lvar='' globally

編集 :オリジナルの変数の値 が関数内で であることを示しています。

#!/bin/bash
set -x
function pass_back_a_string() {
    eval "echo in pass_back_a_string, original $1 is \$$1"
    eval "$1='foo bar rab oof'"
}

return_var='original return_var'
pass_back_a_string return_var
echo $return_var

function call_a_string_func() {
     local lvar='original lvar'
     pass_back_a_string lvar
     echo "lvar='$lvar' locally"
}

call_a_string_func
echo "lvar='$lvar' globally"

これは出力を与える:

+ return_var='original return_var'
+ pass_back_a_string return_var
+ eval 'echo in pass_back_a_string, original return_var is $return_var'
++ echo in pass_back_a_string, original return_var is original return_var
in pass_back_a_string, original return_var is original return_var
+ eval 'return_var='\''foo bar rab oof'\'''
++ return_var='foo bar rab oof'
+ echo foo bar rab oof
foo bar rab oof
+ call_a_string_func
+ local 'lvar=original lvar'
+ pass_back_a_string lvar
+ eval 'echo in pass_back_a_string, original lvar is $lvar'
++ echo in pass_back_a_string, original lvar is original lvar
in pass_back_a_string, original lvar is original lvar
+ eval 'lvar='\''foo bar rab oof'\'''
++ lvar='foo bar rab oof'
+ echo 'lvar='\''foo bar rab oof'\'' locally'
lvar='foo bar rab oof' locally
+ echo 'lvar='\'''\'' globally'
lvar='' globally
186
bstpierre

上記のすべての答えは、bashのmanページに述べられていることを無視しています。

  • 関数内で宣言されたすべての変数は呼び出し元の環境と共有されます。
  • Localと宣言されたすべての変数は共有されません。

コード例

#!/bin/bash

f()
{
    echo function starts
    local WillNotExists="It still does!"
    DoesNotExists="It still does!"
    echo function ends
}

echo $DoesNotExists #Should print empty line
echo $WillNotExists #Should print empty line
f                   #Call the function
echo $DoesNotExists #Should print It still does!
echo $WillNotExists #Should print empty line

そして出力

$ sh -x ./x.sh
+ echo

+ echo

+ f
+ echo function starts 
function starts
+ local 'WillNotExists=It still does!'
+ DoesNotExists='It still does!'
+ echo function ends 
function ends
+ echo It still 'does!' 
It still does!
+ echo

Pdkshとkshでも、このスクリプトは同じことをします。

94
Vicky Ronnen

Bashは、バージョン4.3、feb 2014(?)以降、 "eval"を超えた参照変数または名前参照(nameref)を明示的にサポートしています。 「 'eval'を忘れてこのエラーを修正する必要があります」

declare [-aAfFgilnrtux] [-p] [name[=value] ...]
typeset [-aAfFgilnrtux] [-p] [name[=value] ...]
  Declare variables and/or give them attributes
  ...
  -n Give each name the nameref attribute, making it a name reference
     to another variable.  That other variable is defined by the value
     of name.  All references and assignments to name, except for⋅
     changing the -n attribute itself, are performed on the variable
     referenced by name's value.  The -n attribute cannot be applied to
     array variables.
...
When used in a function, declare and typeset make each name local,
as with the local command, unless the -g option is supplied...

そしてまた:

パラメーター

変数には、-nオプションを使用してdeclareまたはlocal組み込みコマンド(後述のdeclareおよびlocalの説明を参照)にnameref属性を割り当てて、namerefまたは別の変数への参照を作成できます。これにより、変数を間接的に操作することができます。 nameref変数が参照または割り当てられているときはいつでも、操作はnameref変数の値で指定された変数に対して実際に実行されます。 namerefは、シェル関数内で一般的に使用され、その名前が関数の引数として渡される変数を参照します。たとえば、変数名が最初の引数としてShell関数に渡されると、

      declare -n ref=$1

関数内部では、最初の引数として渡された変数名を値とするnameref変数refが作成されます。 refへの参照と代入は、名前が$ 1として渡された変数への参照と代入として扱われます。 forループ内の制御変数がnameref属性を持っている場合、単語のリストはシェル変数のリストになることができ、ループが実行されるときにリスト内の各単語に対して名前参照が順番に確立されます。配列変数に-n属性を指定することはできません。ただし、nameref変数は配列変数と添字付き配列変数を参照できます。 namerefは、unset組み込みコマンドの-nオプションを使用して設定解除できます。それ以外の場合、nameref変数の名前を引数としてunsetが実行されると、nameref変数によって参照される変数は設定解除されます。

例えば、( EDIT 2 :(ありがとうRon)名前の前に関数内部変数名を付け、外部変数の衝突を最小限に抑えます。これは、最終的には適切に答える必要があります。

# $1 : string; your variable to contain the return value
function return_a_string () {
    declare -n ret=$1
    local MYLIB_return_a_string_message="The date is "
    MYLIB_return_a_string_message+=$(date)
    ret=$MYLIB_return_a_string_message
}

そしてこの例をテストする:

$ return_a_string result; echo $result
The date is 20160817

関数内でbashの "declare"ビルトインを使用すると、宣言された変数はデフォルトで "local"になり、 " - n"も "local"と一緒に使用できます。

「重要な宣言」変数と「退屈なローカル」変数を区別することをお勧めします。したがって、このように「declare」と「local」を使用すると、ドキュメントとして機能します。

EDIT 1 - (Karstenによる以下のコメントへの返信) - これ以上コメントを追加できないが、Karstenのコメントで私は思ったので、これを読んで次のテストをしてください。次の手順は問題なく機能するため、コマンドラインから正確な一連のテスト手順を指定して、問題が存在すると考えてください。

$ return_a_string ret; echo $ret
The date is 20170104

(上記の関数をbashの用語にペーストした後、今すぐ実行しました - ご覧のとおり、結果は正常に機能します。)

41
zenaan

上記の bstpierre と同様に、出力変数に明示的に名前を付けることを使用し、使用することをお勧めします。

function some_func() # OUTVAR ARG1
{
   local _outvar=$1
   local _result # Use some naming convention to avoid OUTVARs to clash
   ... some processing ....
   eval $_outvar=\$_result # Instead of just =$_result
}

$を引用符で囲むことに注意してください。これは$resultの内容をシェルの特殊文字として解釈するのを避けます。私はこれがエコーをとらえるresult=$(some_func "arg1")のイディオムよりも 一桁速い であることを発見しました。関数呼び出しからの標準出力のキャプチャがほとんど壊滅的なMSYSでbashを使用すると、速度の違いはさらに顕著になります。

ローカル変数は動的にbashでスコープ指定されているので、ローカル変数を送信してもかまいません。

function another_func() # ARG
{
   local result
   some_func result "$1"
   echo result is $result
}
33
Markarian451

関数の出力を捉えることもできます。

#!/bin/bash
function getSomeString() {
     echo "tadaa!"
}

return_var=$(getSomeString)
echo $return_var
# Alternative syntax:
return_var=`getSomeString`
echo $return_var

奇妙に見えますが、グローバル変数を使用するよりも優れています。パラメータを渡すことは通常通りに動作します。それらを中括弧またはバックティックの内側に置くだけです。

20
chiborg

前述のように、関数から文字列を返すための「正しい」方法はコマンド置換です。関数がコンソールにも出力する必要がある場合(@Maniが前述したように)、関数の先頭に一時的なfdを作成してconsoleにリダイレクトします。文字列を返す前に一時的なfdを閉じます。

#!/bin/bash
# file:  func_return_test.sh
returnString() {
    exec 3>&1 >/dev/tty
    local s=$1
    s=${s:="some default string"}
    echo "writing directly to console"
    exec 3>&-     
    echo "$s"
}

my_string=$(returnString "$*")
echo "my_string:  [$my_string]"

パラメータなしでスクリプトを実行すると、次のようになります。

# ./func_return_test.sh
writing directly to console
my_string:  [some default string]

これが人々に役立つことを願っています

- そして

12
Andy

他の人々が書いているように、最も直接的で堅牢な解決策はコマンド置換を使うことです:

assign()
{
    local x
    x="Test"
    echo "$x"
}

x=$(assign) # This assigns string "Test" to x

これは別のプロセスを必要とするため、欠点はパフォーマンスです。

このトピックで提案されているもう1つの技法、つまり、代入する変数の名前を引数として渡すことには副作用があります。基本的な形式では推奨しません。問題は、おそらく戻り値を計算するために関数内にいくつかの変数が必要になることです。そして戻り値を格納するための変数の名前がそれらの1つを妨害することが起こるかもしれません。

assign()
{
    local x
    x="Test"
    eval "$1=\$x"
}

assign y # This assigns string "Test" to y, as expected

assign x # This will NOT assign anything to x in this scope
         # because the name "x" is declared as local inside the function

もちろん、関数の内部変数をローカル変数として宣言しないでください、そうでなければ、同じ名前を持つ変数がある場合は、誤って親スコープから無関係の変数を上書きしてしまう可能性があります。 。

考えられる回避策の1つは、渡された変数をグローバルとして明示的に宣言することです。

assign()
{
    local x
    eval declare -g $1
    x="Test"
    eval "$1=\$x"
}

名前 "x"が引数として渡された場合、関数本体の2行目は前のローカル宣言を上書きします。しかし、名前自体はまだ干渉する可能性があるため、そこに戻り値を書き込む前に、渡された変数に以前に格納されている値を使用する場合は、最初に別のローカル変数にコピーする必要があります。そうでなければ、結果は予測不可能になります。その上、これはBASHの最新版、すなわち4.2でのみ動作します。より移植性の高いコードでは、同じ効果を持つ明示的な条件付き構造を利用することができます。

assign()
{
    if [[ $1 != x ]]; then
      local x
    fi
    x="Test"
    eval "$1=\$x"
}

おそらく最もエレガントな解決策は、単に関数の戻り値のために1つのグローバル名を予約し、それを書くすべての関数で一貫して使うことです。

10
Tomasz Żuk

グローバル変数を使うことができます:

declare globalvar='some string'

string ()
{
  eval  "$1='some other string'"
} # ----------  end of function string  ----------

string globalvar

echo "'${globalvar}'"

これは与える

'some other string'
8
Fritz G. Mehner

/dev/ttyの使用を避けるための追加のファイルディスクリプタ操作を伴う、Andyの答えに対する私のコメントを説明するために:

#!/bin/bash

exec 3>&1

returnString() {
    exec 4>&1 >&3
    local s=$1
    s=${s:="some default string"}
    echo "writing to stdout"
    echo "writing to stderr" >&2
    exec >&4-
    echo "$s"
}

my_string=$(returnString "$*")
echo "my_string:  [$my_string]"

それでも厄介です。

6
jmb

あなたがそれを持っている方法は、スコープを壊すことなくこれをする唯一の方法です。 Bashはリターンタイプの概念を持っておらず、終了コードとファイルディスクリプタ(stdin/out/errなど)だけを持っています。

3
Daenyth

Vicky Ronnen の頭を上げて、次のコードを考慮してください。

function use_global
{
    eval "$1='changed using a global var'"
}

function capture_output
{
    echo "always changed"
}

function test_inside_a_func
{
    local _myvar='local starting value'
    echo "3. $_myvar"

    use_global '_myvar'
    echo "4. $_myvar"

    _myvar=$( capture_output )
    echo "5. $_myvar"
}

function only_difference
{
    local _myvar='local starting value'
    echo "7. $_myvar"

    local use_global '_myvar'
    echo "8. $_myvar"

    local _myvar=$( capture_output )
    echo "9. $_myvar"
}

declare myvar='global starting value'
echo "0. $myvar"

use_global 'myvar'
echo "1. $myvar"

myvar=$( capture_output )
echo "2. $myvar"

test_inside_a_func
echo "6. $_myvar" # this was local inside the above function

only_difference



あげる

0. global starting value
1. changed using a global var
2. always changed
3. local starting value
4. changed using a global var
5. always changed
6. 
7. local starting value
8. local starting value
9. always changed

通常のシナリオではtest_inside_a_func関数で使用されている構文を使用することになります。したがって、ほとんどの場合、両方の方法を使用できます。 Vicky Ronnenが正しく指摘しているように、他の言語で見つけることができます。

3
Luca Borrione

呼び出し側が変数名を渡すことができる(evalまたはdeclare -nのいずれを使用する場合でも)「名前付き出力変数」方式の主な問題は、不注意なエイリアシング、つまり名前の衝突です。カプセル化の観点から、追加できないのはひどいことです。または、関数の呼び出し元のALLをチェックせずに関数内のローカル変数の名前を変更して、出力パラメータと同じ名前を渡さないようにします。 (または、逆に言えば、使用する出力パラメータがその関数内でローカルではないことを確認するためだけに、呼び出している関数のソースを読む必要はありません。)

これを回避する唯一の方法は、( Evi1M4chine で提案されているように)REPLYのような単一の専用出力変数、または Ron Burk で提案されているような規則を使用することです。

しかし、callを使って行ったように、関数に固定の出力変数internalを使い、それから呼び出し元からこの事実を隠すの上に砂糖を追加することは可能です。次の例の関数これを概念実証と見なしますが、キーポイントは次のとおりです。

  • この関数は常に戻り値をREPLYに代入し、通常どおり終了コードを返すこともできます。
  • 呼び出し側から見ると、戻り値はREPLYを含む任意の変数(ローカルまたはグローバル)に割り当てることができます(wrapperの例を参照)。関数の終了コードはパススルーされます。 ifwhile、あるいは同様の構造は期待通りに動作します。
  • 構文的には、関数呼び出しは依然として単一の単純なステートメントです。

これが機能するのは、call関数自体がローカル変数を持たず、REPLY以外の変数を使用しないため、名前が衝突する可能性がないためです。呼び出し元定義の出力変数名が割り当てられた時点では、呼び出される関数のスコープ内ではなく、事実上呼び出し元のスコープ内(厳密にはcall関数の同じスコープ内)にあります。

#!/bin/bash
function call() { # var=func [args ...]
  REPLY=; "${1#*=}" "${@:2}"; eval "${1%%=*}=\$REPLY; return $?"
}

function greet() {
  case "$1" in
    us) REPLY="hello";;
    nz) REPLY="kia ora";;
    *) return 123;;
  esac
}

function wrapper() {
  call REPLY=greet "[email protected]"
}

function main() {
  local a b c d
  call a=greet us
  echo "a='$a' ($?)"
  call b=greet nz
  echo "b='$b' ($?)"
  call c=greet de
  echo "c='$c' ($?)"
  call d=wrapper us
  echo "d='$d' ($?)"
}
main

出力:

a='hello' (0)
b='kia ora' (0)
c='' (123)
d='hello' (0)
2
Karsten

選択肢はすべて列挙されていると思います。どちらを選ぶかはあなたの特定のアプリケーションに最適なスタイルの問題に帰着するかもしれません、そしてそのような意味で、私は私が役に立つと思う特定のスタイルを一つ提供したいのです。 bashでは、変数と関数は同じ名前空間にはありません。そのため、同じ名前の変数を関数の値として扱うことは、厳密に適用すれば、名前の衝突を最小限に抑え、読みやすさを向上させるための規則です。実生活からの例:

UnGetChar=
function GetChar() {
    # assume failure
    GetChar=
    # if someone previously "ungot" a char
    if ! [ -z "$UnGetChar" ]; then
        GetChar="$UnGetChar"
        UnGetChar=
        return 0               # success
    # else, if not at EOF
    Elif IFS= read -N1 GetChar ; then
        return 0           # success
    else
        return 1           # EOF
    fi
}

function UnGetChar(){
    UnGetChar="$1"
}

そして、そのような関数の使用例

function GetToken() {
    # assume failure
    GetToken=
    # if at end of file
    if ! GetChar; then
        return 1              # EOF
    # if start of comment
    Elif [[ "$GetChar" == "#" ]]; then
        while [[ "$GetChar" != $'\n' ]]; do
            GetToken+="$GetChar"
            GetChar
        done
        UnGetChar "$GetChar"
    # if start of quoted string
    Elif [ "$GetChar" == '"' ]; then
# ... et cetera

ご覧のとおり、戻りステータスは、必要なときに使用するために存在します。不要な場合は無視します。 「返された」変数も同様に使用または無視することができますが、もちろん の後 だけ関数が呼び出されます。

もちろん、これは単なる慣例です。戻り値の前に関連付けられた値を設定すること(つまり、関数の先頭で常にnullにする私の慣例)に失敗したり、関数を再度呼び出すことによって(おそらく間接的に)その値を変換したりするのは自由です。それでも、私がbash関数を多用することに気づいた場合、それは私が非常に役に立つと思う慣習です。

これが兆候であるという感情とは対照的に、 "Perlに移行する"私の哲学は、どんな言語の複雑さを管理するためにも規約が常に重要であるということです。

2
Ron Burk

スカラ配列値の両方のオブジェクトを返すbashパターン:

定義

url_parse() { # parse 'url' into: 'url_Host', 'url_port', ...
   local "[email protected]" # inject caller 'url' argument in local scope
   local url_Host="..." url_path="..." # calculate 'url_*' components
   declare -p ${!url_*} # return only 'url_*' object fields to the caller
}

呼び出し

main() { # invoke url parser and inject 'url_*' results in local scope
   eval "$(url_parse url=http://Host/path)" # parse 'url'
   echo "Host=$url_Host path=$url_path" # use 'url_*' components
}
1

あなたは文字列をechoすることができますが、それを何か他のものにパイプする(|)ことによってそれをキャッチすることができます。

ShellCheck はこの使用法を非推奨として報告していますが、exprでそれを行うことができます。

1
apennebaker

私のプログラムでは、慣例により、これが既存の$REPLY変数の目的であり、readがその目的のために使用しています。

function getSomeString {
  REPLY="tadaa"
}

getSomeString
echo $REPLY

このechoes

tadaa

しかし衝突を避けるために、他のどのグローバル変数もそうするでしょう。

declare result

function getSomeString {
  result="tadaa"
}

getSomeString
echo $result

それでも十分でない場合は、 Markarian451 の解決策をお勧めします。

0
Evi1M4chine