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

Вы ошибаетесь, архиваторы оболочки существовали не позднее 1980 года.

Они были написаны для исходных архивов usenet, чтобы позволить архивам быть появляются в письме. Tar является двоичным и не может быть легко помещен в исходный архив usenet.

9
22.05.2019, 10:39
6 ответов

С GNUfind:

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
16
27.01.2020, 20:04

Традиционный вариант цикла 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
27.01.2020, 20:04

сzsh:

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

Имейте в виду, что хотя **/не будет переходить по символическим ссылкам, */будет.

3
27.01.2020, 20:04

Обратное ответу Иниана , т. е. искать каталоги, а затем смотреть, содержат ли они файл с определенным именем.

Далее выводятся пути к найденным файлам относительно каталога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 файлов с именами каталога.

7
27.01.2020, 20:04

Не указано, но вот решение без регулярных выражений, если кому интересно.

Мы можем использовать 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 в комментариях)

2
27.01.2020, 20:04

В любой день я использую bash-глобирование, простой цикл по строковым тестам в программе Find . Назовите меня иррациональным, и хотя он вполне может быть неоптимальным, такой простой код делает свое дело для меня :читабельным и повторно используемым, даже удовлетворяющим!. Поэтому позвольте мне предложить комбинацию:

• Баш Глобстар:for f in ** ; do...**перебирает все файлы в текущем каталоге и всех подпапках... для проверки статуса globstar в текущем сеансе:shopt -p globstar. Чтобы активировать глобус:shopt -s globstar.

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

• базовое имя, имя каталога:чтобы сравнить имя файла с именем каталога непосредственно над ним. 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
0
27.01.2020, 20:04

Теги

Похожие вопросы