ベース名が親ディレクトリの名前である特定の拡張子を持つすべてのファイルを検索します


9

ベース名がファイルの親ディレクトリの名前と一致*.pdfするディレクトリ内のすべてのファイルを再帰的に検索したいと思い~/fooます。

たとえば、ディレクトリ構造~/fooが次のようになっているとします。

foo
├── dir1
│   ├── dir1.pdf
│   └── dir1.txt
├── dir2
│   ├── dir2.tex
│   └── spam
│       └── spam.pdf
└── dir3
    ├── dir3.pdf
    └── eggs
        └── eggs.pdf

希望するコマンドを実行すると戻ります

~/foo/dir1/dir1.pdf
~/foo/dir2/spam/spam.pdf
~/foo/dir3/dir3.pdf
~/foo/dir3/eggs/eggs.pdf

findまたは他のコアユーティリティを使用してこれは可能ですか?これは-regexオプションを使用して実行できるとfind思いますが、正しいパターンの書き方がわかりません。


はい、私は今例を模擬します。
ブライアンフィッツパトリック

1
@Inian例を追加しました。これは役に立ちますか?
ブライアンフィッツパトリック

回答:


16

GNUの場合find

find . -regextype egrep -regex '.*/([^/]+)/\1\.pdf'
  • -regextype egrep egrepスタイルの正規表現を使用します。
  • .*/ 祖父母の監督に一致します。
  • ([^/]+)/ グループ内の親ディレクトリと一致します。
  • \1\.pdfbackreference親ディレクトリとしてファイル名を一致させるために使用します。

更新

一人(私にとっては一人)はそれ.*が十分に貪欲だと思うかもしれません、それ/は親のマッチングから除外する必要はありません:

find . -regextype egrep -regex '.*/(.+)/\1\.pdf'

上記のコマンドはmathchesであるため、うまく機能しません./a/b/a/b.pdf

  • .*/ マッチ ./
  • (.+)/ マッチ a/b/
  • \1.pdf マッチ a/b.pdf

とてもかっこいい。これをうまく正規表現できればいいのに。
ブライアンフィッツパトリック

またはfind . -regex '.*/\([^/]*\)/\1\.pdf'、BSDでも動作しfindます。
ステファンChazelas

7

find .. -exec sh -c ''シェルの構造を使用してベース名と上記の直接パスを一致させる従来のループバリアントは、以下のようになります。

find foo/ -name '*.pdf' -exec sh -c '
    for file; do 
        base="${file##*/}"
        path="${file%/*}"
        if [ "${path##*/}" =  "${base%.*}" ]; then
            printf "%s\n" "$file" 
        fi
    done' sh {} +

個々のパラメーター展開を分解するには

  • fileコマンド.pdfから返されたファイルの完全パスが含まれていますfind
  • "${file##*/}"最後の部分のみ、/つまりファイルのベース名のみを含みます
  • "${file%/*}"最終までのパス、/つまり結果のベース名部分を除くパスが含まれます
  • "${path##*/}"最後の後の部分が含まれている/からpath、変数、ファイルのベース名以上の即時フォルダパス、すなわち
  • "${base%.*}"ベース名の.pdf拡張子が削除された部分が含まれています

したがって、拡張子のないベース名が上記の直近のフォルダの名前と一致する場合は、パスを出力します。


7

イニアンの答えの逆、つまりディレクトリを探し、特定の名前のファイルを保持しているかどうかを確認します。

次の例では、見つかったファイルのディレクトリに対する相対パス名を出力しますfoo

find foo -type d -exec sh -c '
    for dirpath do
        pathname="$dirpath/${dirpath##*/}.pdf"
        if [ -f "$pathname" ]; then
            printf "%s\n" "$pathname"
        fi
    done' sh {} +

