配列を作成して追加します。 mapfileはarr + =(input)と同じですか、それとも何かが欠けていますか?

配列を作成して追加します。 mapfileはarr + =(input)と同じですか、それとも何かが欠けていますか?

mapfile利点がそれより大きい状況がありますかarr+=(input)

簡単な例

マッピングファイルの配列名、arr:

mkdir {1,2,3}

mapfile -t arr < <(ls)

declare -p arr

出力:

declare -a arr=([0])="1" [1]="2" [2]="3")

編集する:

次のヘッダーを変更しました。本文にはy配列名がありますが、ヘッダーには名前があり、混乱をarr招く可能性があります。

y+=(入力)

IFS=$'\n'

y+=($(ls))

declare -p y

出力:

declare -a y=([0])="1" [1]="2" [2]="3")

mapfile1つの利点は、単語の分割について心配する必要がないことだと思います。

あるいは、単語の分離を防ぐように設定することもできますが、IFS=$'\n'この例では心配する必要はありません。

2番目の例は、書くのが簡単なようです。何か抜けましたか?

ベストアンサー1

後で彼らはまったく同じではありませんIFS=$'\n'

特にbashでは(構文はzshで借りましたが):

arr=( $(cmd) )

arr+=( $(cmd) )以下の目的で使用されます。追加したがって、配列の要素はkeys=( -1 "${!arr[@]}" ); readarray -tO "$(( ${keys[@]: -1} + 1))" arr < <(cmd)²と比較されます。

