Пошук усіх файлів із заданим розширенням, базовим ім'ям яких є ім’я батьківського каталогу


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\.pdfвикористовувати backreferenceдля узгодження імені файлу як батьківського режиму.

оновлення

Хтось (сам для одного) може подумати, що .*це досить жадібно, не потрібно виключати /з відповідності батьків:

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

Наведена вище команда не спрацює добре, тому що вона відповідає ./a/b/a/b.pdf:

  • .*/ сірники ./
  • (.+)/ сірники a/b/
  • \1.pdf сірники a/b.pdf

Дуже круто. Хотілося б, щоб я міг підробити це добре.
Брайан Фіцпатрік

Або find . -regex '.*/\([^/]*\)/\1\.pdf'тоді це навіть працювало б із BSD find.
Стефан Шазелас

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, ніж каталогів. Кількість залучених тестів зменшується, якщо один обмежує запит меншою кількістю (кількістю каталогів).

Наприклад, якщо в одному каталозі є 100 файлів PDF, це намагатиметься виявити лише один із них, а не тестувати імена всіх 100 файлів проти імені каталогу.


3

з zsh:

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

Остерігайтеся, що поки **/не будете слідувати посиланнями, */буде.


2

Це не було вказано, але ось рішення без регулярних виразів, якщо когось цікавить.

Ми можемо find . -type fпросто отримати файли, а потім використовувати dirnameі basenameписати умовні. Утиліти мають таку поведінку:

$ 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

Рішення

Поєднайте вище, щоб сформувати умовне "$(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 Виправлено.
користувач1717828

0

Я беру баш-глобулінг, простий цикл за рядковими тестами в будь-який день за програмою Find . Називайте мене нераціональним, і хоча це може бути недостатньо оптимальним, такий простий код робить для мене хитрість: читабельний та багаторазовий, задовольняючи навіть! Тому дозвольте запропонувати комбінацію:

• Баш globstar : for f in ** ; do ... ** перебирає кожні файли в поточному каталозі і у всіх вкладених папках .. перевірити стан globstar в поточному сеансі: shopt -p globstar. Щоб активувати globstar: shopt -s globstar.

• utlity "file" : if [[ $(file "$f") =~ pdf ]]; then ... перевірити фактичний формат файлу на pdf - більш надійний, ніж тестування лише для розширення файлу

• базове ім’я, ім’я dirname : для порівняння назви файлу з назвою каталогу безпосередньо над ним. basenameповертає ім'я файлу - dirnameповертає весь шлях до каталогу - об'єднайте дві функції, щоб повернути лише один каталог, що містить відповідний файл. Я поміщаю кожен із змінних ( _mydir та _myf ), щоб потім зробити простий тест, використовуючи = ~ для зіставлення рядків.

Одна підпрограмна програма: видаліть будь-яку "крапку" у імені файлу, щоб уникнути відповідності імені файлу поточному каталогу, ярлик якого також "." - Я використовував пряму підстановку рядка для змінної _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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.