So rekursiv durch ein Verzeichnis durchlaufen, um Dateien mit bestimmten Erweiterungen zu löschen


157

Ich muss ein Verzeichnis rekursiv durchlaufen und alle Dateien mit der Erweiterung .pdfund entfernen .doc. Ich schaffe es, ein Verzeichnis rekursiv zu durchlaufen, aber nicht die Dateien mit den oben genannten Dateierweiterungen zu filtern.

Mein Code bisher

#/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

Ich brauche Hilfe, um den Code zu vervollständigen, da ich nicht weiterkomme.


68
Ich weiß, dass es eine schlechte Form ist, Code auszuführen, ohne ihn zu verstehen, aber viele Leute kommen auf diese Seite, um Bash-Scripting zu lernen. Ich bin hierher gekommen, indem ich "Bash-Scripting-Dateien rekursiv" gegoogelt habe und fast eine dieser Antworten ausgeführt habe (nur um die Rekursion zu testen), ohne zu bemerken, dass sie Dateien löschen würde. Ich weiß, dass dies rmein Teil des OP-Codes ist, aber es ist für die gestellte Frage nicht wirklich relevant. Ich denke, es wäre sicherer, wenn die Antworten mit einem harmlosen Befehl wie formuliert würden echo.
Keith


1
@ Keith hatte ähnliche Erfahrungen, stimme voll und ganz zu und änderte den Titel
idclev 463035818

Antworten:


146

find ist nur dafür gemacht.

find /tmp -name '*.pdf' -or -name '*.doc' | xargs rm

19
Oder finden Sie die -deleteOption.
Matthew Flaschen

28
Man sollte immer verwenden find ... -print0 | xargs -0 ..., nicht rohen Fund | xargs, um Probleme mit Dateinamen zu vermeiden, die Zeilenumbrüche enthalten.
Grumbel

7
Die Verwendung xargsohne Optionen ist fast immer ein schlechter Rat, und dies ist keine Ausnahme. Verwenden Sie find … -execstattdessen.
Gilles 'SO - hör auf böse zu sein'

211

Als Folge der Antwort von mouviciel können Sie dies auch als for-Schleife ausführen, anstatt xargs zu verwenden. Ich finde xargs oft umständlich, besonders wenn ich in jeder Iteration etwas Komplizierteres tun muss.

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

Wie einige Leute kommentiert haben, schlägt dies fehl, wenn die Dateinamen Leerzeichen enthalten. Sie können dies umgehen, indem Sie den IFS (Internal Field Separator) vorübergehend auf das Zeilenumbruchzeichen setzen. Dies schlägt auch fehl, wenn \[?*die Dateinamen Platzhalterzeichen enthalten. Sie können dies umgehen, indem Sie die Platzhaltererweiterung (Globbing) vorübergehend deaktivieren.

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

Wenn Ihre Dateinamen Zeilenumbrüche enthalten, funktioniert dies auch nicht. Mit einer xargs-basierten Lösung sind Sie besser dran:

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

(Die mit Klammern versehenen Klammern müssen hier -print0für beide orKlauseln gelten.)

GNU und * BSD find haben auch eine -deleteAktion, die so aussehen würde:

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

27
Dies funktioniert nicht wie erwartet, wenn der Dateiname ein Leerzeichen enthält (die for-Schleife teilt die Ergebnisse von find on whitespace auf).
Trev

3
Wie vermeidest du das Aufteilen auf Leerzeichen? Ich versuche etwas Ähnliches und ich habe viele Verzeichnisse mit Leerzeichen, die diese Schleife vermasseln.
Christian

3
weil es eine sehr hilfreiche Antwort ist?
Zenperttu

1
@Christian Korrigieren Sie die Aufteilung der Leerzeichen mithilfe von Anführungszeichen wie folgt: "$ (find ...)". Ich habe James 'Antwort bearbeitet, um sie zu zeigen.
Matthew

2
@Matthew Ihre Bearbeitung hat überhaupt nichts behoben: Der Befehl funktionierte nur, wenn eine eindeutige gefundene Datei vorhanden war . Zumindest funktioniert diese Version, wenn die Dateinamen keine Leerzeichen, Tabulatoren usw. enthalten. Ich habe auf die alte Version zurückgesetzt. Sinnvoll zu bemerken kann wirklich ein Problem beheben for f in $(find ...). Verwenden Sie diese Methode einfach nicht.
gniourf_gniourf

67

Ohne find:

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

/tmp/*sind Dateien im Verzeichnis und /tmp/**/*sind Dateien in Unterordnern. Möglicherweise müssen Sie die globstar-Option ( shopt -s globstar) aktivieren . Für die Frage sollte der Code also so aussehen:

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

Beachten Sie, dass dies Bash ≥ 4.0 erfordert (oder zsh ohne shopt -s globstaroder ksh mit set -o globstarstatt shopt -s globstar). Darüber hinaus werden in bash <4.3 symbolische Verknüpfungen zu Verzeichnissen sowie zu Verzeichnissen durchlaufen, was normalerweise nicht wünschenswert ist.


1
Diese Methode funktionierte für mich sogar mit Dateinamen, die Leerzeichen unter OSX enthalten
ideasasylum

2
Es ist erwähnenswert, dass globstar nur in Bash 4.0 oder neuer verfügbar ist. Dies ist auf vielen Computern nicht die Standardversion.
Troy Howard

1
Ich glaube nicht, dass Sie das erste Argument angeben müssen. (Zumindest ab heute) for f in /tmp/**wird ausreichen. Enthält die Dateien aus dem Verzeichnis / tmp.
Phil294

1
Wäre es nicht besser so? for f in /tmp/*.{pdf,doc} tmp/**/*.{,pdf,doc} ; do
Ice-Blaze

1
**ist eine schöne Erweiterung, aber nicht portabel für POSIX sh. (Diese Frage ist mit bash markiert, aber es wäre schön darauf hinzuweisen, dass dies im Gegensatz zu einigen der hier aufgeführten Lösungen wirklich nur Bash ist. Oder sie funktioniert auch in mehreren anderen erweiterten Shells.)
Tripleee

27

Wenn Sie etwas rekursiv tun möchten, empfehle ich Ihnen, die Rekursion zu verwenden (ja, Sie können dies mit Stapeln usw. tun, aber hey).

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

(cd /tmp; recursiverm)

Das heißt, findist wahrscheinlich eine bessere Wahl, als bereits vorgeschlagen wurde.


15

Hier ist ein Beispiel mit 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

14

Dies beantwortet Ihre Frage nicht direkt, aber Sie können Ihr Problem mit einem Einzeiler lösen:

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

Einige Versionen von find (GNU, BSD) verfügen über eine -deleteAktion, die Sie verwenden können, anstatt Folgendes aufzurufen rm:

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

7

Diese Methode behandelt Leerzeichen gut.

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

Bearbeiten, Korrekturen nacheinander

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
}