する:

  1. cmdパイプの書き込みの終わりに、標準出力が開いたサブシェルで実行されます。
  2. その間、親シェルプロセスはパイプのもう一方の端からデータを読み込み、次のことを行います。
    • NUL文字と末尾の改行を削除
    • 特殊変数の内容に基づいて$IFS結果文字列を分割します。$IFS空白文字(改行文字など)の場合、動作はより複雑です。
      • 先行項目と末尾項目​​が削除されます。 (改行の場合、上記のようにコマンド置換によって削除されました.)
      • 1つ以上のシーケンスが考慮されます。一つスプリッタ。たとえば、出力はとのprintf '\n\n\na\n\n\nb\n\n\n'2つの要素に分けられます。ab
    • noglobこれにより、これらの各単語はファイル名の生成(別名ワイルドカード)の対象となり、その動作は、、、、、、、などのさまざまnullglobなオプションの影響を受けます。これは、、、およびいくつかのbashバージョンを含む単語に対して機能し、有効になっている場合はそれ以上のバージョンも使用できます。failglobextglobglobasciirangesglabstarnocaseglob*?[\extglob
  3. 次に、結果の単語が配列$arrの要素として割り当てられます。

例:

bash-5.1$ touch x '\x' '?x' aX $'foo\n\n\n\n*'
bash-5.1$ IFS=$'\n'
bash-5.1$ ls | cat
aX
foo



*
?x
\x
x
bash-5.1$ arr=( $(ls) )
bash-5.1$ typeset -p arr
declare -a arr=([0]="aX" [1]="foo" [2]="aX" [3]=$'foo\n\n\n\n*' [4]="?x" [5]="\\x" [6]="x" [7]="?x" [8]="\\x" [9]="\\x" [10]="x")

ご覧のとおり、ファイルは現在の作業ディレクトリのファイルリストに$'foo\n\n\n\n*'分割されfooて展開され*ます。これは、我々が両方とも取得する理由と同様に、行の出力が両方とも一致するために3回取得される理由を説明します。*foo$'foo\n\n\n\n*'?x\x"\\x"\xls*?x

bash 5.0では、次のようになります。

bash-5.0$ arr=( $(ls) )
bash-5.0$ typeset -p arr
declare -a arr=([0]="aX" [1]="foo" [2]="aX" [3]=$'foo\n\n\n\n*' [4]="?x" [5]="\\x" [6]="x" [7]="?x" [8]="\\x" [9]="x" [10]="x")

このバージョンでは、バックスラッシュが2回だけ3回、後にワイルドカードが来なくてもバックスラッシュはワイルドカードなので、ワイルド\xカードと一致します。x\xx

その後shopt nocaseglob私達は次を得ます:

bash-5.1$ shopt -s nocaseglob
bash-5.1$ arr=( $(ls) )
bash-5.1$ typeset -p arr
declare -a arr=([0]="aX" [1]="foo" [2]="aX" [3]=$'foo\n\n\n\n*' [4]="?x" [5]="\\x" [6]="x" [7]="aX" [8]="?x" [9]="\\x" [10]="\\x" [11]="x")

aXやはり一致するので3回表示されます?x

後ろにshopt -s failglob:

bash-5.0$ shopt -s failglob
bash-5.0$ arr=( $(printf '\\z\n') )
bash: no match: \z
bash-5.0$ arr=( $(printf 'WTF\n?') )
bash: no match: WTF?

そしてarr=( $(echo '/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*') )

メモリ不足のため、システムは数分間使用できません。

したがって、要約すると、IFS=$'\n'; arr=( $(cmd) )の出力行は配列cmdに格納されませんが、出力の空でない行拡張によるファイル名はcmdglobパターンとして扱われます。


mapfile誤解を招くようなエイリアスを使用してくださいreadarray

readarray -t arr < <(cmd)
  1. 前述のように、cmdサブシェルで実行されると、標準出力はパイプの書き込み側で開きます。
  2. パイプラインの読み取りの終わりに開いている親シェルのファイル記述子がある場所と同じに拡張されます<(...)/dev/fd/63/proc/self/fd/6363
  3. <リダイレクトの略語を使用すると、0</dev/fd/63 が fd 0 から読み取るために開きます。これは、stdin がreadarrayそのパイプの読み込み終了でもあることを意味します。
  4. readarrayこのパイプから各行を読み取り(cmd書き込み中)、行区切り記号(-t)を捨てて保存します$arr

したがって、最終的に出力にNULがないと$arr仮定すると、空であるかグローバル文字が含まれているかにかかわらず、出力cmdのすべての行の内容が含まれます。cmd

上記の例を見てください:

bash-5.1$ readarray -t arr < <(ls)
bash-5.1$ typeset -p arr
declare -a arr=([0]="aX" [1]="foo" [2]="" [3]="" [4]="" [5]="*" [6]="?x" [7]="\\x" [8]="x")

これは前の出力で見たものと一致しls | catますが、現在の作業ディレクトリにあるファイルのリストを取得することが目的であれば、まだ間違っています。最新バージョン(9.0以降)など、GNUによって実装された一部の拡張機能を使用しないと、出力はls後処理できません。ls--quoting-style=shell-always--zero

bash-5.2$ readarray -td '' arr < <(ls --zero)
bash-5.2$ typeset -p arr
declare -a arr=([0]="aX" [1]=$'foo\n\n\n\n*' [2]="?x" [3]="\\x" [4]="x")

今回はreadarrayNUL制限レコードの内容を。 NUL は変数に保存できないため使用できません。d$arrIFS=$'\0'bashbash

または:

bash-5.1$ eval "arr=( $(ls --quoting-style=shell-always) )"
bash-5.1$ typeset -p arr
declare -a arr=([0]="aX" [1]=$'foo\n\n\n\n*' [2]="?x" [3]="\\x" [4]="x")

とにかく、現在の作業ディレクトリのファイルのリストを配列に配置する正しい方法は次のとおりです。

shopt -s nullglob
arr=( * )

リストをサイズまたは変更時間で並べ替えたい場合にのみ、ls --zerobash glob (zsh とは反対) では実行できない操作です。

良い:

扱いにくい 最新のGNU bash + GNU coreutils
new_to_old=( *.txt(Nom) ) readarray -td '' new_to_old < <(ls -td --zero -- *.txt)
four_largest=( *.txt(NOL[1,4]) ) readarray -td '' four_largest < <(ls -tdrS --zero -- *.txt | head -zn4)

a=($(cmd))との間の別の違いreadarray < <(cmd)は終了状態です。前者はであり、cmd後者はですreadarray。最新バージョンではbashcmdwait "$!"; cmd_status=$?


¹arr=( ... )構文はzshに由来しました(配列は1996年2.0までbashには表示されませんでした)。ただし、zshでは、コマンドの置き換えは末尾の改行も削除し、$IFS-strippingの影響を受けますが、NULを無視しません(NULはデフォルトでも$IFS)、他のBourneに似たシェルなどのワイルドカードの影響を受けないため、より安全なシェルになります。 。

²別名追加モードはreadarrayありませんが、最新バージョンでは、次のように、最初の要素のmapfileインデックスと要素の保存を開始する場所がわかります。-Obashで最後の要素のインデックスを見つけるのは非常に難しいです(配列はkshほど希薄です!)。ここでは、cmdtoの出力行を追加するのではなく、with$arr一時配列として行を読み取ってwithreadarray -r tmp < <(cmd)に要素を追加することもできます。また、変数がスカラーまたは結合型として宣言されている場合、これらのメソッド間の動作が異なることに注意してください。$arrarr+=( "${tmp[@]}" )arr

おすすめ記事