
find
ファイルからディレクトリリストを除外するために実行しようとしています。このファイルは他のコマンドでも使用されるため、ファイル形式に固執しました。つまり、各行には「as is」というディレクトリ名とスラッシュが含まれています。
My Files/
xargsのようなさまざまなアプローチを試しましたが、うまく機能しませんでしたが、ついにブラックリストを前処理して次のパラメータを生成することにしました。
find . -type f $(cat .blacklist.txt | while read -r line; do printf "! -path './%s*' -prune " "$line"; done)
ただし、ブラックリストでスペースを含むディレクトリ名が見つかると、次のエラーが発生します。
find: paths must precede expression: `<Last Part Of Name>/*''
何が起こっているのかを調べようとしましたがset -x
、ディレクトリ名の一部が個別に参照されているようです。たとえば、リストに「My Files」というディレクトリ名があるとします。set -x
ファイルに次の特定の行の出力を表示できます。
++ printf '! -path '\''./%s*'\'' -prune ' 'My Files/'
これまではとても良いと思います。ただし、最後に組み立てられたコマンドで出力を見ると、次のようになります。
'!' -path ''\''./My' 'Files/*'\''' -prune
問題は明らかです。ディレクトリ名を二つの部分に分けようと頑張っているということです!しかし、なぜこのようなことをするのか理解できません。私は次のようないくつかのバリエーションを試しました。
printf "! -path './%s*' -prune " "$line"
私はどういうわけかprintfから出てくるときにパスの結果出力が引用されるようにしなければならないと思います。しかし、私はそれらをすべて試しましたが、そのうちの何も動作しません。
printf "! -path \"./%s*\" -prune " "$line"
printf "! -path "'./$line*'" -prune "
printf "! -path '"./$line*"' -prune "
ただし、どちらもディレクトリ名の個々の単語が分割されるのを防ぎません。
私もこれを試しました:
printf "! -path ./%s* -prune " "$line"
これにより、ディレクトリ名が分割されるのを防ぐことができますが、参照されなくなり、そのパスの下のすべてのサブディレクトリに展開されます。これは間違っており、今では必要なサブディレクトリが1つしかないため、コマンドも中断されます。
%s
別のパラメータを使用してパラメータprintf
を%s
直接使用しても問題にならないようです。$line
答え1
そして、ディレクトリ名の一部が個別に参照されるようです。
私はそう言わないでしょう。! -path './My Files*' -prune
たとえば、コマンドオーバーライドで印刷すると、トークン化ステップで引用符などの構文を処理せずに空白文字(または既に知っているすべての設定)のみが表示されるため、、、、!
で-path
トークン'./My
化されます。これはglobの役割もしますが、一重引用符で終わるファイル名がない可能性があります。Files*'
-prune
IFS
Files*'
出力に表示される引用符は、set -x
Bashが出力をシェルへの入力として表示できるようにするために存在します。エスケープされた引用は、で印刷された引用ですprintf
。
!
ここでは、 、を-path
の他のパラメータとして生成する必要があります。 1つの方法は、パラメータを配列として収集することです。./My Files*
-prune
find
#!/bin/bash
args=()
filename='./My Files'
args+=( ! -path "$filename*" -prune )
# etc.
find . -type f "${args[@]}"
しかし、本当に必要かもしれません。
find . -type f ! -path './somedir/*' ! -path './otherdir/*'
(いいえ-prune
、リストされているディレクトリを含むツリー全体をナビゲートし、その中のすべてのエントリを無視します。)またはStéphane Chazelasが答えに示すように
find . \( -path ./somedir -o -path ./otherdir \) -prune -o -type f -print
(リストされたディレクトリに入ることもできません。()
シェルをエスケープする必要があります。)
そして除外された名前にパターン一致find
に一意の文字(*?[
)を含めることができる場合は、バックスラッシュとバックスラッシュを使用してこれらの文字をエスケープする必要があります。
古いパターンを使用してエスケープを無視する場合:
#!/bin/bash
args=(-type f)
while read -r filename; do
args+=( ! -path "./$filename/*" )
done < excluded.txt
find . "${args[@]}"
より良いアプローチは、次のように少し複雑です。
#!/bin/bash
args=()
first=1
while read -r escaped; do
if [ "$first" != 1 ]; then
args+=( -o )
fi
args+=( -path "./$escaped" )
first=0
done < <(sed -e 's/[[?*\]/\\&/g' < excluded.txt)
find . \( "${args[@]}" \) -prune -o -type f -print
(ここでもプロセス置換(<(cmd...)
)を使用してエスケープのためにsedを介してファイル名のリストをパイプします)
配列を作成する際の重要な点は、引用符を追加せずにコマンドに入力したのと同じように、パラメータを割り当てに入力することです。その後、配列を扱うときは構文に集中し、引用符に"${args[@]}"
注意する必要があります。
答え2
/
通常のファイルを見つけることが目的の場合は、パスとディレクトリ内のファイルを一覧表示する各行をスキップしてください.blacklist.txt
。これにより、ファイルに次のものが含まれます。
my dir/
my [other] dir?/sub dir/
たとえば、次のパラメーターを使用して find を呼び出す必要があります。
find
.
(
-path
./my dir
-o
-path
./ my \[other\] dir\?/sub dir
([
そしてエスケープされます。そうでなければsによって特別に処理され?
ます。ほとんどの実装ではエスケープは必要ありませんが、害はありません)find
-path
]
find
)
-prune
#上記で一致するものをトリミングします。-o
#他のもの:-type
f
#「一般」タイプのファイル-print
したがって、各行には次のものが必要です。
- 末尾の削除
/
- 特殊文字エスケープ
-path
:\?*[]
- プレフィックス
./
-path
1行前に追加
次に、それぞれの間に引数を配置し、-o
結果行をリストとして収集しますfind
。
readarray -t
この分割を実行するには、bashシェルでzsh
およびパラメータ拡張フラグを使用して実行できますf
。二重引用符を省略し、以前と同じように分割+globを使用できますが、最初は改行文字にのみ設定し、不要なglob部分を無効にする必要があります$(...)
(zshでは実行されません)。$IFS
set -o noglob
存在するbash
:
readarray -t args < <(
sed 's|/$||
s|[][\\?*]|\\&|g
s|^|./|
1!i\
-o
i\
-path' .blacklist.txt
)
find . '(' "${args[@]}" ')' -prune -o -type f -print
zshから:
find . '(' ${(f)"$(
sed 's|/$||
s/[][\\?*]/\\&/g
s|^|./|
1!i\
-o
i\
-path' .blacklist.txt)"} ')' -prune -o -type f -print
zsh
b
glob演算子をエスケープするためのパラメータ拡張フラグがあり、そのシェルのN
ullglob修飾子と一緒にglobbingを使用して、ブラックリストを実際に存在するディレクトリに減らしてパラメータをP
再リンクできます。-o
-path
() {
find . '(' $@[2,-1] ')' -prune -o -type f -print
} ./${(f)^"$(<.blacklist.txt)"}(Ne['REPLY=${(b)REPLY%/}']P[-o]P[-path])
ここでは、glob修飾子を使用して合計を各ファイルの前に追加し、P
リストを匿名関数に渡し、最後から2番目の引数を()に渡します。結果から自分を省略したい場合は、次のようにすることもできます。-o
-path
P
find
$@[2,-1]
.blacklist.txt
find . '(' -path ./.blacklist.txt ./${(f)^"$(<.blacklist.txt)"}(Ne['REPLY=${(b)REPLY%/}']P[-o]P[-path]) ')' -prune -o -type f -print
GNUまたは互換バージョンがある場合は、エスケープ、末尾の削除、またはプレフィックスの削除が不要なfind
代替方法は、次のものを使用することです。-path
/
./
-samefile
find . '(' -samefile .blacklist.txt ${(f)^"$(<.blacklist.txt)"}(NP[-o]P[-path]) ')' -prune -o -type f -print
答え3
perl
私はこれをやったでしょう。ファイル::検索find
多くの述語を使用して長いコマンドを書くのは、単純な手続き型スクリプトを書くよりもPITAだからです。
#!/usr/bin/perl
use strict;
use File::Find;
use autodie qw(open);
# load the blacklist into an array
my @blacklist;
open(my $BL,"<","blacklist.txt");
while(<$BL>) {
# Assume one directory per line. Paths are treated
# as regular expressions, not as literal strings.
chomp;
push @blacklist, $_;
};
close($BL);
# generate a regexp to match all the entries in the array
our $blacklist_re = "^(?:" . join("|",@blacklist) . ")";
#print "$blacklist_re\n";
find { wanted => \&wanted, preprocess => \&prune }, '.';
sub wanted { -f && print "$File::Find::name\n" };
sub prune { return grep { ! -d || ! m/$blacklist_re/ } @_ };
2023年4月24日更新 - このバージョンでは、事前処理サブルーチンを使用して、不要なディレクトリがFile::Find
そのディレクトリに含まれないように実際にクリーンアップします。
prune
前処理サブルーチンは、ディレクトリが入力されるたびに呼び出されますfind
(この場合、関数に引数find
として与えられたすべてのディレクトリを含む.
)。
ディレクトリ(ディレクトリ、ソケット、デバイスノードなどを含む)のファイル名は配列に渡され、ディレクトリではないかブラックリスト正規表現prune
と一致しないファイル名の配列を返します。これはPerlの組み込みgrep
機能を使用します。これは外部コマンドではなく、/bin/grep
ファイルではなくリスト/配列で機能します。望むよりperldoc -f grep
。
クリーンアップによって返された配列にないファイル名は追加の処理から除外されるため、find
サブルーチンwanted
は単純なテストに縮小されました-f
。wanted
クリーンアップされたディレクトリやファイルはまったく表示されません。
実行例:
以下を含むblacklist.txt
ファイル:
foo bar
bar baz
次のディレクトリといくつかのダミーファイルを作成します。
mkdir 'foo bar' 'bar baz'
touch 'bar baz/a.txt' 'foo bar/b.txt' 'foo bar/c.txt'
touch d.txt
ディレクトリ構造は次のとおりです。
.
├── bar baz
│ └── a.txt
├── blacklist.txt
├── d.txt
├── ff.pl
├── foo bar
├── b.txt
└── c.txt
スクリプトを実行すると(たとえば、別の名前で保存してff.pl
実行可能に設定chmod +x ff.pl
)、次の出力が生成されます。
$ ./ff.pl
./ff.pl
./blacklist.txt
./d.txt
つまり、blacklist.txtのディレクトリは出力から除外されます。
答え4
私ができること(家庭bash
とGNU find
):
find . -exec fgrep -qx \{} $(sed 's#/$##' .blacklist.txt) \; -prune -o -type f -print
ブラックリストが長い場合、それほど効率的ではありませんが、ほとんどのファイル名には機能します。 (最大:たとえば、改行を含むファイル名はまだ問題です。 )