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.


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


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

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

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.


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.


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

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

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
}

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.


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

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


0

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

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

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;
}

-1

Solo haz

find . -name '*.pdf'|xargs rm

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


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.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.