直接呼び出しとシェル変数の bufferin 検索出力が異なります。

直接呼び出しとシェル変数の bufferin 検索出力が異なります。

コマンドを使用すると奇妙な動作が発生しますfindが、説明が見つかりません。

1行に1つのファイル名を持つ.txtファイルがあり、findコマンドを使用してデータベースからファイルを再帰的に検索しています。次のコマンドを使用する場合:

for filename in `cat filelist.csv`; do
find /location*/time*/ -name *${filename}*txt
done

1行に1つの出力が期待される結果を取得します。ただし、同じコマンドを使用しても出力を変数に設定する場合(最終的にそうする必要があります):

for filename in `cat filelist.csv`; do
out=`find /location*/time*/ -name *${filename}*txt`
echo ${out}
done

findコマンドは、同じ行のフォルダから一致するすべてのファイルを印刷するようです。 2つの質問があります。

  1. この動作の原因は何ですか?
  2. find一致する各ファイル(フォルダに一致するファイルが多い場合でも)を新しい行の変数として出力するにはどうすればよいですか?

乾杯!

答え1

これは、シェルが変数を展開すると、すべての改行文字が「折りたたまれて」スペースに変わるために発生します。したがって、out変数に改行文字が含まれている場合は、${out}その改行文字をすべて空白に変更してください。ただし、"${out}"改行文字は保存されます。

答え2

filelist.csvファイルに正確に一致するものが含まれている場合はfind ... -print0 | grep -z -F -f filelist.csv | xargs -0r...などを使用できますが、そのファイルにリストされているファイル名の一部(ファイル名の前のすべての文字と追加された「.txt」)と一致したいようです。これを行う最も簡単な方法は正規表現を使用することです。

あなたはそれを使用することができますプロセスの交換を読み取ると、部分ファイル名をfilelist.csv適切な正規表現に変換します。filelist.csvgrep

ただし、sedの-iオプションを使用しない限り(この特定の操作では実行しないでください)、この変換は永続的ではなく、元のfilelist.csvファイルには影響せず、入力テキストストリームにのみ影響しますgrep -f

または出力find . -name '*.txt'grep。このように、grepに表示される入力はで終わるファイル名でフィルタリングされるため、正規表現を変更する.txt必要sedはありません。

とにかく、次のようにしてみてください。

まず、この実験のためのいくつかの設定は次のとおりです。

$ cat filelist.csv 
test
foo

$ touch test test.txt foo foo.txt footest footest.txt

$ ls -l
total 4
-rw-r--r-- 1 cas cas 10 Sep  8 04:01 filelist.csv
-rw-r--r-- 1 cas cas  0 Sep  8 04:01 foo
-rw-r--r-- 1 cas cas  0 Sep  8 04:01 footest
-rw-r--r-- 1 cas cas  0 Sep  8 04:01 footest.txt
-rw-r--r-- 1 cas cas  0 Sep  8 04:01 foo.txt
-rw-r--r-- 1 cas cas  0 Sep  8 04:01 test
-rw-r--r-- 1 cas cas  0 Sep  8 04:01 test.txt

次に、bash組み込み関数を使用してfindとgrepの出力で呼び出された配列をmapfile埋めます。out

