コマンドの結果を変更するときは、「パスは式の前になければなりません」を探します。

コマンドの結果を変更するときは、「パスは式の前になければなりません」を探します。

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*'-pruneIFSFiles*'

出力に表示される引用符は、set -xBashが出力をシェルへの入力として表示できるようにするために存在します。エスケープされた引用は、で印刷された引用ですprintf

!ここでは、 、を-pathの他のパラメータとして生成する必要があります。 1つの方法は、パラメータを配列として収集することです。./My Files*-prunefind

#!/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 を呼び出す必要があります。

  1. find
  2. .
  3. (
  4. -path
  5. ./my dir
  6. -o
  7. -path
  8. ./ my \[other\] dir\?/sub dir[そしてエスケープされます。そうでなければsによって特別に処理され?ます。ほとんどの実装ではエスケープは必要ありませんが、害はありません)find-path]find
  9. )
  10. -prune#上記で一致するものをトリミングします。
  11. -o#他のもの:
  12. -type
  13. f#「一般」タイプのファイル
  14. -print

したがって、各行には次のものが必要です。

  • 末尾の削除/
  • 特殊文字エスケープ-path:\?*[]
  • プレフィックス./
  • -path1行前に追加

次に、それぞれの間に引数を配置し、-o結果行をリストとして収集しますfind

readarray -tこの分割を実行するには、bashシェルでzshおよびパラメータ拡張フラグを使用して実行できますf。二重引用符を省略し、以前と同じように分割+globを使用できますが、最初は改行文字にのみ設定し、不要なglob部分を無効にする必要があります$(...)(zshでは実行されません)。$IFSset -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

zshbglob演算子をエスケープするためのパラメータ拡張フラグがあり、そのシェルのNullglob修飾子と一緒に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-pathPfind$@[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は単純なテストに縮小されました-fwantedクリーンアップされたディレクトリやファイルはまったく表示されません。

実行例:

以下を含む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

ブラックリストが長い場合、それほど効率的ではありませんが、ほとんどのファイル名には機能します。 (最大:たとえば、改行を含むファイル名はまだ問題です。 )

関連情報