Cómo recorrer un directorio de forma recursiva para eliminar archivos con ciertas extensiones

157

Necesito recorrer un directorio de forma recursiva y eliminar todos los archivos con extensión .pdfy .doc. Estoy logrando recorrer un directorio de forma recursiva pero no estoy logrando filtrar los archivos con las extensiones de archivo mencionadas anteriormente.

Mi código hasta ahora

#/bin/sh

SEARCH_FOLDER="/tmp/*"

for f in $SEARCH_FOLDER
do
    if [ -d "$f" ]
    then
        for ff in $f/*
        do      
            echo "Processing $ff"
        done
    else
        echo "Processing file $f"
    fi
done

Necesito ayuda para completar el código, ya que no voy a ninguna parte.

Elitmiar
fuente
68
Sé que es una mala forma ejecutar código sin entenderlo, pero mucha gente viene a este sitio para aprender scripting bash. Llegué buscando en Google "bash scripting files recursively", y casi ejecuté una de estas respuestas (solo para probar la recursión) sin darme cuenta de que eliminaría archivos. Sé que rmes parte del código de OP, pero en realidad no es relevante para la pregunta formulada. Creo que sería más seguro si las respuestas se formularan usando un comando inofensivo como echo.
Keith
Pregunta similar aquí: stackoverflow.com/questions/41799938/…
codeforester
1
@Keith tuvo una experiencia similar, completamente de acuerdo y cambió el título
idclev 463035818

Respuestas:

146

find está hecho para eso.

find /tmp -name '*.pdf' -or -name '*.doc' | xargs rm
Mouviciel
fuente
19
O la -deleteopción de encontrar .
Matthew Flaschen
28
Uno siempre debe usar find ... -print0 | xargs -0 ... , no raw find | xargs para evitar problemas con los nombres de archivo que contienen nuevas líneas.
Grumbel
77
Usar xargssin opciones casi siempre es un mal consejo y esta no es una excepción. Usar en su find … -execlugar.
Gilles 'SO- deja de ser malvado'
211

Como seguimiento a la respuesta de Mouviciel, también puede hacer esto como un bucle for, en lugar de usar xargs. A menudo encuentro engorrosos los xargs, especialmente si necesito hacer algo más complicado en cada iteración.

for f in $(find /tmp -name '*.pdf' -or -name '*.doc'); do rm $f; done

Como han comentado varias personas, esto fallará si hay espacios en los nombres de archivo. Puede solucionar este problema configurando temporalmente el IFS (separador de campo interno) en el carácter de nueva línea. Esto también falla si hay caracteres comodín \[?*en los nombres de archivo. Puede solucionarlo deshabilitando temporalmente la expansión de comodines (globbing).

IFS=$'\n'; set -f
for f in $(find /tmp -name '*.pdf' -or -name '*.doc'); do rm "$f"; done
unset IFS; set +f

Si tiene nuevas líneas en sus nombres de archivo, entonces tampoco funcionará. Estás mejor con una solución basada en xargs:

find /tmp \( -name '*.pdf' -or -name '*.doc' \) -print0 | xargs -0 rm

(Los corchetes escapados se requieren aquí para que se -print0apliquen a ambas orcláusulas).

GNU y * BSD find también tienen una -deleteacción, que se vería así:

find /tmp \( -name '*.pdf' -or -name '*.doc' \) -delete
James Scriven
fuente
27
Esto no funciona como se esperaba si hay un espacio en el nombre del archivo (el bucle for divide los resultados de find en espacios en blanco).
trev
3
¿Cómo evitar la división en espacios en blanco? Estoy intentando algo similar y tengo muchos directorios con espacios en blanco que arruinan este bucle.
Christian
3
porque es una respuesta muy útil?
zenperttu
1
@Christian Repara la división de espacios en blanco usando comillas como esta: "$ (find ...)". He editado la respuesta de James para mostrar.
Mateo
2
@Matthew su edición no solucionó nada en absoluto: en realidad hizo que el comando solo funcionara si hay un archivo encontrado único . Al menos esta versión funciona si no hay espacios, pestañas, etc. en los nombres de archivo. Regresé a la versión anterior. Observar lo sensato realmente puede arreglar a for f in $(find ...). Simplemente no uses este método.
gniourf_gniourf
67

Sin find:

for f in /tmp/* tmp/**/* ; do
  ...
