Como percorrer um diretório recursivamente para excluir arquivos com determinadas extensões

157

Eu preciso percorrer um diretório recursivamente e remover todos os arquivos com extensão .pdfe .doc. Estou conseguindo percorrer um diretório recursivamente, mas não consigo filtrar os arquivos com as extensões de arquivo mencionadas acima.

Meu código até agora

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

Preciso de ajuda para concluir o código, pois não estou chegando a lugar algum.

Elitmiar
fonte
68
Eu sei que é uma má forma executar código sem entendê-lo, mas muitas pessoas acessam este site para aprender scripts bash. Cheguei aqui pesquisando "arquivos de script bash recursivamente" e quase executei uma dessas respostas (apenas para testar a recursão) sem perceber que excluiria arquivos. Sei que rmfaz parte do código do OP, mas na verdade não é relevante para a pergunta. Eu acho que seria mais seguro se as respostas fossem formuladas usando um comando inofensivo como echo.
Keith #
Pergunta semelhante aqui: stackoverflow.com/questions/41799938/…
codeforester
1
@Keith teve experiência semelhante, concordo completamente e mudou o título
idclev 463035818

Respostas:

146

find é feito apenas para isso.

find /tmp -name '*.pdf' -or -name '*.doc' | xargs rm
mouviciel
fonte
19
Ou -deleteescolha a opção.
Matthew Flaschen
28
Deve-se sempre usar find ... -print0 | xargs -0 ..., não encontrar bruto | xargs para evitar problemas com nomes de arquivos que contêm novas linhas.
Grumbel 22/10/11
7
Usar xargssem opções quase sempre é um mau conselho e isso não é exceção. Use em find … -execvez disso.
Gilles 'SO- stop be evil'
211

Como acompanhamento da resposta do mouviciel, você também pode fazer isso como um loop for, em vez de usar xargs. Costumo achar xargs pesados, especialmente se precisar fazer algo mais complicado em cada iteração.

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

