Belirli uzantılara sahip dosyaları silmek için bir dizinde özyinelemeli olarak nasıl döngü yapılır


157

Ben bir dizinin döngü yinelemeli ihtiyaç ve uzantılı tüm dosyaları kaldırmak .pdfve .doc. Yinelenen bir dizin üzerinden döngü yöneten ancak yukarıda belirtilen dosya uzantıları ile dosyaları filtrelemek için yönetmiyorum.

Kodum şu ana kadar

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

Hiçbir yere ulaşmadığım için kodu tamamlamak için yardıma ihtiyacım var.


68
Kodu anlamadan yürütmenin kötü bir form olduğunu biliyorum, ancak birçok kişi bash komut dosyasını öğrenmek için bu siteye geliyor. Buraya "bash betik dosyaları özyinelemeli" googling ile geldi ve neredeyse bu cevaplardan birini (sadece özyineleme sınamak için) dosyaları siler farkında olmadan koştu. rmOP kodunun bir parçası olduğunu biliyorum , ama aslında sorulan soru ile ilgili değil. Cevaplar gibi zararsız bir komutla ifade edilmesinin daha güvenli olacağını düşünüyorum echo.
Keith


1
@Keith benzer deneyime sahip, tamamen katılıyorum ve unvanı değiştirdi
idclev 463035818

Yanıtlar:


146

find sadece bunun için yapılmış.

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

19
Veya find'ın -deleteseçeneği.
Matthew Flaschen

28
Kişi her zaman kullanmalı find ... -print0 | xargs -0 ..., çiğ değil bulmalı | yeni satır içeren dosya adlarıyla ilgili sorunları önlemek için xargs.
Grumbel

7
xargsHiçbir seçenek olmadan kullanmak neredeyse her zaman kötü bir tavsiye ve bu bir istisna değildir. find … -execBunun yerine kullanın .
Gilles 'SO- kötü olmayı bırak

211

Mouviciel'in cevabının bir takibi olarak, bunu xargs kullanmak yerine for döngüsü olarak da yapabilirsiniz. Xargs'ı hantal buluyorum, özellikle de her bir yinelemede daha karmaşık bir şey yapmam gerekirse.

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

Bazı kişilerin yorumladığı gibi, dosya adlarında boşluk varsa bu başarısız olur. Geçici olarak IFS'yi (dahili alan ayırıcısı) yeni satır karakterine ayarlayarak bu sorunu çözebilirsiniz. \[?*Dosya adlarında joker karakterler varsa bu da başarısız olur . Joker karakter genişletmeyi (globbing) geçici olarak devre dışı bırakarak bu sorunu çözebilirsiniz.

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

Dosya adlarınızda yeni satırlar varsa, bu da çalışmaz. Xargs tabanlı bir çözümle daha iyi durumdasınız:

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

(Burada kaçan köşeli parantezlerin her -print0iki ormaddeye de uygulanması gerekir .)

GNU ve * BSD find'ın da şöyle bir -deleteeylemi vardır :

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

27
Dosya adında bir boşluk varsa bu beklendiği gibi çalışmaz (for döngüsü, bulgunun sonuçlarını boşlukta ayırır).
trev

3
Boşlukta bölünmeyi nasıl sağlıyorsunuz? Ben benzer bir şey deniyorum ve ben bu döngü berbat beyaz boşluk ile dizinler bir sürü var.
Christian

3
çünkü bu çok yardımcı bir cevap mı?
zenperttu

1
@Christian Aşağıdaki gibi tırnak işaretlerini kullanarak boşluk ayırmayı düzeltin: "$ (find ...)". Göstermek için James'in cevabını düzenledim.
Matthew

2
@Matthew düzenlemeniz hiçbir şeyi düzeltmedi: aslında komutun yalnızca benzersiz bir bulunan dosya varsa çalışmasını sağladı . Dosya adlarında boşluk, sekme vb. Yoksa en azından bu sürüm çalışır . Eski sürüme geri döndüm. Mantıklı bir fark gerçekten düzeltebilir for f in $(find ...). Sadece bu yöntemi kullanmayın.
gniourf_gniourf

67

Olmadan find:

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

/tmp/*dir dizinindeki /tmp/**/*dosyalar ve alt klasörlerdeki dosyalardır. Globstar seçeneğini ( shopt -s globstar) etkinleştirmeniz gerekebilir . Yani soru için kod şöyle görünmelidir:

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

Bunun bash ≥4.0 (veya zsh olmadan shopt -s globstarveya set -o globstaryerine ksh ile shopt -s globstar) gerektirdiğini unutmayın. Ayrıca, bash <4.3'te, bu genellikle istenmeyen dizinlerin yanı sıra dizinlere sembolik bağları da izler.


1
Bu yöntem benim için çalıştı, hatta OSX'te boşluk içeren dosya adları ile
ideasasylum

