長い話を短く

長い話を短く

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効果はありません。


私の質問は:この値を配列としてどのように読み取ることができますか? (これは私のアルゴスプラグインで使用されます。Git バー、もし私のコードをすべて見ることができるように)。

答え1

長い話を短く

バッシュから:

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

あなたの質問には2つの異なる質問があります

  1. 殻が壊れています。
    これを行うとき:

    arr1=($(git … ))
    

    「コマンド拡張」には引用符がないため、シェル分割とglobの影響を受けます。

    シェル分割が何をするのかを正確に見るには、printfを使用してください。

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

    これは次の方法で回避できます。引用する:

    $ 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しかし、パイプラインを離れた後は、その価値は維持されません。

解決策

改行を分割するには読み取りを使用する必要がありますが、パイプは使用できません。
古いものから新しいものまで:

  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. Here-string here document() の代わりに 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[@]}"
    
  3. プロセスの置き換え
    これをサポートするシェル(ksh、zsh、bash)では、これを使用して<( … )ここで文字列を置き換えることができます。

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

    違いは<( )NULバイトをエクスポートする機能ですが、文字列はNULを削除(または警告)できます。デフォルトでは、ここでは末尾の改行文字が文字列に追加されます。 AFAIK他に何かあるかもしれません。

  4. readarrayは次の目的で
    使用されます。readarraybash[ㅏ](別名mapfile)ループを避ける:

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

[ㅏ]kshでは、使用する前に変数を消去する必要がありますread -Aが、改行文字を分割して入力全体を一度に読むには少し「魔法」が必要です。

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

必要です。mapfilezshからモジュールをロードする似たようなことをしてみてください。

答え2

readarray にパイプすると、arr2 配列を正しく埋めるサブシェルが起動してから終了します。 readarrayへの入力としてプロセス置換を使用します。

readarray -t arr2 < <(git ...)

答え3

あなたは近いです

これは「空白文字を含むファイル名」の問題です。

デフォルトでは、区切り文字は空白文字です。これは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

答え4

コードの内部部分に引用符を追加します。

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

関連情報