101

配列を作成する関数があり、呼び出し元に配列を返したい:

create_array() {
  local my_list=("a", "b", "c")
  echo "${my_list[@]}"
}

my_algorithm() {
  local result=$(create_array)
}

これで、展開された文字列のみを取得します。グローバルなものを使用せずに my_list を「返す」にはどうすればよいですか?

4

19 に答える 19

41

グローバルの何が問題になっていますか?

配列を返すことは実際には実用的ではありません。落とし穴がたくさんあります。

とはいえ、変数が同じ名前であっても問題ない場合に機能するテクニックの 1 つを次に示します。

$ f () { local a; a=(abc 'def ghi' jkl); declare -p a; }
$ g () { local a; eval $(f); declare -p a; }
$ f; declare -p a; echo; g; declare -p a
declare -a a='([0]="abc" [1]="def ghi" [2]="jkl")'
-bash: declare: a: not found

declare -a a='([0]="abc" [1]="def ghi" [2]="jkl")'
-bash: declare: a: not found

コマンド (のdeclare -pコマンドを除く) は、f()デモンストレーション目的で配列の状態を表示するために使用されます。 ではf()、配列を返すメカニズムとして使用されます。

配列に別の名前が必要な場合は、次のようにすることができます。

$ g () { local b r; r=$(f); r="declare -a b=${r#*=}"; eval "$r"; declare -p a; declare -p b; }
$ f; declare -p a; echo; g; declare -p a
declare -a a='([0]="abc" [1]="def ghi" [2]="jkl")'
-bash: declare: a: not found

-bash: declare: a: not found
declare -a b='([0]="abc" [1]="def ghi" [2]="jkl")'
-bash: declare: a: not found
于 2012-05-14T12:41:40.150 に答える
17

Bash はデータ構造を戻り値として渡すことができません。戻り値は、0 ~ 255 の数値の終了ステータスでなければなりません。ただし、必要に応じて、コマンドまたはプロセス置換を使用して、コマンドを eval ステートメントに渡すことができます。

これはめったに面倒なことではありません、IMHO。Bash でデータ構造を渡す必要がある場合は、グローバル変数を使用します。これがその目的です。ただし、何らかの理由でそれを行いたくない場合は、位置パラメーターの観点から考えてください。

あなたの例は、グローバル変数の代わりに位置パラメーターを使用するように簡単に書き直すことができます。

use_array () {
    for idx in "$@"; do
        echo "$idx"
    done
}

create_array () {
    local array=("a" "b" "c")
    use_array "${array[@]}"
}

ただし、これにより、ある程度の不必要な複雑さが生じます。一般に、bash 関数は、副作用のあるプロシージャのように扱い、順番に呼び出すと最適に機能します。

# Gather values and store them in FOO.
get_values_for_array () { :; }

# Do something with the values in FOO.
process_global_array_variable () { :; }

# Call your functions.
get_values_for_array
process_global_array_variable

グローバル名前空間を汚染することだけが心配な場合は、unset ビルトインを使用して、使い終わったグローバル変数を削除することもできます。元の例を使用して、my_listをグローバルにし ( localキーワードを削除して)、my_algorithmunset my_listの最後に追加して自分の後にクリーンアップします。

于 2012-05-14T12:30:09.917 に答える
14

あなたは元の解決策からそれほど離れていませんでした。いくつかの問題があり、カンマを区切り記号として使用し、返されたアイテムをリストにキャプチャできませんでした。これを試してください:

my_algorithm() {
  local result=( $(create_array) )
}

create_array() {
  local my_list=("a" "b" "c")  
  echo "${my_list[@]}" 
}

埋め込まれたスペースに関するコメントを考慮すると、いくつかの微調整を使用してIFSそれを解決できます。

my_algorithm() {
  oldIFS="$IFS"
  IFS=','
  local result=( $(create_array) )
  IFS="$oldIFS"
  echo "Should be 'c d': ${result[1]}"
}

