シェルファイル名拡張子はリストの(*)エントリをどのように区別しますか?

シェルファイル名拡張子はリストの(*)エントリをどのように区別しますか?

私はまだシェル拡張を完全に理解していません(いつか理解できることを願っています)…
このコメントを見ました。スーパーユーザーの問題さて、まだ道沿いに駐車されているようです…

シェルなしでLinuxを使用することは、市内交通でフェラーリを時速50kmで運転するのと同じです。すべての喜びが消えるでしょう…

次の例がわかりません。 2番目の例「配列項目の数:」が最初の例と異なる階層またはその他の要因は何ですか?

シェルに「空間」が導入された場合はどうなりますか?それともecho空白を導入し、シェルは(おそらく)\ 0を使用しますか?

#!/bin/bash
# Make a couple of files whose names contain a space.
junkd=$HOME/junkd
mkdir $junkd # || exit 1
cd $junkd
touch f\ {1..2}
#
echo -n * |xxd         # This shows a space between the two names.
names=$(echo -n * )
echo -n "$names" |xxd  # This shows a space between the two names.
#
# So far, it seems that the shell is inserting a space between each filename.
#
array=( $names )
echo "array item count: ${#array[@]}" 
# 4 items... This shows that a space is the delimiter char ....
#
array=( * )
echo "array item count: ${#array[@]}" 
# 2 items... What happened to the shell introduced space?
#

答え1

シェルコマンド(より正確には「簡単なコマンド」)は単語リストで構成されています。各単語は任意の文字列にすることができます。シェル語にはスペースと句読点を含めることができます。

を実行すると、echo -n *シェルはパス名拡張(ファイル名の生成またはワイルドカードとも呼ばれる)を実行し、*それを一致するファイル名のリストに置き換えます。したがって、拡張後、このコマンドは、echo4-nf 1の単語で構成されますf 2。このコマンドはecho2つの引数で実行され、引数の間にスペースを含む引数を出力します(-nオプションによって終了する改行文字はありません)。したがって、出力はf 1 f 2演習:2つの連続したスペースで構成される名前を持つ別のファイルを作成して実行したecho -n *後、出力を理解していることを確認してください。

これを実行すると、names=$(echo -n * )コマンドの出力がnames変数に保存されます。ここで行はと同じですnames='f 1 f 2'

今行きますarray=( $names )。これは配列割り当てですが、この場合の拡張には影響しません。引用符なしの変数拡張なので、$names単語を分割してパス名拡張を実行します。トークン化とは、変数の値(文字列)が空白の各シーケンスのさまざまな部分に分割されることを意味します(IFS正確なルールはシェル文書を検索してください)。 0個、1個以上の単語で終わることができます。ここで文字列は、および4つの単語fに分割され1ます。したがって、配列には4つの要素が含まれます(各要素は1文字の単語です)。f2演習:名前に2つの連続したスペースを含む追加ファイルの配列の正確な内容は何ですか?

次に、あなたはそれを試しましたarray=( * )。これには一般的な拡張が適用される配列の単語があり、最後の単語はパス名拡張です。一致するファイルが2つあるため、配列には各ファイルの名前であるというf 12つの単語が含まれますf 2

シェルプログラミングの実践に関するこの分析からどのようなアドバイスを得ることができますか?まず、一般的なシェルプログラミングの原則があります。特に理由がない限り、変数の拡張子の周りに二重引用符を付けてください。その後、リストを文字列変数に保存しないでください。ファイル名のリストを保存するには、配列に直接置きます。

files=(*)
ls -l "${files[@]}"

追加の練習:単一のアスタリスク(touch '*')で名前付きファイルを作成し、このコマンドを再実行します。出力を理解していますか?

また、zshは変数拡張のためにトークン化またはパス名拡張を実行しません。これはプログラミングをよりスマートにします。

答え2

~から男乱交

拡張 拡張は単語に分割され、コマンドラインから行われます。実行される拡張タイプには、中括弧拡張、チルダ拡張、パラメータおよび変数拡張、コマンド置換、算術拡張、トークン化、パス名拡張などの7種類があります。拡張順序は、中かっこ拡張、チルダ拡張、パラメータ、変数と算術拡張、コマンド置換(左から右へ)、トークン化、パス名拡張です。

array=( $names )

これが4つの項目を提供する理由は、引用符のない$names引数が次のようにさらに影響を受けるためです。噴射IFSデフォルトの内部フィールド区切り記号に基づいています<space><tab><newline>。単語分割を無効にするために引用をすると"$names"、valueを持つ配列要素が得られますf 1 f 2。これはまた望むものではありません。

array=( * )

一方、上記は以下の場合にのみ適用されます。パス名拡張これは最後に実行された拡張です。明らかにするいいえ単語分割を実行して必要な2つの要素を取得します。

これを機能させるには、array=( $names )ファイル名に含まれていない空白以外の文字を使用してファイル名を何らかの形で区切る必要があります。次に、IFSをその文字に設定する必要があります。

$ names=$(echo f* | sed "s/ /#/2")
$ echo $names
f 1#f 2
$ IFS='#' array=( $names )
$ echo ${#array[@]}
2
$ echo ${array[0]}
f 1

よりエレガントなアプローチは、NULバイトを\0ファイル名の区切り文字として使用することです。なぜなら、NULバイトはファイル名の一部にならないことが保証されるからです。これを達成するには、フラグ付きfindのコマンド-print0readNULで区切られた組み込みコマンドを使用する必要があります。また、スペースに対してトークン化を行わないようにIFSを消去する必要があります。

#!/bin/bash

unset array

while IFS= read -r -d $'\0' name; do
  array+=( "$name" )
done < <(find . -type f -name "f*" -print0 )

修正する

拡張は単語に分割され、コマンドラインから行われます。

ポイントをさらに説明するために、人々が上記の引用によってどれほど混乱しているかを想像することができます。噴射最後から2番目に発生する拡張です。

私の考えでは、引用文を表現するより良い方法は次のとおりです。

分割後のコマンドラインからの拡張 議論

シェルでは、引数の分割は次のようになります。いつもスペースが完成すると、これらのパラメータはさらに拡張されます。パラメーターにスペースを含めるには、以下を使用する必要があります。引用するまたは逃げるIFSパラメータ分割は改善されず、単語分割のみが改善されます。

次の例を考えてみましょう。

$ touch f{1,2}; IFS="#"; rm f1#f2
rm: cannot remove `f1#f2': No such file or directory

に設定しても、シェルがまだ1つの引数しか見えないという事実は変わらずIFS、さまざまな拡張によってさらに影響を受けます。#f1#f2

自分で試してみてください。バッシュFAQまだしていない場合。特に、次の2つの補足項目を読むことを強くお勧めします。

  1. 議論
  2. 噴射

関連情報