web-dev-qa-db-ja.com

bashを使用して行ごとに1つの要素を配列に読み込みます

(Gitを使用して)ディレクトリ内のすべてのステージングされていないファイルの変更のbash配列を取得しようとしています。次のコードは、ディレクトリ内の変更されたすべてのファイルを出力するために機能します。

git -C $dir/.. status --porcelain | grep "^.\w" | cut -c 4-

これは印刷します

"Directory Name/File B.txt"
"File A.txt"

使ってみた

arr1=($(git status --porcelain | grep "^.\w" | cut -c 4-))

しかしその後

for a in "${arr1[@]}"; do echo "$a"; done

${arr1[@]}の前後に引用符がある場合とない場合の両方の出力

"Directory
Name/File
B.txt"
"File
A.txt"

私も試しました

git -C $dir/.. status --porcelain | grep "^.\w" | cut -c 4- | readarray arr2

しかしその後

for a in "${arr2[@]}"; do echo "$a"; done

${arr2[@]}の前後に引用符がある場合とない場合の両方)は何も出力しません。 declare -a arr2を事前に使用しても、まったく何も起こりません。


私の質問はこれです:これらの値を配列に読み込むにはどうすればよいですか? (これは私のargosプラグイン gitbar で使用されているので、必要に応じて、すべてのコードを確認できます)。

1
vikarjramun

TL; DR

Bashでは:

_readarray -t arr2 < <(git … )
printf '%s\n' "${arr2[@]}"
_

あなたの質問には2つの明確な問題があります

  1. シェル分割。
    あなたがしたとき:

    _arr1=($(git … ))
    _

    「コマンド展開」は引用符で囲まれていないため、シェル分割とグロブの影響を受けます。

    シェル分割が何をするかを正確に確認するには、printfを使用します。

    _$ printf '<%s>  '  $(echo Word '"one simple sentence"')
    <Word>  <"one>  <simple>  <sentence">
    _

    quotingでそれを回避できます:

    _$ printf '<%s>  '  "$(echo Word '"one simple sentence"')"
    <Word "one simple sentence">
    _

    しかし、それはまた、あなたが望む改行の分割を避けるでしょう。

  2. パイプ
    あなたが実行したとき:

    _git … | … | … | readarray arr2
    _

    配列変数_arr2_が設定されましたが、パイプ(_|_)が閉じられたときになくなりました。

    最後のサブシェル内に留まる場合は、この値を使用できます。

    _$ printf '%s\n' "First value." "Second value." | 
            { readarray -t arr2; printf '%s\n' "${arr2[@]}"; }
    First value.
    Second value.
    _

    ただし、_arr2_の値はパイプの外では存続しません。

ソリューション

パイプではなく、改行で分割するにはreadを使用する必要があります。
古いものから新しいものへ:

  1. ループ。
    配列のない古いシェルの場合(位置引数を使用、唯一の準配列):

    _set --
    while IFS='' read -r value; do
        set -- "$@" "$value"
    done <<-EOT
    $(printf '%s\n' "First value." "Second value.")
    EOT
    
    printf '%s\n' "$@"
    _

    配列を設定するには(ksh、zsh、bash)

    _i=0; arr1=()
    while IFS='' read -r value; do
        arr1+=("$value")
    done <<-EOT
    $(printf '%s\n' "First value." "Second value.")
    EOT
    
    printf '%s\n' "${arr1[@]}"
    _
  2. ヒアストリング
    ヒアドキュメント(_<<_)の代わりに、ヒア文字列(_<<<_)を使用できます。

    _i=0; arr1=()
    while IFS='' read -r value; do
        arr1+=("$value")
    done <<<"$(printf '%s\n' "First value." "Second value.")"
    
    printf '%s\n' "${arr1[@]}"
    _
  3. プロセス置換
    それをサポートするシェル(ksh、zsh、bash)では、<( … )を使用してhere-stringを置き換えることができます。

    _i=0; arr1=()
    while IFS='' read -r value; do
        arr1+=("$value")
    done < <(printf '%s\n' "First value." "Second value.")
    
    printf '%s\n' "${arr1[@]}"
    _

    違い:<( )はNULバイトを出力できますが、here-stringはNULを削除(または警告)する可能性があります。 here-stringは、デフォルトで末尾の改行を追加します。他のAFAIKがあるかもしれません。

  4. readarray
    readarraybashを使用[a] (別名mapfile)ループを回避するには:

    _readarray -t arr2 < <(printf '%s\n' "First value." "Second value.")
    printf '%s\n' "${arr2[@]}"
    _

[a]Kshでは、使用前に変数をクリアする_read -A_を使用する必要がありますが、改行で分割して入力全体を一度に読み取るには、いくつかの「魔法」が必要です。

_IFS=$'\n' read -d '' -A arr2 < <(printf '%s\n' "First value." "Second value.")
_

同様のことをするには zshにmapfileモジュールをロードする が必要です。

7
Isaac

Readarrayにパイプで接続したときに、arr2配列を正しく設定するサブシェルを開始しましたが、終了しました。 readarrayへの入力としてプロセス置換を使用します。

readarray -t arr2 < <(git ...)
3
Jeff Schaller

あなたは近い

これは、「スペース文字を含むファイル名」の問題です。

デフォルトでは、セパレーターはスペース文字です。これを設定するのはIFS環境変数です。

環境変数を一時的に変更するには、これを使用します。

ifs_backup=$IFS
IFS=$(echo -en "\n\b")

次に、このコマンドの出力:

for a in "${arr1[@]}"; do echo "$a"; done

になります:

"Directory Name/File B.txt"
"File A.txt"

IFSを復元するには:

IFS=$ifs_backup
2
lauhub