$ mapfile -d '' out < \
    <(find . -type f -print0 |
        grep -z -f <(sed -e 's/^\(.*\)/.*\1\.txt$/' filelist.csv)

または:

$ mapfile -d '' out < \
    <(find . -type f -name '*.txt' -print0 |
        grep -z -f filelist.csv )

結果:

$ declare -p out
declare -a out=([0]="./foo.txt" [1]="./footest.txt" [2]="./test.txt")

$ ls -l "${out[@]}"
-rw-r--r-- 1 cas cas 0 Sep  8 04:01 ./footest.txt
-rw-r--r-- 1 cas cas 0 Sep  8 04:01 ./foo.txt
-rw-r--r-- 1 cas cas 0 Sep  8 04:01 ./test.txt

配列には、および、outのみが含まれていますが、foo.txtfootest.txttest.txtいいえ fooまたはtestまたはfootest

$outただし、次のようにファイル名を繰り返すことができます。

for f in "${out[@]}"; do
  echo "$f"
  do-something-else-with "$f"
done

または、値の代わりに配列のインデックス(0、1、2)を繰り返します。時にはこれがより便利です。たとえば、同じインデックスを持つ複数の配列があり、一部で一緒に使用したい場合です。他の目的でインデックスを使用する必要がある場合:

for i in "$!{out[@]}"; do
   echo "${out[$i]}"
done 

覚えている:

  1. シェルが変数のglobをトークン化または拡張したり、変数のシェルメタ文字(orなど)に対して機能しないようにするには、変数(つまり"$var"単なるtypeではないtype)を二重引用符で囲みます。これは$var;&ほぼいつも。経験則:特定のケースで二重引用符なしで変数を使用する理由がわからない場合は、二重引用符を使用してください。元の質問に対する引用$outや直接的な原因はありません。$filename

  2. ファイル名に空白や改行文字などの迷惑な文字がないと仮定しないでください。これはUnixのファイル名に対して完全に有効な文字なので、スクリプトはそれを処理する必要があります。実は、ただパス/ファイル名に表示できない文字はNULです。

  3. 任意のファイル名または不明なファイル名の間の区切り文字として常にNULを使用してください。これは利用可能な唯一の区切り記号です。どのファイル名。

  4. しかし、多くの例外があります。ほとんどの場合、変数に複数の値を保持させるには、スペースで区切られた文字列または類似の「偽/エミュレート配列」メソッドではなく、配列を使用する必要があります。特に、値がファイル名である場合、またはいずれかの値で区切り文字が有効な文字である場合は、これがさらに重要です。

答え3

txt名前で終わり、中間行に何も含まれているすべてのファイルを見つけるには、シェルfilelist.csvで次の操作を行います。zsh

print -rC1 -- **/*(${(j[|])~${(fb)"$(<filelist.csv)"}}*})*txt(ND)

または、一度に1段階ずつ細分化してください。

csv_contents=$(<filelist.csv)
non_empty_lines_of_csv=(${(f)csv_contents})
lines_with_wildcards_excaped=(${(b)non_empty_lines_of_csv})
ored_patterns=${(j[|])lines_with_wildcards_excaped}
filename_pattern="*($ored_patterns)*txt"

print -rC1 -- **/$~filename_pattern(ND)

以下が含まれている場合filelist.csv

???
foo bar
baz

これにより、次の再帰グローバルが拡張されます。

**/*(\?\?\?|foo bar|baz)*txt(ND)

あなたの質問について:

for filename in `cat filelist.csv`; do
out=`find /location*/time*/ -name *${filename}*txt`
echo ${out}
done
  • for var in `cmd`出力行を繰り返さずに出力を取得し、cmd末尾の改行文字を削除し、それに対して分割+グローブを実行します(zshでのみ分割)。文字を分割し(デフォルトは空白、タブ、改行)、展開します。結果単語のワイルドカード文字。したがって、出力の場合は、beingで一度繰り返すことなく、現在のディレクトリから。で始まるファイル名を繰り返し、から始まるファイル名を繰り返します。`cmd`cmd$IFScmda* b*vara* b*ab
  • では、これらの合計が引用されていないので、-name *${filename}*.txt再びシェルワイルドカードを使用します。したがって、その場合、シェルは一致するファイルのリストに展開されます。現在のディレクトリに呼び出されたファイルがある場合、そのファイルはになります。また、で変更してもで終わるファイル名はすべて返されます。*${filename}${filename}abc*abc*txtxabcytxt-name xabcytxt-name "*${filename}*txt"$filename*-name '***txt'findtxt*
  • echo ${out}二重$out引用符がない場合は、分割+globを意味します(zshを除く)。また、echoさまざまな実装のさまざまな特殊なケースでは望みどおりに実行されないため、任意のデータを出力することを避ける必要があります。

また見なさい:

関連情報