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")
mapfile
1つの利点は、単語の分割について心配する必要がないことだと思います。
あるいは、単語の分離を防ぐように設定することもできますが、IFS=$'\n'
この例では心配する必要はありません。
2番目の例は、書くのが簡単なようです。何か抜けましたか?
ベストアンサー1
後で彼らはまったく同じではありませんIFS=$'\n'
。
特にbashでは(構文はzshで借りましたが):
arr=( $(cmd) )
(arr+=( $(cmd) )
以下の目的で使用されます。追加したがって、配列の要素はkeys=( -1 "${!arr[@]}" ); readarray -tO "$(( ${keys[@]: -1} + 1))" arr < <(cmd)
²と比較されます。
する:
cmd
パイプの書き込みの終わりに、標準出力が開いたサブシェルで実行されます。- その間、親シェルプロセスはパイプのもう一方の端からデータを読み込み、次のことを行います。
- NUL文字と末尾の改行を削除
- 特殊変数の内容に基づいて
$IFS
結果文字列を分割します。$IFS
空白文字(改行文字など)の場合、動作はより複雑です。- 先行項目と末尾項目が削除されます。 (改行の場合、上記のようにコマンド置換によって削除されました.)
- 1つ以上のシーケンスが考慮されます。一つスプリッタ。たとえば、出力はとの
printf '\n\n\na\n\n\nb\n\n\n'
2つの要素に分けられます。a
b
noglob
これにより、これらの各単語はファイル名の生成(別名ワイルドカード)の対象となり、その動作は、、、、、、、などのさまざまnullglob
なオプションの影響を受けます。これは、、、およびいくつかのbashバージョンを含む単語に対して機能し、有効になっている場合はそれ以上のバージョンも使用できます。failglob
extglob
globasciiranges
glabstar
nocaseglob
*
?
[
\
extglob
- 次に、結果の単語が配列
$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"
\x
ls
*
?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
\x
x
その後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
に格納されませんが、出力の空でない行拡張によるファイル名はcmd
globパターンとして扱われます。
mapfile
誤解を招くようなエイリアスを使用してくださいreadarray
。
readarray -t arr < <(cmd)
- 前述のように、
cmd
サブシェルで実行されると、標準出力はパイプの書き込み側で開きます。 - パイプラインの読み取りの終わりに開いている親シェルのファイル記述子がある場所と同じに拡張されます
<(...)
。/dev/fd/63
/proc/self/fd/63
63
<
リダイレクトの略語を使用すると、0<
/dev/fd/63 が fd 0 から読み取るために開きます。これは、stdin がreadarray
そのパイプの読み込み終了でもあることを意味します。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")
今回はreadarray
NUL制限レコードの内容を。 NUL は変数に保存できないため使用できません。d
$arr
IFS=$'\0'
bash
bash
または:
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 --zero
bash 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
。最新バージョンではbash
。cmd
wait "$!"; cmd_status=$?
¹arr=( ... )
構文はzshに由来しました(配列は1996年2.0までbashには表示されませんでした)。ただし、zshでは、コマンドの置き換えは末尾の改行も削除し、$IFS
-strippingの影響を受けますが、NULを無視しません(NULはデフォルトでも$IFS
)、他のBourneに似たシェルなどのワイルドカードの影響を受けないため、より安全なシェルになります。 。
²別名追加モードはreadarray
ありませんが、最新バージョンでは、次のように、最初の要素のmapfile
インデックスと要素の保存を開始する場所がわかります。-O
bashで最後の要素のインデックスを見つけるのは非常に難しいです(配列はkshほど希薄です!)。ここでは、cmd
toの出力行を追加するのではなく、with$arr
一時配列として行を読み取ってwithreadarray -r tmp < <(cmd)
に要素を追加することもできます。また、変数がスカラーまたは結合型として宣言されている場合、これらのメソッド間の動作が異なることに注意してください。$arr
arr+=( "${tmp[@]}" )
arr