シェルスクリプトでエラーを追跡しているときに、このコードスニペットで次の動作が見つかりました。
declare -a filelist
readarray filelist < <(ls -A)
readonly filelist
for file in "${filelist[@]}"; do
sha256sum ${filelist[$file]} | head -c 64
done
配列filelist
が二重引用符で囲まれていない場合、コマンドは成功します。 ShellCheckを使用してコーディングを改善しようとしています。
二重引用符でグロブとワード分割を防止します。
この場合はWordの分割について心配していませんが、他の多くの場合はそうです。そのため、コードの一貫性を保つようにしています。ただし、配列を二重引用符で囲むと、コマンドが失敗します。コードを単一の要素に単純化すると、次のようになります。
bash-5.0# sha256sum ${filelist[0]} | head -c 64
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
bash-5.0# sha256sum "${filelist[0]}" | head -c 64
sha256sum: can't open 'file1
': No such file or directory
この例ではWordの分割は問題ではないので、私は明らかに...二重引用符は使用できません。でも将来はそうなるかもしれないので投稿したかったのです。
私の質問には2つの部分があります。
また、好奇心から、なぜecho ${filelist[0]}
追加の改行は含まないがecho "${filelist[0]}"
しますか?
配列の拡張を引用しても問題はありません。
そしてもちろん、あなたが結果を知って受け入れさえすれば、引用することなく問題はありません。引用符で囲まれていない展開は、分割およびグロビングの対象になります。また、コードでは、${filelist[…]}
はIFS文字の削除(および文字列に<space>
、<tab>
、または<newline>
が含まれている場合は分割)の対象になります。
これは、引用符で囲まれていない展開で行うことです。末尾の<newline>
を削除します。
何createsこの問題は、各配列要素から末尾の区切り文字を削除せずにreadarray
を使用していることです。
これを行うと、エラーメッセージに反映される末尾の<newline>
が保持されます。
あなたが使うことができたのは:
readarray -t filelist < <(ls -A)
-t
オプションは、各ファイル名の末尾の改行をすべて削除します。
-t読み取られた各行から末尾のdelim(デフォルトの改行)を削除します。
ただし、コードにはいくつかの問題があります。
配列filelist
を宣言したり空にしたりする必要はありません。これは、デフォルトでreadarrayによって行われます。他のいくつかのケースではそれを行う必要があります。
ls
の出力を解析する必要はありません。実際、これは悪い考えです。配列内のファイルのリストを取得する最も簡単な方法は、次のとおりです。
filelist=( ./* )
そして、それをさらに良くするために、ディレクトリを避けるのは良い考えです:
for file in ./*; do
[[ -f $file ]] && filelist+=( "$file" )
done
ループでは、var $file
の値を使用する必要があります。
for file in "${filelist[@]}"; do
sha256sum "$file" | head -c 64
done
for file in "${!filelist[@]}"; do
を使用しない限り、配列のkeysがリストされます。
リスト全体は、one sha256sumを呼び出すだけで処理できます。
sha256sum "${filelist[@]}" | cut -c -64
改善されたスクリプトは次のとおりです。
filelist=() # declare filelist as an array and empty it.
for file in ./*; do
if [[ -f $file ]]; then
filelist+=( "$file" )
fi
done
declare -r filelist # declare filelist as readonly.
sha256sum "${filelist[@]}" | cut -c -64
この場合、Word分割について心配していません
まあ、実際には、配列エントリから末尾の改行を削除するに依存しています!
Bashのreadarray
(mapfile
) は、デフォルトで区切り文字を残します。マニュアルページやコマンドラインのヘルプには明示的には書かれていないようですが、区切り文字をremoveするオプションがあるため、デフォルトでは削除されません。
-t Remove a trailing delim (default newline) from each line read.
したがって、配列の実際の文字列はfile1[newline]
です。
引用符がないと、単語分割により末尾の空白が削除され、改行が修正されます。ただし、ファイル名にスペースが含まれている場合、通常のように、Word分割はそれらを台無しにします。配列を二重引用符で囲むと、それを防ぐことができます。最初の質問に答えるために、ベストプラクティスは二重引用符です。ここには不要な余分な改行があります。
(配列または$@
を二重引用符で囲むと、二重引用符で囲まれた文字列が複数の単語になり、配列要素ごとに1つになります)。
また、${filelist[$file]}
コマンドラインにsha256sum
があります。これは機能しません。file
には、インデックスではなく、配列から受け取った値がすでに含まれています。
最小限の変更として、これはうまくいくかもしれません:
declare -a filelist
readarray -t filelist < <(ls -A)
readonly filelist
for file in "${filelist[@]}"; do
sha256sum "$file" | head -c 64
done
(明示的なdeclare
も実際には必要ないと思います。)
上記の問題はls
自体には関係ありません。ファイルにファイル名が1行に1つ保存されている場合、同じ問題が発生します。 readarray
/mapfile
を使用して、-t
オプションを使用せずにそれらを読み取りました。 (または、find
の出力を読み取った場合、代わりにfind -exec
を使用できる場合があります。)
もちろん、これはls
の無用な使用であり、一部のバージョンのls
は、出力時にファイル名を壊す可能性があります。 (GNU lsはパイプに出力するときにそれを行います)とは思いません。)
バッシュでは、代わりに配列をグロブで埋めることができます:
shopt -s dotglob
filelist=(*)
for file in *; do ...
または、配列に格納せずにグロブでループを実行するだけです:
shopt -s dotglob
for file in *; do ...
必要なことに注意してください shopt -s dotglob
を取得するには*
を取得するにはdotfilesと一致します で、これはシェルに依存します。
コードスニペットに基づく問題の一部は、ls
の出力を解析していることである可能性があります。これは危険で無数の問題を抱えており、回避するのが最善です。
のではなく
declare -a filelist
readarray filelist < <(ls -A)
readonly filelist
for file in "${filelist[@]}"; do
以下を行う方がはるかに簡単です(そして安全です!)。
for file in *; do
この場合:
for file in *; do
sha256sum "${file}" | head -c 64
done
readarray
を呼び出すと、改行を含めて、渡されたリテラルデータを保持するのにも役立ちます。したがって、引用符で囲まれた値をエコーすると、改行が保持されます。引用しないと、シェルは無視するためにトークン間の空白としてそれを消費します。これがsha256sum
が失敗する理由でもあります。 foo
というファイルがある場合、readarray
はfoo\n
の値を渡していますが、これはファイルに対応していません。これを引用解除すると、変数の値の一部が誤って破棄されて問題が「修正」されます。