Cara loop melalui direktori secara rekursif untuk menghapus file dengan ekstensi tertentu

157

Saya perlu mengulang direktori secara rekursif dan menghapus semua file dengan ekstensi .pdfdan .doc. Saya mengelola untuk mengulang direktori secara rekursif tetapi tidak berhasil memfilter file dengan ekstensi file yang disebutkan di atas.

Kode saya sejauh ini

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

Saya perlu bantuan untuk menyelesaikan kode, karena saya tidak mendapatkan apa-apa.

Elitmiar
sumber
68
Saya tahu ini bentuk yang buruk untuk mengeksekusi kode tanpa memahaminya, tetapi banyak orang datang ke situs ini untuk mempelajari scripting bash. Saya sampai di sini dengan googling "file scripting bash secara rekursif", dan hampir menjalankan salah satu jawaban ini (hanya untuk menguji rekursi) tanpa disadari akan menghapus file. Saya tahu rmadalah bagian dari kode OP, tetapi sebenarnya tidak relevan dengan pertanyaan yang diajukan. Saya pikir akan lebih aman jika jawaban diungkapkan menggunakan perintah yang tidak berbahaya echo.
Keith
Pertanyaan serupa di sini: stackoverflow.com/questions/41799938/…
codeforester
1
@Keith memiliki pengalaman serupa, sepenuhnya setuju dan mengubah judul
idclev 463035818

Jawaban:

146

find hanya dibuat untuk itu.

find /tmp -name '*.pdf' -or -name '*.doc' | xargs rm
mouviciel
sumber
19
Atau -deleteopsi temukan .
Matthew Flaschen
28
Orang harus selalu menggunakan find ... -print0 | xargs -0 ..., bukan mencari mentah | xargs untuk menghindari masalah dengan nama file yang mengandung baris baru.
Grumbel
7
Menggunakan xargstanpa opsi hampir selalu merupakan saran yang buruk dan ini tidak terkecuali. Gunakan find … -execsebagai gantinya.
Gilles 'SO- berhenti bersikap jahat'
211

Sebagai tindak lanjut dari jawaban mouviciel, Anda juga bisa melakukan ini sebagai for for, alih-alih menggunakan xargs. Saya sering menemukan xargs rumit, terutama jika saya perlu melakukan sesuatu yang lebih rumit di setiap iterasi.

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