2
Globstar'ın sadece Bash 4.0 veya daha yeni sürümlerinde mevcut olduğunu belirtmek gerekir ki bu, birçok makinede varsayılan sürüm değildir.
Troy Howard

1
İlk argümanı belirtmeniz gerektiğini düşünmüyorum. (En azından bugün itibariyle) for f in /tmp/**yeterli olacak. / Tmp dizinindeki dosyaları içerir.
phil294

1
Böyle daha iyi olmaz mıydı? for f in /tmp/*.{pdf,doc} tmp/**/*.{,pdf,doc} ; do
Ice-Blaze

1
**güzel bir uzantıdır ancak POSIX için taşınabilir değildir sh. (Bu soru etiketli bash . Ama burada çözümlerin birçok farklı olarak, bu gerçekten Bash okunur Yoksa bu işaret etmek iyi olurdu, iyi, çok çeşitli diğer genişletilmiş kabukları içinde çalışır.)
tripleee

27

Özyinelemeli bir şey yapmak istiyorsanız, özyineleme kullanmanızı öneririm (evet, yığınları kullanarak yapabilirsiniz, ancak hey).

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

(cd /tmp; recursiverm)

Bununla birlikte find, muhtemelen daha önce önerildiği gibi daha iyi bir seçimdir.


15

Shell ( bash) kullanan bir örnek :

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

Bu, sorunuza doğrudan cevap vermez, ancak sorununuzu tek bir astarla çözebilirsiniz:

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

Bazı find sürümlerinin (GNU, BSD) -deleteçağırmak yerine kullanabileceğiniz bir eylemi vardır rm:

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

7

Bu yöntem boşlukları iyi işler.

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

Düzenleme, tek tek düzeltmeler

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
}

Ekodan sonra "-n" bayrağına ihtiyaç olmadığını düşünüyorum. Sadece kendiniz test edin: "-n" ile betiğiniz yanlış sayıda dosya verir. Dizindeki tam olarak bir dosya için "Sayı: 0" çıktısı verir
Lopa

1
Bu tüm dosya adlarıyla çalışmaz: adın sonundaki boşluklarla, satırsonu içeren dosya adlarıyla ve ters eğik çizgi içeren bazı dosya adlarıyla başarısız olur. Bu kusurlar düzeltilebilir, ancak tüm yaklaşım gereksiz yere karmaşıktır, bu yüzden rahatsız etmeye değmez.
Gilles 'SO- kötü olmayı bırak

3

Bash için (sürüm 4.0'dan beri):

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

Bu kadar.
Sondaki ".ext" uzantısı o uzantıya sahip dosyaları (veya dizinleri) seçmek içindir.

Opsiyon globstar ** (arama özyinelemeli) özelliğini etkinleştirir.
Nullglob seçeneği, bir dosya / dizin ile eşleşmediğinde * işaretini kaldırır.
Seçenek dotglob, nokta ile başlayan dosyaları (gizli dosyalar) içerir.

Bash 4.3'ten önce, **/arzu edilmeyen dizinlere sembolik bağlantılar da uyguladığına dikkat edin .


1

Aşağıdaki işlev, \home\ubuntudizindeki tüm dizinleri (ubuntu altındaki tüm dizin yapısı) yinelemeli olarak yineleyecek ve gerekli denetimleri elseblok halinde uygulayacaktır .

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

Bunu yapmayı bildiğim en basit yol bu: rm **/@(*.doc|*.pdf)

** bu işi tekrar tekrar yapar

@(*.doc|*.pdf) pdf VEYA doc ile biten bir dosya arar

Değiştirerek güvenli bir şekilde test rmetmekls


0

Çıkışını findbaşka bir yardımcı programa bağlamak için hiçbir neden yoktur . findiçinde bir -deletebayrak var.

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

0

Sağlanan diğer yanıtlar, a ile başlayan dosyaları veya dizinleri içermez. aşağıdakiler benim için çalıştı:

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

Sadece yap

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

4
Hayır, yapma. Boşluklu veya diğer komik sembollerle dosya adlarınız varsa bu durum bozulur.
gniourf_gniourf

-1

Aşağıdakiler, verilen dizinde özyinelemeli olarak dolaşır ve tüm içeriği listeler:

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


Hayır, bu işlev bir şeyi tekrar tekrar geçmez. Yalnızca alt dizinlerin içeriğini listeler. Sadece kabartmak ls -l /home/ubuntu/*/, bu yüzden oldukça işe yaramaz.
Gilles 'SO- kötü olmayı bırak

-1

Komutu çalıştırmak için kullanılan kabuğu değiştirebilirseniz, işi yapmak için ZSH'yi kullanabilirsiniz.

#!/usr/bin/zsh

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

Bu, tüm dosyalar / klasörler arasında yinelemeli olarak döngü yapacaktır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.