rm -r: excluir determinados subdiretórios

1

O problema

Eu tenho uma estrutura de diretórios, e. g. algo assim (aqui as barras finais representam pastas):

./A
./B/A/A
./B/A/B
./B/B/
./B/C
./C/

E eu preciso recursivamente remover tudo, exceto alguns arquivos e diretórios:

./A
./B/A

Depois de executar o comando / script que estou procurando, desejo manter a hierarquia a seguir:

./A
./B/A/A
./B/A/B

A tentativa de solução

Eu tentei usar find ( -print é um marcador de posição):

find \( -path ./A -or -path ./B/A \) -prune -or -print

Isso não funciona porque remove diretórios pai de entradas na lista "não tocar":

$ find \( -path ./A -or -path ./B/A \) -prune -or -print
.
./B
./B/B
./B/C
./C

Especialmente, isso remove ./B , enquanto eu preciso manter ./B/A . Heck, isso remove o diretório atual, afinal.

Eu quero evitar invocações recursivas (ou seja, find -exec something-that-calls-find.sh ), já que as listas de diretórios que lidarei são muito grandes ...

    
por intelfx 03.09.2013 / 21:00

2 respostas

1

Acho mais fácil usar uma expressão regular para corresponder aos caminhos

  • ./B/A
  • ./B/A/A
  • ./B/A/B
  • ./B/A/B/C
  • et cetera

Assim, o seguinte corresponderá a ./A ou qualquer coisa abaixo da pasta ./B/A , incluindo-a. Eu adicionei um \ para tornar o comando mais legível. Observe também que isso funciona apenas com o GNU find , ou seja, não no BSD find .

find -depth -regextype posix-extended -mindepth 1 \
! \( -path "./A" -or -regex "\./B(/A(/.*)?)?" \)

Para explicar o regex: O /.* corresponde a qualquer coisa no diretório A . Você precisa da barra aqui, porque senão um diretório chamado AB teria sido correspondido também. Esse padrão anterior pode aparecer zero vezes (para o diretório A ) ou uma vez (para qualquer coisa abaixo de A ), é por isso que precisamos do ? . Como não queremos excluir B , a parte depois disso pode ocorrer zero ou uma vez ( ? ).

Como há uma negação ( ! ), o comando find corresponde:

./B/B
./B/C
./C

Você pode então adicionar a opção -exec rm -rf {} para remover esses arquivos e pastas. Precisamos que a opção -depth comece com a mais profunda, para não tentar remover pastas que não existem mais.

    
por 03.09.2013 / 21:19
1

Aqui está minha própria solução para isso.
OBSERVAÇÃO: Eu não sou muito amante de portabilidade quando se trata de shell e utilitários, então possivelmente depende muito do Bash 4 e do GNU encontrar.

Código

#!/bin/bash

## given "a/b/c/d", prints "a/b/c", "a/b" and "a".
# $1...: pathes to process
function get_parent_directories() {
    local CURRENT_CHUNK

    for arg; do
        CURRENT_CHUNK="$arg"

        while true; do
            CURRENT_CHUNK="$(dirname "$arg")"
            [[ "$CURRENT_CHUNK" == "." ]] && break
            echo "$CURRENT_CHUNK"
        done
    done
}

## recursively removes all files in given directory, except given names.
# $1: target directory
# $2...: exceptions
function remove_recursive() {
    local DIR="$1"
    shift
    local EXCEPTIONS=( "$@" )

    # find all files in given directory...
    local FIND_ARGS=( find "$DIR" -mindepth 1 )

    # ...skipping all exceptions and below...
    for file in "${EXCEPTIONS[@]}"; do
        FIND_ARGS+=( -path "$file" -prune -or )
    done

    # ...and ignoring all parent directories of exceptions (to avoid removing "./B" when "./B/A" is an exception)...
    while read file; do
        FIND_ARGS+=( -path "$file" -or )
    done < <(get_parent_directories "${EXCEPTIONS[@]}" | sort -u)

    # ...and printing all remaining names, without their descendants (we're going to recursively remove these anyway).
    FIND_ARGS+=( -print0 -prune )

    "${FIND_ARGS[@]}" | xargs -r0 rm -r
}

Explicação

A linha de comando find resultante é criada como uma cadeia de sequências -predicates -actions -or .

Isso significa seguir: para cada caminho, se -predicates tiver êxito, faça -actions , caso contrário, vá para a próxima seqüência. Último elemento na cadeia é apenas -actions , que é o caso padrão.

Aqui, estou fazendo -prune para todos os patches encontrados diretamente em $EXCEPTIONS . Isso impede que find desça além desses nomes.

Em seguida, não estou fazendo nada para todos os pais de pathes em $EXCEPTIONS . Não queremos remover diretórios-pai de exceções, pois a remoção é recursiva.

Finalmente, estou alimentando todos os pathes restantes (o caso padrão) para xargs rm -r . Isso é apenas mais rápido que -exec rm -r {} \; porque somente um rm será gerado.

Eu também faço -prune para eles porque não há sentido em remover explicitamente ./A/B/C se formos remover ./A/B .

p. S.: isso acabou na minha biblioteca de snippets:)

    
por 04.09.2013 / 14:43