Bashでコマンドの出力を配列に読み込む 質問する

Bashでコマンドの出力を配列に読み込む 質問する

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

ps aux | grep | grep | x 

次のように行ごとに出力されます。

10
20
30

コマンド出力からの値を配列に読み込む必要があり、配列のサイズが 3 未満の場合は何らかの処理を実行します。

ベストアンサー1

コマンドの出力にスペース (かなり頻繁に発生します) や 、 、 などの glob 文字が含まれている場合、他の回答は機能しなく*なり?ます[...]

コマンドの出力を要素ごとに 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のみ。したがって、スクリプトの残りの部分にはまったく影響しません。この変数の目的は、readEOL 文字でストリームを中断するように指示することです\n
    • -r: これは重要です。read バックスラッシュをエスケープシーケンスとして解釈しないようにします。
    • -d ''-d:オプションとその引数の間にスペースを入れてください''。ここにスペースを入れないと、オプションは''表示されず、引用削除Bash がステートメントを解析するときのステップ。これはreadnil バイトで読み取りを停止するように指示します。 と書く人もいます-d $'\0'が、実際には必要ありません。 の-d ''方が良いでしょう。
    • -a my_arrayストリームを読み取りながらread配列を設定するように指示します。my_array
    • printf '\0'次の文を使用する必要があります my_commandなので、 がread返されます0。 実際に使用しなくても大きな問題にはなりません ( という戻りコードが返されるだけですが1、 を使用しない場合は問題ありませんset -e。そもそも は使用すべきではありません)。ただし、その点に注意してください。 の方がよりクリーンで、意味的に正しいです。 これは とは異なりますprintf ''。 は何も出力しません。 は、がそこで読み取りを停止するためprintf '\0'に必要な null バイトを出力します(オプションを覚えていますか?)。read-d ''

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

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

は、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!

しかし、今度は別のコマンドを使ってみましょう。グロブ:

$ 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 -f必要があります(実際には修正していません)。これを行うと、IFSset -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]")'

ここではシェルを操作しています。

おすすめ記事