コマンドの出力をBashの配列に読み込む


111

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

ps aux | grep | grep | x 

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

10
20
30

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


5
@barpさん、あなたの質問に答えてください。あなたのタイプがコミュニティ全体のドレインにならないようにしてください。
James

9
@ジェームズ問題は彼が彼の質問に答えていないという事実ではありません...これはQ / Aサイトです。彼はそれらを答えられたとマークしませんでした。彼はそれらをマークする必要があります。ヒント。@ barp
DDPWNAGE

4
@barp、質問に回答済みのマークを付けてください。
smonff 2016年

関連:プロセス置換によるコマンドの出力の読み取りはファイルからの読み取りと同様であるため、Bashでファイルのコンテンツをループします。
codeforester

回答:


161

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

要素ごとに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がステートメントを解析する際の引用削除ステップでスペースが消えてしまうため、スペースは表示されません。これはread、nilバイトで読み取りを停止するように指示します。一部の人々はそれを-d $'\0'と書きますが、それは本当に必要ではありません。-d ''優れている。
    • -a my_arrayストリームの読み取り中readに配列を設定するように指示しmy_arrayます。
    • printf '\0'ステートメントを使用する必要があるため、が戻ります。それはあなたがいない場合(あなただけのリターンコード取得します実際には大したことではありませんあなたが使用していない場合は大丈夫ですが、念頭に置いていることが、ちょうど熊-これはあなたがとにかくいけないが)。それはよりクリーンで、より意味的に正しいです。これは何も出力しないとは異なることに注意してください。nullバイトを出力します。そこから読み取りを停止するために必要です(オプションを覚えていますか?)。 my_commandread01set -eprintf ''printf '\0'read-d ''

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

を使用する場合read、行が読み取られるときに何らかの処理を実行する場合、ループ(メソッド2)がメソッド3よりも優れている可能性があります。ループに直接アクセスできます($line上記の例の変数を介して)。また、すでに読んだ行にアクセスできます(${my_array[@]}例で示した例の配列を介して)。

mapfileは、各行を読み取るときにコールバックを評価する方法を提供することに注意してください。実際には、N本の行を読み取るごとにこのコールバックのみを呼び出すように指示することもできます。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 -f無効グロブへ:しかし、それを見て:あなたは変更する必要がありますIFSし、使用set -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]")'

ここでは、シェルで作業しています!


4
これは素晴らしいです、私はmapfile以前に聞いたことがありません、それはまさに私が何年も欠けていたものです。の最近のバージョンにbashは素晴らしい新機能がたくさんあると思います。ドキュメントを読んで素敵なチートシートを書き留めるのに数日費やすだけです。
ジーンパブロフスキー

6
ところで、この構文< <(command)をシェルスクリプトで使用するには、shebang行を次のようにする必要があります#!/bin/bash-として実行すると#!/bin/sh、bashは構文エラーで終了します。
ジーンパブロフスキー

1
@GenePavlovskyの役立つメモを拡張すると、スクリプトbash my_script.shはshコマンドではなくbashコマンドで実行する必要もありますsh my_script.sh
Vito

2
@Vito:確かに、この回答はBashのみを対象としていますが、厳密に準拠したPOSIXシェルは配列も実装していないため、これは問題にshなりdashません(もちろん、位置パラメータ$@配列)。
gniourf_gniourf 2017年

3
bash 4.0を必要としない別の方法としてIFS=$'\n' read -r -d '' -a my_array < <(my_command && printf '\0')、bash 3.xでも正しく機能することと、失敗した終了ステータスをからmy_commandに渡すことを検討してくださいread
Charles Duffy

86

使用できます

my_array=( $(<command>) )

コマンドの出力を<command>配列に格納しmy_arrayます。

その配列の長さにアクセスするには、

my_array_length=${#my_array[@]}

これで長さがに保存されmy_array_lengthます。


19
$(command)の出力にスペースとスペースを含む複数行がある場合はどうなりますか?「$(command)」を追加すると、すべての行からのすべての出力が配列の最初の[0]要素に配置されます。
ikwyl6 2016年

3
@ ikwyl6回避策は、コマンド出力を変数に割り当て、それを使って配列を作成するか、配列に追加することです。VAR="$(<command>)"その後、my_array=("$VAR")またはmy_array+=("$VAR")
Vito

10

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

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

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

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

これはコアコンセプトであり、入力は以前に無害化されていると見なされていることに注意してください。つまり、余分な文字の削除、空の文字列の処理などです(これはこのスレッドのトピックではありません)。


3
上記の回答で言及された理由によるひどいアイデア
Hubert Grzeskowiak 2018年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.