Ich denke "-n" Flag nach Echo nicht benötigt. Testen Sie es einfach selbst: Mit "-n" gibt Ihr Skript eine falsche Anzahl von Dateien an. Für genau eine Datei im Verzeichnis wird "Count: 0"
ausgegeben

1
Dies funktioniert nicht mit allen Dateinamen: Es schlägt mit Leerzeichen am Ende des Namens fehl, mit Dateinamen, die Zeilenumbrüche enthalten, und mit einigen Dateinamen, die Backslashes enthalten. Diese Mängel könnten behoben werden, aber der gesamte Ansatz ist unnötig komplex, sodass es sich nicht lohnt, sich darum zu kümmern.
Gilles 'SO - hör auf böse zu sein'

3

Für Bash (seit Version 4.0):

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

Das ist alles.
Die nachfolgende Erweiterung ".ext", um Dateien (oder Verzeichnisse) mit dieser Erweiterung auszuwählen.

Option globstar aktiviert das ** (Suche rekursiv).
Die Option nullglob entfernt ein *, wenn es keiner Datei / keinem Verzeichnis entspricht.
Option dotglob enthält Dateien, die mit einem Punkt beginnen (versteckte Dateien).

Beachten Sie, dass vor Bash 4.3 **/auch symbolische Links zu Verzeichnissen durchlaufen werden, was nicht wünschenswert ist.


1

Die folgende Funktion würde rekursiv alle Verzeichnisse im \home\ubuntuVerzeichnis durchlaufen (gesamte Verzeichnisstruktur unter Ubuntu) und die erforderlichen Prüfungen im elseBlock anwenden .

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

Dies ist der einfachste Weg, den ich kenne, um dies zu tun: rm **/@(*.doc|*.pdf)

** macht diese Arbeit rekursiv

@(*.doc|*.pdf) sucht nach einer Datei, die auf pdf OR doc endet

Einfach sicher zu testen durch Ersetzen rmdurchls


0

Es gibt keinen Grund, die Ausgabe von findan ein anderes Dienstprogramm weiterzuleiten. findhat eine -deleteFlagge eingebaut.

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

0

Die anderen Antworten enthalten keine Dateien oder Verzeichnisse, die mit a beginnen. Folgendes hat bei mir funktioniert:

#/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

Mach einfach

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

4
Nein, tu das nicht. Dies bricht ab, wenn Sie Dateinamen mit Leerzeichen oder anderen lustigen Symbolen haben.
gniourf_gniourf

-1

Im Folgenden wird das angegebene Verzeichnis rekursiv durchlaufen und der gesamte Inhalt aufgelistet:

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


Nein, diese Funktion durchläuft nichts rekursiv. Es wird nur der Inhalt der Unterverzeichnisse aufgelistet. Es ist nur Flaum herum ls -l /home/ubuntu/*/, also ist es ziemlich nutzlos.
Gilles 'SO - hör auf böse zu sein'

-1

Wenn Sie die zum Ausführen des Befehls verwendete Shell ändern können, können Sie den Job mit ZSH ausführen.

#!/usr/bin/zsh

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

Dadurch werden alle Dateien / Ordner rekursiv durchlaufen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.