Encontrar todos los archivos con una extensión dada cuyo nombre base es el nombre del directorio padre

9

Quiero buscar recursivamente cada *.pdfarchivo en un directorio ~/foocuyo nombre base coincida con el nombre del directorio principal del archivo.

Por ejemplo, suponga que la estructura del directorio se ~/foove así

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

Ejecutar mi comando deseado volvería

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

¿Es esto posible usar findo alguna otra utilidad básica? Supongo que esto es factible usando la -regexopción findpero no estoy seguro de cómo escribir el patrón correcto.

Brian Fitzpatrick
fuente
Sí, me burlaré de un ejemplo ahora.
Brian Fitzpatrick
1
@Inian Agregó un ejemplo. ¿Esto ayuda?
Brian Fitzpatrick

Respuestas:

16

Con GNU find:

find . -regextype egrep -regex '.*/([^/]+)/\1\.pdf'
  • -regextype egrep use la expresión regular de estilo egrep.
  • .*/ coincidir con las directivas de los abuelos.
  • ([^/]+)/ hacer coincidir el directorio padre en un grupo.
  • \1\.pdfuse backreferencepara hacer coincidir el nombre del archivo como directorio principal.

actualizar

Uno (yo mismo) podría pensar que .*es lo suficientemente codicioso, no es necesario excluir /de la coincidencia de padres:

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

El comando anterior no funcionará bien, porque coincide ./a/b/a/b.pdf:

  • .*/ partidos ./
  • (.+)/ partidos a/b/
  • \1.pdf partidos a/b.pdf
dedowsdi
fuente
Muy genial. Ojalá pudiera regex así de bien.
Brian Fitzpatrick
O find . -regex '.*/\([^/]*\)/\1\.pdf'y luego incluso funcionaría con BSD find.
Stéphane Chazelas
7

La variante de bucle tradicional de find .. -exec sh -c ''usar las construcciones de shell para que coincida con el nombre base y la ruta inmediata anterior sería hacer a continuación.

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

Para desglosar las expansiones de parámetros individuales

  • filecontiene la ruta completa del .pdfarchivo devuelto por el findcomando
  • "${file##*/}"contiene solo la parte posterior a la última, /es decir, solo el nombre base del archivo
  • "${file%/*}"contiene la ruta hasta el final, /es decir, excepto la parte del nombre base del resultado
  • "${path##*/}"contiene la parte posterior a la última /de la pathvariable, es decir, la ruta de la carpeta inmediata sobre el nombre base del archivo
  • "${base%.*}"contiene la parte del nombre base con la .pdfextensión eliminada

Entonces, si el nombre base sin extensión coincide con el nombre de la carpeta inmediata anterior, imprimimos la ruta.

Inian
fuente
7

El reverso de la respuesta de Inian , es decir, buscar directorios, y luego ver si contienen un archivo con un nombre en particular.

A continuación se imprimen los nombres de ruta de los archivos encontrados en relación con el directorio 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##*/}será reemplazado por la parte del nombre del archivo de la ruta del directorio, y podría ser reemplazado por $(basename "$dirpath").

Para las personas que les gusta la sintaxis de cortocircuito:

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

El beneficio de hacerlo de esta manera es que puede tener más archivos PDF que directorios. El número de pruebas involucradas se reduce si uno restringe la consulta por un número menor (el número de directorios).

Por ejemplo, si un solo directorio contiene 100 archivos PDF, esto solo intentaría detectar uno de ellos en lugar de probar los nombres de los 100 archivos con respecto al del directorio.

Kusalananda
fuente
3

con zsh:

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

Tenga en cuenta que si bien **/no seguirá enlaces simbólicos, lo */hará.

Stéphane Chazelas
fuente
2

No se especificó, pero aquí hay una solución sin expresiones regulares si alguien está interesado.

Podemos usar find . -type fpara obtener archivos, luego utilizar dirnamey basenameescribir el condicional. Las utilidades tienen el siguiente comportamiento:

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

basenamedevuelve solo el nombre del archivo después del último /:

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

dirnameda todo el camino hasta la final /:

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

Por lo tanto, basename $(dirname $file)proporciona el directorio principal del archivo.

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

Solución

Combine lo anterior para formar el condicional "$(basename $file)" = "$(basename $(dirname $file))".pdf, luego solo imprima cada resultado findsi ese condicional devuelve verdadero.

$ 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

En el ejemplo anterior, hemos agregado un directorio / archivo con espacios en el nombre para tratar ese caso (gracias a @Kusalananda en los comentarios)

usuario1717828
fuente
Desafortunadamente, esto se romperá en nombres de archivo como Final Thesis.pdf(con un espacio).
Kusalananda
@Kusalananda fijo.
user1717828
0

Tomo bash globbing, pruebas simples de bucle sobre cadena cualquier día sobre el programa Find . Llámame irracional, y si bien puede ser subóptimo, un código tan simple me sirve: ¡legible y reutilizable, incluso satisfactorio! Permítanme, por lo tanto, sugerir una combinación de:

• fiesta Globstar : for f in ** ; do ... ** bucles más de todos los archivos en el directorio actual y todas las subcarpetas .. comprobar el estado de Globstar en su sesión actual: shopt -p globstar. Para activar Globstar: shopt -s globstar.

• Utilidad "archivo" : if [[ $(file "$f") =~ pdf ]]; then ... para verificar el formato de archivo real para pdf , más robusto que probar solo para la extensión del archivo

• basename, dirname : para comparar el nombre del archivo con el nombre del directorio inmediatamente superior. basenamedevuelve el nombre del archivo - dirnamedevuelve la ruta completa del directorio - combina las dos funciones para devolver solo el directorio que contiene el archivo correspondiente. Puse cada uno en una variable ( _mydir y _myf ) para luego hacer una prueba simple usando = ~ para la coincidencia de cadenas.

Una sutileza: elimine cualquier "punto" en el nombre del archivo para evitar que coincida con el directorio actual cuyo acceso directo también es "." - Utilicé la sustitución directa de cadenas en la variable _myf : ${_myf//./}- no es muy elegante pero funciona. Coincidencias positivas volverán ruta de cada archivo - junto con la ruta completa de la carpeta actual precediendo la salida con: $(pwd)/.

Código

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
fuente