Znajdowanie wszystkich plików z danym rozszerzeniem, których podstawową nazwą jest nazwa katalogu nadrzędnego

9

Chcę rekurencyjnie szukać każdego *.pdfpliku w katalogu, ~/fooktórego podstawowa nazwa odpowiada nazwie katalogu nadrzędnego pliku.

Załóżmy na przykład, że struktura katalogów ~/foowygląda tak

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

Uruchomienie żądanego polecenia powróci

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

Czy jest to możliwe przy użyciu findlub innego podstawowego narzędzia? Zakładam, że jest to wykonalne przy użyciu -regexopcji, findale nie jestem pewien, jak napisać prawidłowy wzór.

Brian Fitzpatrick
źródło
Tak, wykpię teraz przykład.
Brian Fitzpatrick
1
@Inian Dodano przykład. czy to pomaga?
Brian Fitzpatrick

Odpowiedzi:

16

Z GNU find:

find . -regextype egrep -regex '.*/([^/]+)/\1\.pdf'
  • -regextype egrep użyj wyrażenia regularnego w stylu egrep.
  • .*/ dopasuj dyrektywy dziadków.
  • ([^/]+)/ dopasuj katalog nadrzędny w grupie.
  • \1\.pdfsłuży backreferencedo dopasowania nazwy pliku jako nadrzędnego reż.

aktualizacja

Jeden (dla mnie jeden) może uważać, że .*jest wystarczająco chciwy, nie trzeba wykluczać /z dopasowania rodziców:

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

Powyższe polecenie nie będzie działać dobrze, ponieważ jest zgodne ./a/b/a/b.pdf:

  • .*/ mecze ./
  • (.+)/ mecze a/b/
  • \1.pdf mecze a/b.pdf
dedowsdi
źródło
Bardzo fajny. Chciałbym móc tak dobrze regexować.
Brian Fitzpatrick
Lub find . -regex '.*/\([^/]*\)/\1\.pdf'wtedy to nawet działałoby z BSD find.
Stéphane Chazelas
7

Tradycyjny wariant pętli polegający na find .. -exec sh -c ''stosowaniu konstrukcji powłoki w celu dopasowania do nazwy basename i bezpośredniej ścieżki powyżej byłby do wykonania poniżej.

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 {} +

Aby rozbić poszczególne rozszerzenia parametrów

  • filezawiera pełną ścieżkę do .pdfpliku zwróconego z findpolecenia
  • "${file##*/}"zawiera tylko część po ostatniej, /tj. tylko nazwę pliku pliku
  • "${file%/*}"zawiera ścieżkę do finału, /tj. z wyjątkiem części wynikame w postaci basename
  • "${path##*/}"zawiera część za ostatnią /ze pathzmiennej, tj. bezpośrednią ścieżkę folderu nad nazwą bazową pliku
  • "${base%.*}"zawiera część nazwy basenu z .pdfusuniętym rozszerzeniem

Więc jeśli basename bez rozszerzenia pasuje do nazwy bezpośredniego folderu powyżej, drukujemy ścieżkę.

Inian
źródło
7

Odwrotność odpowiedzi Iniana , tj. Poszukaj katalogów, a następnie sprawdź, czy przechowują plik o określonej nazwie.

Poniższe wypisuje ścieżki znalezionych plików względem katalogu 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##*/}zostanie zastąpiony fragmentem nazwy pliku ścieżki katalogu i może zostać zastąpiony przez $(basename "$dirpath").

Dla osób, które lubią składnię zwarciową:

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

Zaletą robienia tego w ten sposób jest to, że możesz mieć więcej plików PDF niż katalogów. Liczba zaangażowanych testów zostanie zmniejszona, jeśli ograniczy się zapytanie o mniejszą liczbę (liczbę katalogów).

Na przykład, jeśli pojedynczy katalog zawiera 100 plików PDF, próbowałoby to wykryć tylko jeden z nich, zamiast testować nazwy wszystkich 100 plików w stosunku do nazwy katalogu.

Kusalananda
źródło
3

z zsh:

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

Uważaj, że chociaż **/nie będzie podążać za dowiązaniami symbolicznymi, to */zrobi to.

Stéphane Chazelas
źródło
2

Nie został określony, ale oto rozwiązanie bez wyrażeń regularnych, jeśli ktoś jest zainteresowany.

Możemy użyć, find . -type faby po prostu pobrać pliki, a następnie wykorzystać dirnamei basenamenapisać warunek. Narzędzia mają następujące zachowanie:

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

basenamezwraca tylko nazwę pliku po ostatnim /:

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

dirnamedaje całą ścieżkę do finału /:

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

Dlatego basename $(dirname $file)podaje katalog nadrzędny pliku.

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

Rozwiązanie

Połącz powyższe, aby utworzyć warunek "$(basename $file)" = "$(basename $(dirname $file))".pdf, a następnie wydrukuj każdy wynik, findjeśli warunek zwróci true.

$ 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

W powyższym przykładzie dodaliśmy katalog / plik ze spacjami w nazwie, aby traktować tę sprawę (dzięki @Kusalananda w komentarzach)

użytkownik1717828
źródło
Niestety spowoduje to uszkodzenie nazw plików takich jak Final Thesis.pdf(ze spacją).
Kusalananda
@Kusalananda Naprawiono.
user1717828,
0

Biorę bash masek, prosty pętli nad ciąg testuje każdy dzień nad Find programu. Nazwij mnie irracjonalnym, a choć może być nieoptymalny, taki prosty kod robi dla mnie pewną sztuczkę: czytelny i wielokrotnego użytku, nawet satysfakcjonujący! Pozwól mi zatem zasugerować kombinację:

• bash globstar : for f in ** ; do ... ** pętle nad każdym plików w bieżącym katalogu i wszystkich podfolderów .. Aby sprawdzić status globstar w bieżącej sesji: shopt -p globstar. Aby aktywować globstar: shopt -s globstar.

• Narzędzie „plikowe” : if [[ $(file "$f") =~ pdf ]]; then ... sprawdzanie aktualnego formatu pliku pdf - bardziej niezawodne niż testowanie tylko rozszerzenia pliku

• basename, dirname : aby porównać nazwę pliku z nazwą katalogu znajdującego się bezpośrednio nad nim. basenamezwraca nazwę pliku - dirnamezwraca całą ścieżkę do katalogu - połącz dwie funkcje, aby zwrócić tylko jeden katalog zawierający pasujący plik. Umieszczam każdy z nich w zmiennej ( _mydir i _myf ), aby następnie wykonać prosty test przy użyciu = ~ do dopasowania łańcucha.

Jedna subtelność: usuń „kropkę” z nazwy pliku, aby uniknąć dopasowania nazwy pliku do bieżącego katalogu, którego skrót to także „.” - Użyłem bezpośredniego podstawienia łańcucha na zmiennej _myf : ${_myf//./}- niezbyt elegancki, ale działa. Pozytywne wyniki powróci ścieżkę każdego pliku - wraz z pełną ścieżkę bieżącego folderu poprzedzając wyjście z: $(pwd)/.

Kod

for f in ** ; do
  if [[ $(file "$f") =~ PDF ]]; then
    _mydir="$(basename $(dirname $f))" ; 
    _myf="$(basename $f)" ; 
    [[ "${_myf//./}" =~ "$_mydir" ]] && echo -e "$(pwd)/$f" ; 
  fi ; 
done
docgyneco69
źródło