web-dev-qa-db-ja.com

Bashで配列をダブルクォートすることとダブルクォートしないことの違いは何ですか?

シェルスクリプトでエラーを追跡しているときに、このコードスニペットで次の動作が見つかりました。

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つの部分があります。

  1. 上記のように配列を二重引用符で囲む以外に、Wordの分割を防ぐ「ベストプラクティス」の方法はありますか?
  2. 単一引用符は配列のどこから来ていますか? 編集:一重引用符はありません。一重引用符は、開くことができないファイルの名前を示すエラーです。

また、好奇心から、なぜecho ${filelist[0]}追加の改行は含まないがecho "${filelist[0]}"しますか?

4
dcwaters

配列の拡張を引用しても問題はありません。

そしてもちろん、あなたが結果を知って受け入れさえすれば、引用することなく問題はありません。引用符で囲まれていない展開は、分割およびグロビングの対象になります。また、コードでは、${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
7
Isaac

この場合、Word分割について心配していません

まあ、実際には、配列エントリから末尾の改行を削除するに依存しています!

Bashのreadarraymapfile は、デフォルトで区切り文字を残します。マニュアルページやコマンドラインのヘルプには明示的には書かれていないようですが、区切り文字を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と一致します で、これはシェルに依存します。

5
ilkkachu

コードスニペットに基づく問題の一部は、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というファイルがある場合、readarrayfoo\nの値を渡していますが、これはファイルに対応していません。これを引用解除すると、変数の値の一部が誤って破棄されて問題が「修正」されます。

3
DopeGhoti