create_array() {
  IFS=','
  local my_list=("a b" "c d" "e f") 
  echo "${my_list[*]}" 
}
于 2012-05-14T19:27:46.687 に答える
4

便利な例: 関数から配列を返す

function Query() {
  local _tmp=`echo -n "$*" | mysql 2>> zz.err`;
  echo -e "$_tmp";
}

function StrToArray() {
  IFS=$'\t'; set $1; for item; do echo $item; done; IFS=$oIFS;
}

sql="SELECT codi, bloc, requisit FROM requisits ORDER BY codi";
qry=$(Query $sql0);
IFS=$'\n';
for row in $qry; do
  r=( $(StrToArray $row) );
  echo ${r[0]} - ${r[1]} - ${r[2]};
done
于 2012-11-07T11:26:50.843 に答える
3

私はさまざまな実装を試しましたが、スペースを含む要素を持つ配列を保持するものはありませんでした...それらはすべてを使用する必要があったためechoです。

# These implementations only work if no array items contain spaces.
use_array() {  eval echo  '(' \"\${${1}\[\@\]}\" ')';  }
use_array() {  local _array="${1}[@]"; echo '(' "${!_array}" ')';  }

解決

それから私はデニス・ウィリアムソンの答えに出くわしました。彼のメソッドを次の関数に組み込んで、a) 任意の配列を受け入れ、b) 配列の受け渡し、複製、追加に使用できるようにしました。