${dirpath##*/}は、ディレクトリパスのファイル名の部分に置き換えられ$(basename "$dirpath")ます。

短絡構文が好きな人のために:

find foo -type d -exec sh -c '
    for dirpath do
        pathname="$dirpath/${dirpath##*/}.pdf"
        [ -f "$pathname" ] && printf "%s\n" "$pathname"
    done' sh {} +

この方法で行うことの利点は、ディレクトリよりもPDFファイルの数が多くなる可能性があることです。より小さい数(ディレクトリの数)でクエリを制限すると、関連するテストの数が減ります。

たとえば、1つのディレクトリに100個のPDFファイルが含まれている場合、100個すべてのファイルの名前をディレクトリの名前に対してテストするのではなく、1つのディレクトリのみを検出しようとします。


3

zsh

printf '%s\n' **/*/*.pdf(e@'[[ $REPLY:t = $REPLY:h:t.pdf ]]'@)

**/シンボリックリンクをたどらないが、従うことに注意してください*/


2

これは指定されていませんが、誰かが興味を持っている場合は、正規表現を使用しない解決策です。

私たちはfind . -type fファイルを取得するために使用でき、次に条件付きを利用dirnamebasenameて書き込みます。ユーティリティの動作は次のとおりです。

$ find . -type f
./dir2/spam/spam.pdf
./dir2/dir2.tex
./dir3/dir3.pdf
./dir3/eggs/eggs.pdf
./dir1/dir1.pdf
./dir1/dir1.txt

basename最後のファイル名だけを返します/

$ for file in $(find . -type f); do basename $file; done
spam.pdf
dir2.tex
dir3.pdf
eggs.pdf
dir1.pdf
dir1.txt

dirname最終までの全体のパスを与える/

$ for file in $(find . -type f); do dirname $file; done
./dir2/spam
./dir2
./dir3
./dir3/eggs
./dir1
./dir1

したがって、basename $(dirname $file)ファイルの親ディレクトリを提供します。

$ for file in $(find . -type f); do basename $(dirname $file) ; done
spam
dir2
dir3
eggs
dir1
dir1

解決

上記を組み合わせて条件付きを形成し、その条件がtrueを返す場合に"$(basename $file)" = "$(basename $(dirname $file))".pdfのみ、各結果を出力しfindます。

$ while read file; do if [ "$(basename "$file")" = "$(basename "$(dirname "$file")")".pdf ]; then echo $file; fi done < <(find . -type f)
./dir2/spam/spam.pdf
./dir3/dir3.pdf
./dir3/eggs/eggs.pdf
./dir1/dir1.pdf
./Final Thesis/grits/grits.pdf
./Final Thesis/Final Thesis.pdf

上記の例では、そのケースを処理するために名前にスペースが含まれるディレクトリ/ファイルを追加しています(コメントの@Kusalanandaに感謝)


これは残念ながらFinal Thesis.pdf(スペースを含む)のようなファイル名で壊れます。
Kusalananda

@Kusalananda修正済み。
user1717828

0

私はFindプログラムで、bash globbing、文字列テストの単純なループを毎日受けています。私を不合理だと呼びます。最適ではないかもしれませんが、そのような単純なコードは私にとってはトリックです。読みやすく再利用可能で、満足のいくものです!。したがって、以下の組み合わせを提案させてください。

•bash globstarfor f in ** ; do ... **現在のディレクトリ内のすべてのファイルとすべてのサブフォルダーをループして、現在のセッションのglobstarステータスを確認しますshopt -p globstar。globstarを有効にするには:shopt -s globstar

•「ファイル」ユーティリティPDFのif [[ $(file "$f") =~ pdf ]]; then ... 実際のファイル形式をチェックする-ファイルの拡張子のみをテストするよりも堅牢

•basename、dirname:ファイル名をそのすぐ上のディレクトリの名前と比較します。basenameファイル名を返します- dirnameディレクトリパス全体を返します-2つの関数を組み合わせて、一致するファイルを含む1つのディレクトリのみを返します。それぞれを変数(_mydirおよび_myf)に入れて、文字列のマッチングに=〜を使用して簡単なテストを行います。

1つの微妙さ:ファイル名の「ドット」を削除して、ショートカットが「。」でもある現在のディレクトリにファイル名が一致しないようにします。-私は、変数の上に直接文字列置換を使用_myf${_myf//./}-非常にエレガントではないが、それは動作します。一致した場合、各ファイルのパスが返されます$(pwd)/。出力の前に:を付けると、現在のフォルダの完全パスが返されます。

コード

for f in ** ; do
  if [[ $(file "$f") =~ PDF ]]; then
    _mydir="$(basename $(dirname $f))" ; 
    _myf="$(basename $f)" ; 
    [[ "${_myf//./}" =~ "$_mydir" ]] && echo -e "$(pwd)/$f" ; 
  fi ; 
done
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.