done;

/tmp/*son archivos en dir y /tmp/**/*son archivos en subcarpetas. Es posible que tenga que habilitar la opción globstar ( shopt -s globstar). Entonces, para la pregunta, el código debería verse así:

shopt -s globstar
for f in /tmp/*.pdf /tmp/*.doc tmp/**/*.pdf tmp/**/*.doc ; do
  rm "$f"
done

Tenga en cuenta que esto requiere bash ≥4.0 (o zsh sin shopt -s globstaro ksh con en set -o globstarlugar de shopt -s globstar). Además, en bash <4.3, esto atraviesa enlaces simbólicos a directorios así como a directorios, lo que generalmente no es deseable.

Tomek
fuente
1
Este método funcionó para mí, incluso con nombres de archivo que contienen espacios en OSX
ideasasylum
2
Vale la pena señalar que globstar solo está disponible en Bash 4.0 o posterior ... que no es la versión predeterminada en muchas máquinas.
Troy Howard el
1
No creo que necesites especificar el primer argumento. (Al menos a partir de hoy) for f in /tmp/**será suficiente. Incluye los archivos del directorio / tmp.
phil294
1
¿No sería mejor así? for f in /tmp/*.{pdf,doc} tmp/**/*.{,pdf,doc} ; do
Ice-Blaze
1
**es una buena extensión pero no portátil para POSIX sh. (Esta pregunta está etiquetada como bash, pero sería bueno señalar que, a diferencia de varias de las soluciones aquí, esto realmente es solo Bash. O, bueno, también funciona en varios otros shells extendidos).
tripleee
27

Si quieres hacer algo de forma recursiva, te sugiero que uses la recursividad (sí, puedes hacerlo usando pilas, etc., pero bueno).

recursiverm() {
  for d in *; do
    if [ -d "$d" ]; then
      (cd -- "$d" && recursiverm)
    fi
    rm -f *.pdf
    rm -f *.doc
  done
}

(cd /tmp; recursiverm)

Dicho esto, findprobablemente sea una mejor opción como ya se ha sugerido.

falstro
fuente
15

Aquí hay un ejemplo usando shell ( bash):

#!/bin/bash