# Print array definition to use with assignments, for loops, etc.
#   varname: the name of an array variable.
use_array() {
    local r=$( declare -p $1 )
    r=${r#declare\ -a\ *=}
    # Strip keys so printed definition will be a simple list (like when using
    # "${array[@]}").  One side effect of having keys in the definition is 
    # that when appending arrays (i.e. `a1+=$( use_array a2 )`), values at
    # matching indices merge instead of pushing all items onto array.
    echo ${r//\[[0-9]\]=}
}
# Same as use_array() but preserves keys.
use_array_assoc() {
    local r=$( declare -p $1 )
    echo ${r#declare\ -a\ *=}
}  

その後、他の関数は、キャッチ可能な出力または間接引数を使用して配列を返すことができます。

# catchable output
return_array_by_printing() {
    local returnme=( "one" "two" "two and a half" )
    use_array returnme
}
eval test1=$( return_array_by_printing )

# indirect argument
return_array_to_referenced_variable() {
    local returnme=( "one" "two" "two and a half" )
    eval $1=$( use_array returnme )
}
return_array_to_referenced_variable test2

# Now both test1 and test2 are arrays with three elements
于 2013-04-12T23:03:32.463 に答える
3

[注:以下は、私には意味をなさない理由により、この回答の編集として拒否されました(編集は投稿の作成者に対処することを意図していなかったためです!)。答え。]

Matt McClure の手法を Steve Zobell が適応させたより単純な実装では、RastaMatt で提案されているように bash 組み込み (バージョン == 4以降)を使用して、実行時に配列に変換できる配列の表現を作成します。( と の両方が同じコードに名前を付けていることに注意してください。) それでもグローバルを回避し (パイプ内で関数を使用できるようにします)、依然として厄介な文字を処理します。readarray readarraymapfile

より完全に開発された (より多くのモジュール化など) が、まだおもちゃのような例については、bash_pass_arrays_between_functionsを参照してください。以下は、簡単に実行できるいくつかの例です。これらは、モデレーターが外部リンクについて騒ぎ立てるのを避けるためにここに提供されています。

次のブロックを切り取り、bash ターミナルに貼り付けて and を作成/tmp/source.sh/tmp/junk1.shます。

FP='/tmp/source.sh'     # path to file to be created for `source`ing
cat << 'EOF' > "${FP}"  # suppress interpretation of variables in heredoc
function make_junk {
   echo 'this is junk'
   echo '#more junk and "b@d" characters!'
   echo '!#$^%^&(*)_^&% ^$#@:"<>?/.,\\"'"'"
}

### Use 'readarray' (aka 'mapfile', bash built-in) to read lines into an array.
### Handles blank lines, whitespace and even nastier characters.
function lines_to_array_representation {
    local -a arr=()
    readarray -t arr
    # output array as string using 'declare's representation (minus header)
    declare -p arr | sed -e 's/^declare -a [^=]*=//'
}
EOF

FP1='/tmp/junk1.sh'      # path to script to run
cat << 'EOF' > "${FP1}"  # suppress interpretation of variables in heredoc
#!/usr/bin/env bash

source '/tmp/source.sh'  # to reuse its functions

returned_string="$(make_junk | lines_to_array_representation)"
eval "declare -a returned_array=${returned_string}"
for elem in "${returned_array[@]}" ; do
    echo "${elem}"
done
EOF
chmod u+x "${FP1}"
# newline here ... just hit Enter ...

実行/tmp/junk1.sh:出力は

this is junk
#more junk and "b@d" characters!
!#$^%^&(*)_^&% ^$#@:"<>?/.,\\"'

Notelines_to_array_representationは空白行も処理します。次のブロックを bash ターミナルに貼り付けてみてください。

FP2='/tmp/junk2.sh'      # path to script to run
cat << 'EOF' > "${FP2}"  # suppress interpretation of variables in heredoc
#!/usr/bin/env bash

source '/tmp/source.sh'  # to reuse its functions

echo '`bash --version` the normal way:'
echo '--------------------------------'
bash --version
echo # newline

echo '`bash --version` via `lines_to_array_representation`:'
echo '-----------------------------------------------------'
bash_version="$(bash --version | lines_to_array_representation)"
eval "declare -a returned_array=${bash_version}"
for elem in "${returned_array[@]}" ; do
    echo "${elem}"
done
echo # newline

echo 'But are they *really* the same? Ask `diff`:'
echo '-------------------------------------------'

echo 'You already know how to capture normal output (from `bash --version`):'
declare -r PATH_TO_NORMAL_OUTPUT="$(mktemp)"
bash --version > "${PATH_TO_NORMAL_OUTPUT}"
echo "normal output captured to file @ ${PATH_TO_NORMAL_OUTPUT}"
ls -al "${PATH_TO_NORMAL_OUTPUT}"
echo # newline

echo 'Capturing L2AR takes a bit more work, but is not onerous.'
echo "Look @ contents of the file you're about to run to see how it's done."

declare -r RAW_L2AR_OUTPUT="$(bash --version | lines_to_array_representation)"
declare -r PATH_TO_COOKED_L2AR_OUTPUT="$(mktemp)"
eval "declare -a returned_array=${RAW_L2AR_OUTPUT}"
for elem in "${returned_array[@]}" ; do
    echo "${elem}" >> "${PATH_TO_COOKED_L2AR_OUTPUT}"
done
echo "output from lines_to_array_representation captured to file @ ${PATH_TO_COOKED_L2AR_OUTPUT}"
ls -al "${PATH_TO_COOKED_L2AR_OUTPUT}"
echo # newline

echo 'So are they really the same? Per'
echo "\`diff -uwB "${PATH_TO_NORMAL_OUTPUT}" "${PATH_TO_COOKED_L2AR_OUTPUT}" | wc -l\`"
diff -uwB "${PATH_TO_NORMAL_OUTPUT}" "${PATH_TO_COOKED_L2AR_OUTPUT}" | wc -l
echo '... they are the same!'
EOF
chmod u+x "${FP2}"
# newline here ... just hit Enter ...

/tmp/junk2.sh@ コマンドラインを実行します。あなたの出力は私のものに似ているはずです:

`bash --version` the normal way:
--------------------------------
GNU bash, version 4.3.30(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

`bash --version` via `lines_to_array_representation`:
-----------------------------------------------------
GNU bash, version 4.3.30(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

But are they *really* the same? Ask `diff`:
-------------------------------------------
You already know how to capture normal output (from `bash --version`):
normal output captured to file @ /tmp/tmp.Ni1bgyPPEw
-rw------- 1 me me 308 Jun 18 16:27 /tmp/tmp.Ni1bgyPPEw

Capturing L2AR takes a bit more work, but is not onerous.
Look @ contents of the file you're about to run to see how it's done.
output from lines_to_array_representation captured to file @ /tmp/tmp.1D6O2vckGz
-rw------- 1 me me 308 Jun 18 16:27 /tmp/tmp.1D6O2vckGz

So are they really the same? Per
`diff -uwB /tmp/tmp.Ni1bgyPPEw /tmp/tmp.1D6O2vckGz | wc -l`
0
... they are the same!
于 2016-09-15T03:54:29.090 に答える
2

最近、同様の機能が必要だったので、以下はRashaMattSteve Zobellによる提案の組み合わせです。

  1. 関数内から各配列/リスト要素を個別の行としてエコーします
  2. mapfileを使用して、関数によってエコーされたすべての配列/リスト要素を読み取ります。

私が見る限り、文字列はそのまま保持され、空白は保持されます。

#!bin/bash

function create-array() {
  local somearray=("aaa" "bbb ccc" "d" "e f g h")
  for elem in "${somearray[@]}"
  do
    echo "${elem}"
  done
}

mapfile -t resa <<< "$(create-array)"

# quick output check
declare -p resa

いくつかのバリエーション…</p>

#!/bin/bash

function create-array-from-ls() {
  local somearray=("$(ls -1)")
  for elem in "${somearray[@]}"
  do
    echo "${elem}"
  done
}

function create-array-from-args() {
  local somearray=("$@")
  for elem in "${somearray[@]}"
  do
    echo "${elem}"
  done
}


mapfile -t resb <<< "$(create-array-from-ls)"
mapfile -t resc <<< "$(create-array-from-args 'xxx' 'yy zz' 't s u' )"

sentenceA="create array from this sentence"
sentenceB="keep this sentence"

mapfile -t resd <<< "$(create-array-from-args ${sentenceA} )"
mapfile -t rese <<< "$(create-array-from-args "$sentenceB" )"
mapfile -t resf <<< "$(create-array-from-args "$sentenceB" "and" "this words" )"

# quick output check
declare -p resb
declare -p resc
declare -p resd
declare -p rese
declare -p resf
于 2016-10-12T13:54:30.247 に答える
2

evalを使用したり変更IFSしたりする必要はありません\n。これを行うには、少なくとも 2 つの良い方法があります。

1)echomapfile

関数内の配列の各項目を単純にエコーし、それを使用mapfileして配列に変換できます。

outputArray()
{
    for i
    {
        echo "$i"
    }
}

declare -a arr=( 'qq' 'www' 'ee rr' )
mapfile -t array < <(outputArray "${arr[@]}")
for i in "${array[@]}"
do
   echo "i=$i"
done

(( $# == 0 )) && readarray -t temp && set "${temp[@]}" && unset tempパイプを使用して機能させるには、出力配列の先頭に追加します。stdin をパラメーターに変換します。

2)declare -psed

declare -pこれは、sedの代わりにとを使用して行うこともできますmapfile

outputArray()
{
    (( $# == 0 )) && readarray -t temp && set "${temp[@]}" && unset temp
    for i; { echo "$i"; }
}

returnArray()
{
    local -a arr=()
    (( $# == 0 )) && readarray -t arr || for i; { arr+=("$i"); }
    declare -p arr | sed -e 's/^declare -a [^=]*=//'
}

declare -a arr=( 'qq' 'www' 'ee rr' )

declare -a array=$(returnArray "${arr[@]}")
for i in "${array[@]}"
do
   echo "i=$i"
done

declare -a array=$(outputArray "${arr[@]}" | returnArray)
echo
for i in "${array[@]}"
do
    echo "i=$i"
done

declare -a array < <(outputArray "${arr[@]}" | returnArray)
echo
for i in "${array[@]}"
do
   echo "i=$i"
done
于 2021-04-09T22:16:59.713 に答える