137

スクリプト内のコマンドの出力を配列に読み込む必要があります。コマンドは、たとえば次のとおりです。

ps aux | grep | grep | x 

そしてそれはこのように行ごとに出力を与えます:

10
20
30

コマンド出力から配列に値を読み取る必要があります。配列のサイズが3未満の場合は、いくつかの作業を行います。

4

5 に答える 5

205

*コマンドの出力にスペース (かなり頻繁) や, ?,などのグロブ文字が含まれている場合、他の回答は壊れます[...]

要素ごとに 1 行の配列でコマンドの出力を取得するには、基本的に 3 つの方法があります。

  1. Bash≥4 を使用するmapfileと、最も効率的です。

    mapfile -t my_array < <( my_command )
    
  2. それ以外の場合は、出力を読み取るループ (遅くなりますが安全です):

    my_array=()
    while IFS= read -r line; do
        my_array+=( "$line" )
    done < <( my_command )
    
  3. コメントで Charles Duffy が示唆しているように (ありがとう!)、次の方法は 2 番目のループ方法よりもパフォーマンスが優れている可能性があります。

    IFS=$'\n' read -r -d '' -a my_array < <( my_command && printf '\0' )
    

    このフォームを正確に使用していることを確認してください。つまり、次のものがあることを確認してください。

    • IFS=$'\n' ステートメントと同じ行read:これはIFS readステートメントのみの環境変数のみを設定しますしたがって、スクリプトの残りの部分にはまったく影響しません。この変数の目的はread、EOL 文字でストリームを中断するように指示すること\nです。
    • -r: これは重要。バックスラッシュをエスケープ シーケンスとして解釈しないように指示します。read
    • -d ''-d:オプションとその引数の間のスペースに注意してください''。ここにスペースを残さないと、Bash がステートメントを解析するときに引用符''の削除ステップで消えるため、 が表示されることはありません。これは、nil バイトで読み取りを停止するように指示します。と書く人もいますが、必ずしも必要ではありません。優れている。read-d $'\0'-d ''
    • -a my_arrayストリームの読み取り中readに配列にデータを入力するように指示します。my_array
    • を返すように、ステートメントのprintf '\0'にステートメントを使用する必要があります。使用しなくても実際には大したことではありません ( return code が返されるだけです。使用しなくても問題ありません。使用しないでください)。よりクリーンで、より意味的に正しいです。これは、何も出力しない とは異なることに注意してください。喜んでそこで読み取りを停止するために必要な null バイトを出力します(オプションを覚えていますか?)。 my_commandread01set -eprintf ''printf '\0'read-d ''

可能であれば、つまり、コードが Bash4 以上で動作することが確実な場合は、最初の方法を使用してください。また、短くなっていることがわかります。

を使用したい場合read、行が読み取られるときに何らかの処理を行いたい場合、ループ (方法 2) は方法 3 よりも利点がある可能性があります。($line私が示した例の変数を介して)それに直接アクセスできます。また、既に読み取られた行にアクセスすることもできます (${my_array[@]}私が示した例の配列を介して)。

これは、各行が読み取られるたびにコールバックを評価する方法を提供することに注意してください。実際、 N行が読み取らmapfileれるたびにこのコールバックのみを呼び出すように指示することもできます。とその中のオプションを見てください。(これについての私の意見では、これは少し不格好ですが、単純なことしかできない場合に使用できる場合があります。そもそもなぜこれが実装されたのか、よくわかりません!)。help mapfile-C-c


ここで、次の方法の理由を説明します。

my_array=( $( my_command) )

スペースがあると壊れます:

$ # I'm using this command to test:
$ echo "one two"; echo "three four"
one two
three four
$ # Now I'm going to use the broken method:
$ my_array=( $( echo "one two"; echo "three four" ) )
$ declare -p my_array
declare -a my_array='([0]="one" [1]="two" [2]="three" [3]="four")'
$ # As you can see, the fields are not the lines
$
$ # Now look at the correct method:
$ mapfile -t my_array < <(echo "one two"; echo "three four")
$ declare -p my_array
declare -a my_array='([0]="one two" [1]="three four")'
$ # Good!

次に、一部の人々はそれIFS=$'\n'を修正するために使用することを推奨します:

$ IFS=$'\n'
$ my_array=( $(echo "one two"; echo "three four") )
$ declare -p my_array
declare -a my_array='([0]="one two" [1]="three four")'
$ # It works!

しかし、今度はglobsで別のコマンドを使用しましょう:

$ echo "* one two"; echo "[three four]"
* one two
[three four]
$ IFS=$'\n'
$ my_array=( $(echo "* one two"; echo "[three four]") )
$ declare -p my_array
declare -a my_array='([0]="* one two" [1]="t")'
$ # What?

これは、現在のディレクトリにファイルが呼び出されているためです...そして、このファイル名はグロブtと一致しています...この時点で、グロブを無効にするために使用することをお勧めする人もいます:しかし、それを見てください:修正できるようにするには、変更して使用する必要があります壊れたテクニック(そして、あなたはそれを本当に修正していません)!それを行うとき、私たちは本当にシェルと戦っています。シェルで作業しているのではありません。 [three four]set -fIFSset -f

$ mapfile -t my_array < <( echo "* one two"; echo "[three four]")
$ declare -p my_array
declare -a my_array='([0]="* one two" [1]="[three four]")'

ここでは、シェルを使用しています。

于 2015-10-04T08:13:35.367 に答える
20

簡単な例を次に示します。ファイルとディレクトリ名 (現在のフォルダーの下) を配列に入れ、それらをカウントしようとしていると想像してください。スクリプトは次のようになります。

my_array=( `ls` )
my_array_length=${#my_array[@]}
echo $my_array_length

または、次のスクリプトを追加して、この配列を反復処理できます。

for element in "${my_array[@]}"
do
   echo "${element}"
done

これは中心的な概念であり、処理の前に入力をサニタイズする必要があることに注意してください。つまり、余分な文字を削除したり、空の文字列を処理したりします (これはこのスレッドのトピックではありません)。

于 2015-09-25T22:34:43.797 に答える