Como várias pessoas comentaram, isso falhará se houver espaços nos nomes de arquivos. Você pode contornar isso definindo temporariamente o IFS (separador de campo interno) para o caractere de nova linha. Isso também falha se houver caracteres curinga \[?*nos nomes dos arquivos. Você pode contornar isso desativando temporariamente a expansão de curinga (globbing).

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

Se você tiver novas linhas em seus nomes de arquivos, isso também não funcionará. Você está melhor com uma solução baseada em xargs:

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

(Os colchetes de escape são necessários aqui para que as -print0duas orcláusulas sejam aplicáveis ).

O GNU e o * BSD find também têm uma -deleteação, que seria assim:

find /tmp \( -name '*.pdf' -or -name '*.doc' \) -delete
James Scriven
fonte
27
Isso não funciona como o esperado se houver um espaço no nome do arquivo (o loop for divide os resultados da localização no espaço em branco).
trev
3
Como você evita a divisão no espaço em branco? Estou tentando uma coisa semelhante e tenho muitos diretórios com espaços em branco que estragam esse loop.
Christian
3
porque é uma resposta muito útil?
Zenperttu
1
@Christian Corrija a divisão do espaço em branco usando aspas como esta: "$ (find ...)". Eu editei a resposta de James para mostrar.
Mateus
2
@ Matthew sua edição não corrigiu nada: na verdade, ele fez o comando funcionar apenas se houver um arquivo encontrado exclusivo . Pelo menos esta versão funciona se não houver espaços, tabulações etc. nos nomes de arquivos. Voltei para a versão antiga. Observar sensato pode realmente consertar a for f in $(find ...). Só não use esse método.
gniourf_gniourf
67

Sem find:

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

/tmp/*são arquivos em dir e /tmp/**/*são arquivos em subpastas. É possível que você tenha que ativar a opção globstar ( shopt -s globstar). Portanto, para a pergunta, o código deve ficar assim:

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

Observe que isso requer bash ≥4.0 (ou zsh sem shopt -s globstarou ksh com em set -o globstarvez de shopt -s globstar). Além disso, no bash <4.3, isso percorre links simbólicos para diretórios e também para diretórios, o que geralmente não é desejável.

Tomek
fonte
1
Este método funcionou para mim, mesmo com nomes de arquivos que contenham espaços no OSX
ideasasylum
2
Vale ressaltar que a globstar está disponível apenas no Bash 4.0 ou mais recente .. que não é a versão padrão em muitas máquinas.
Troy Howard
1
Eu não acho que você precise especificar o primeiro argumento. (Pelo menos a partir de hoje) for f in /tmp/**será suficiente. Inclui os arquivos de / tmp dir.
phil294
1
Não seria melhor assim? for f in /tmp/*.{pdf,doc} tmp/**/*.{,pdf,doc} ; do
Ice-Blaze
1
**é uma boa extensão, mas não é portátil para POSIX sh. (Esta questão é marcado festa . Mas seria bom ressaltar que, diferentemente de várias das soluções aqui, este é realmente Bash-only Ou, bem, ele funciona em vários outros conchas prolongados, também.)
tripleee
27

Se você quiser fazer algo recursivamente, sugiro que você use recursão (sim, você pode fazê-lo usando pilhas e assim por diante, mas ei).

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

(cd /tmp; recursiverm)

Dito isto, findé provavelmente uma escolha melhor, como já foi sugerido.

falstro
fonte
15

Aqui está um exemplo 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
fonte
15

Isso não responde diretamente à sua pergunta, mas você pode resolver o problema com uma única linha:

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

Algumas versões do find (GNU, BSD) têm uma -deleteação que você pode usar em vez de chamar rm:

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

Este método lida bem com os espaços.

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

Editar, corrige um por um

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
fonte
Acho que a flag "-n" após o eco não é necessária. Apenas teste você mesmo: com "-n", seu script fornece um número incorreto de arquivos. Para exatamente um arquivo no diretório, ele gera "Count: 0"
Lopa
1
Isso não funciona com todos os nomes de arquivos: falha com espaços no final do nome, com nomes de arquivos contendo novas linhas e com alguns nomes de arquivos contendo barras invertidas. Esses defeitos podem ser corrigidos, mas toda a abordagem é desnecessariamente complexa, portanto não vale a pena incomodar.
Gilles 'SO- stop be evil'
3

Para o bash (desde a versão 4.0):

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

Isso é tudo.
A extensão à direita ".ext" lá para selecionar arquivos (ou diretórios) com essa extensão.

A opção globstar ativa o ** (pesquisa recursivamente).
A opção nullglob remove um * quando não corresponde a nenhum arquivo / diretório.
A opção dotglob inclui arquivos que começam com um ponto (arquivos ocultos).

Cuidado antes do bash 4.3, **/também percorre links simbólicos para diretórios que não são desejáveis.

Gilles 'SO- parar de ser mau'
fonte
1

A função a seguir iteraria recursivamente por todos os diretórios no \home\ubuntudiretório (toda a estrutura de diretórios no ubuntu) e aplicaria as verificações necessárias no elsebloco.

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

Esta é a maneira mais simples de fazer isso: rm **/@(*.doc|*.pdf)

** faz esse trabalho recursivamente

@(*.doc|*.pdf) procura um arquivo que termina em pdf OU doc

Fácil de testar com segurança, substituindo rmporls

ecotecnia
fonte
0

Não há razão para canalizar a saída findpara outro utilitário. findtem uma -deletebandeira embutida nele.

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

As outras respostas fornecidas não incluirão arquivos ou diretórios que começam com a. o seguinte funcionou para mim:

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

Apenas faça

find . -name '*.pdf'|xargs rm
Navi
fonte
4
Não faça isso. Isso é interrompido se você tiver nomes de arquivos com espaços ou outros símbolos engraçados.
gniourf_gniourf
-1

O seguinte fará um loop pelo diretório fornecido recursivamente e listará todo o conteúdo:

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

SK Venkat
fonte
Não, essa função não percorre nada recursivamente. Ele lista apenas o conteúdo dos subdiretórios. É apenas fluff ls -l /home/ubuntu/*/, por isso é bastante inútil.
Gilles 'SO- stop be evil'
-1

Se você pode alterar o shell usado para executar o comando, pode usar o ZSH para fazer o trabalho.

#!/usr/bin/zsh

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

Isso irá percorrer recursivamente todos os arquivos / pastas.

Amin NAIRI
fonte