Seperti yang dikomentari sejumlah orang, ini akan gagal jika ada spasi dalam nama file. Anda dapat mengatasi ini dengan mengatur sementara IFS (internal field seperator) ke karakter baris baru. Ini juga gagal jika ada karakter wildcard \[?*dalam nama file. Anda dapat mengatasinya dengan menonaktifkan sementara ekspansi wildcard (globbing).

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

Jika Anda memiliki baris baru dalam nama file Anda, maka itu tidak akan berhasil. Anda lebih baik dengan solusi berbasis xargs:

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

(Kurung yang lolos diperlukan di sini untuk memiliki -print0berlaku untuk kedua orklausa.)

GNU dan * BSD find juga memiliki -deleteaksi, yang akan terlihat seperti ini:

find /tmp \( -name '*.pdf' -or -name '*.doc' \) -delete
James Scriven
sumber
27
Ini tidak berfungsi seperti yang diharapkan jika ada spasi dalam nama file (untuk loop membagi hasil pencarian di spasi putih).
trev
3
Bagaimana Anda menghindari pemisahan di whitespace? Saya mencoba hal serupa dan saya punya banyak direktori dengan spasi putih yang mengacaukan loop ini.
Christian
3
karena itu jawaban yang sangat membantu?
zenperttu
1
@Christian Perbaiki pemisahan spasi putih dengan menggunakan tanda kutip seperti ini: "$ (find ...)". Saya sudah mengedit jawaban James untuk ditampilkan.
Matius
2
@Matthew hasil edit Anda tidak memperbaiki apa pun: itu sebenarnya membuat perintah hanya berfungsi jika ada file yang ditemukan unik . Setidaknya versi ini berfungsi jika tidak ada spasi, tab, dll. Dalam nama file. Saya kembali ke versi lama. Memperhatikan yang masuk akal dapat benar - benar memperbaiki for f in $(find ...). Hanya saja, jangan gunakan metode ini.
gniourf_gniourf
67

Tanpa find:

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

/tmp/*adalah file dalam dir dan /tmp/**/*file dalam subfolder. Mungkin saja Anda harus mengaktifkan opsi globstar ( shopt -s globstar). Jadi untuk pertanyaan kodenya akan terlihat seperti ini:

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

Perhatikan bahwa ini membutuhkan bash ≥4.0 (atau zsh tanpa shopt -s globstar, atau ksh dengan set -o globstarbukan shopt -s globstar). Selanjutnya, dalam bash <4.3, ini melintasi tautan simbolik ke direktori serta direktori, yang biasanya tidak diinginkan.

Tomek
sumber
1
Metode ini bekerja untuk saya, bahkan dengan nama file yang mengandung spasi di OSX
ideasasylum
2
Patut dicatat bahwa globstar hanya tersedia di Bash 4.0 atau lebih baru .. yang bukan versi default pada banyak mesin.
Troy Howard
1
Saya tidak berpikir Anda perlu menentukan argumen pertama. (Setidaknya pada hari ini,) for f in /tmp/**akan cukup. Termasuk file dari / tmp dir.
phil294
1
Bukankah lebih baik seperti ini? for f in /tmp/*.{pdf,doc} tmp/**/*.{,pdf,doc} ; do
Ice-Blaze
1
**adalah ekstensi yang bagus tetapi tidak portabel untuk POSIX sh. (Pertanyaan ini ditandai dengan bash tetapi akan lebih baik untuk menunjukkan bahwa tidak seperti beberapa solusi di sini, ini benar-benar hanya untuk Bash. Atau, well, ia juga berfungsi di beberapa shell yang diperluas.)
tripleee
27

Jika Anda ingin melakukan sesuatu secara rekursif, saya sarankan Anda menggunakan rekursi (ya, Anda bisa melakukannya menggunakan tumpukan dan sebagainya, tapi hei).

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

(cd /tmp; recursiverm)

Yang mengatakan, findmungkin merupakan pilihan yang lebih baik seperti yang telah disarankan.

Falstro
sumber
15

Berikut ini contoh menggunakan 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
sumber
15

Ini tidak menjawab pertanyaan Anda secara langsung, tetapi Anda dapat menyelesaikan masalah Anda dengan satu kalimat:

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

Beberapa versi find (GNU, BSD) memiliki -deletetindakan yang dapat Anda gunakan alih-alih memanggil rm:

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

Metode ini menangani ruang dengan baik.

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

Edit, perbaiki satu per satu

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
sumber
Saya pikir bendera "-n" setelah gema tidak diperlukan. Cukup uji sendiri: dengan "-n" skrip Anda memberikan jumlah file yang salah. Untuk tepat satu file di direktori itu output "Hitung: 0"
Lopa
1
Ini tidak berfungsi dengan semua nama file: gagal dengan spasi di akhir nama, dengan nama file yang berisi baris baru dan dengan beberapa nama file yang mengandung garis miring terbalik. Cacat ini bisa diperbaiki tetapi seluruh pendekatan tidak perlu rumit sehingga tidak layak untuk diganggu.
Gilles 'SANGAT berhenti menjadi jahat'
3

Untuk bash (sejak versi 4.0):

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

Itu saja.
Ekstensi tambahan ".ext" di sana untuk memilih file (atau dir) dengan ekstensi itu.

Opsi globstar mengaktifkan ** (pencarian secara berulang).
Opsi nullglob menghapus * ketika tidak cocok dengan file / dir.
Opsi dotglob termasuk file yang mulai dengan titik (file tersembunyi).

Berhati-hatilah bahwa sebelum bash 4.3, **/juga melintasi tautan simbolik ke direktori yang tidak diinginkan.

Gilles 'SANGAT berhenti menjadi jahat'
sumber
1

Fungsi berikut akan secara berulang mengulangi semua direktori dalam \home\ubuntudirektori (seluruh struktur direktori di bawah Ubuntu) dan menerapkan pemeriksaan yang diperlukan dalam elseblok.

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
sumber
1

Ini adalah cara paling sederhana yang saya tahu untuk melakukan ini: rm **/@(*.doc|*.pdf)

** membuat ini bekerja secara rekursif

@(*.doc|*.pdf) mencari file yang berakhiran pdf OR doc

Mudah diuji dengan aman dengan menggantinya rmdenganls

ecotechie
sumber
0

Tidak ada alasan untuk menyalurkan output findke utilitas lain. findmemiliki -deletebendera yang terpasang di dalamnya.

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

Jawaban lain yang diberikan tidak akan menyertakan file atau direktori yang dimulai dengan a. berikut ini bekerja untuk saya:

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

Kerjakan saja

find . -name '*.pdf'|xargs rm
Navi
sumber
4
Tidak, jangan lakukan ini. Ini pecah jika Anda memiliki nama file dengan spasi atau simbol lucu lainnya.
gniourf_gniourf
-1

Berikut ini akan berulang melalui direktori yang diberikan secara rekursif dan daftar semua konten:

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

SK Venkat
sumber
Tidak, fungsi ini tidak melintasi apa pun secara rekursif. Itu hanya mencantumkan isi subdirektori. Itu hanya fluff sekitar ls -l /home/ubuntu/*/, jadi sangat tidak berguna.
Gilles 'SANGAT berhenti menjadi jahat'
-1

Jika Anda dapat mengubah shell yang digunakan untuk menjalankan perintah, Anda dapat menggunakan ZSH untuk melakukan pekerjaan itu.

#!/usr/bin/zsh

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

Ini akan berulang secara berulang melalui semua file / folder.

Amin NAIRI
sumber