# loop & print a folder recusively,
print_folder_recurse() {
    for i in "$1"/*;do
        if [ -d "$i" ];then
            echo "dir: $i"
            print_folder_recurse "$i"
        elif [ -f "$i" ]; then
            echo "file: $i"
        fi
    done
}


# try get path from param
path=""
if [ -d "$1" ]; then
    path=$1;
else
    path="/tmp"
fi

echo "base path: $path"
print_folder_recurse $path
Eric Wang
fuente
15

Esto no responde su pregunta directamente, pero puede resolver su problema con una sola línea:

find /tmp \( -name "*.pdf" -o -name "*.doc" \) -type f -exec rm {} +

Algunas versiones de find (GNU, BSD) tienen una -deleteacción que puede usar en lugar de llamar rm:

find /tmp \( -name "*.pdf" -o -name "*.doc" \) -type f -delete
Oliver Charlesworth
fuente
7

Este método maneja bien los espacios.

files="$(find -L "$dir" -type f)"
echo "Count: $(echo -n "$files" | wc -l)"
echo "$files" | while read file; do
  echo "$file"
done

Editar, arreglos fuera de uno

function count() {
    files="$(find -L "$1" -type f)";
    if [[ "$files" == "" ]]; then
        echo "No files";
        return 0;
    fi
    file_count=$(echo "$files" | wc -l)
    echo "Count: $file_count"
    echo "$files" | while read file; do
        echo "$file"
    done
}
TJR
fuente
Creo que la bandera "-n" después del eco no es necesaria. Simplemente pruébelo usted mismo: con "-n" su script da un número incorrecto de archivos. Para exactamente un archivo en el directorio, muestra "Count: 0"
Lopa
1
Esto no funciona con todos los nombres de archivo: falla con espacios al final del nombre, con nombres de archivo que contienen líneas nuevas y con algunos nombres de archivo que contienen barras diagonales inversas. Estos defectos podrían repararse, pero todo el enfoque es innecesariamente complejo, por lo que no vale la pena molestarse.
Gilles 'SO- deja de ser malvado'
3

Para bash (desde la versión 4.0):

shopt -s globstar nullglob dotglob
echo **/*".ext"

Eso es todo.
La extensión final ".ext" allí para seleccionar archivos (o directorios) con esa extensión.

La opción globstar activa el ** (búsqueda recursiva).
La opción nullglob elimina un * cuando no coincide con ningún archivo / directorio.
La opción dotglob incluye archivos que comienzan con un punto (archivos ocultos).

Tenga en cuenta que antes de bash 4.3, **/también atraviesa enlaces simbólicos a directorios que no es deseable.

Gilles 'SO- deja de ser malvado'
fuente
1

La siguiente función iteraría recursivamente a través de todos los directorios en el \home\ubuntudirectorio (estructura de directorio completa en ubuntu) y aplicaría las comprobaciones necesarias en elsebloque.

function check {
        for file in $1/*      
        do
        if [ -d "$file" ]
        then
                check $file                          
        else
               ##check for the file
               if [ $(head -c 4 "$file") = "%PDF" ]; then
                         rm -r $file
               fi
        fi
        done     
}
domain=/home/ubuntu
check $domain
K_3
fuente
1

Esta es la forma más simple que sé hacer esto: rm **/@(*.doc|*.pdf)

** hace que esto funcione recursivamente

@(*.doc|*.pdf) busca un archivo que termine en pdf O doc

Fácil de probar de forma segura al reemplazar rmconls

ecotechie
fuente
0

No hay razón para canalizar la salida finda otra utilidad. findtiene una -deletebandera incorporada.

find /tmp -name '*.pdf' -or -name '*.doc' -delete
Zak
fuente
0

Las otras respuestas proporcionadas no incluirán archivos o directorios que comiencen con a. lo siguiente funcionó para mí:

#/bin/sh
getAll()
{
  local fl1="$1"/*;
  local fl2="$1"/.[!.]*; 
  local fl3="$1"/..?*;
  for inpath in "$1"/* "$1"/.[!.]* "$1"/..?*; do
    if [ "$inpath" != "$fl1" -a "$inpath" != "$fl2" -a "$inpath" != "$fl3" ]; then 
      stat --printf="%F\0%n\0\n" -- "$inpath";
      if [ -d "$inpath" ]; then
        getAll "$inpath"
      #elif [ -f $inpath ]; then
      fi;
    fi;
  done;
}
TrevTheDev
fuente
-1

Solo haz

find . -name '*.pdf'|xargs rm
Navi
fuente
44
No, no hagas esto. Esto se rompe si tiene nombres de archivo con espacios u otros símbolos divertidos.
gniourf_gniourf
-1

Lo siguiente recorrerá el directorio dado de forma recursiva y enumerará todos los contenidos:

for d in /home/ubuntu/*; do echo "listing contents of dir: $d"; ls -l $d/; done

SK Venkat
fuente
No, esta función no atraviesa nada de forma recursiva. Solo enumera el contenido de los subdirectorios. Es solo pelusa ls -l /home/ubuntu/*/, así que es bastante inútil.
Gilles 'SO- deja de ser malvado'
-1

Si puede cambiar el shell utilizado para ejecutar el comando, puede usar ZSH para hacer el trabajo.

#!/usr/bin/zsh

for file in /tmp/**/*
do
    echo $file
done

Esto recorrerá recursivamente todos los archivos / carpetas.

Amin NAIRI
fuente