Listar pastas que contêm apenas uma subpasta chamada Attic

5

Estou limpando um repositório do CVS antes da migração para o git. Como parte da preparação, eu preciso encontrar (e possivelmente remover) qualquer pasta que contenha APENAS uma pasta Attic .

Meu unix-fu não é strong, mas aqui está o que eu tentei, o que não funciona, mas espero que transmita a intenção.

shopt -s globstar
for file in **/*
do
  if [ -d "$file" ];then
    if ['ls | wc -l' == 1 && 'ls Attic | wc -l' == 1]; then
      ((echo Attic-only folder))
    fi
  fi
done

A segunda parte para isso é, então, encontrar quaisquer pastas (ou cadeias de pastas) que estejam vazias.

Por exemplo, se /foo/bar/Attic for removido e /foo/bar estiver ambos vazios, vamos eliminar essa parte da árvore também.

Background: Estou tentando limpar um repositório CVS para migração para o git. O CVS cria uma pasta Attic para arquivos excluídos. Nos últimos 10 anos, algumas coisas ruins aconteceram. Eu estou totalmente ciente dos riscos e implicações. Eu fiz backup dos meus dados e estou trabalhando em uma cópia.

    
por Denham Coote 02.08.2016 / 12:14

7 respostas

3

Com bash , GNU find e comm :

comm -12 \
    <( find /path/to/CVS/repo -printf '%h\n' \
        sort | uniq -u ) \
    <( find /path/to/CVS/repo -name Attic -type d -printf '%h\n' | \
        sort )

O primeiro find imprime basename s ( -printf '%h\n' ) de tudo, arquivos e diretórios, no repositório. sort | uniq -u , em seguida, imprime diretórios com exatamente um descendente, arquivo ou diretório.

Em seguida, o segundo find imprime os basename s dos diretórios Attic . A interseção desse conjunto com o conjunto acima (ou seja, comm -12 ) são exatamente os diretórios com apenas um Attic descendente.

Isso obviamente ignora coisas como links simbólicos e outros tipos de diversão e nomes de arquivos com novas linhas incorporadas. Você não deve tê-los em um repositório de CVS de qualquer maneira.

    
por 02.08.2016 / 13:25
3

A primeira parte parece ser mais fácil de fazer com um pouco de Python:

#!/usr/bin/env python

import os, sys

for topdir in sys.argv:
    for root, dirs, files in os.walk(topdir):
        if not files and len(dirs) == 1 and dirs[0] == 'Attic':
            print os.path.join(root)

Execute assim:

./script.py /path/to/CVS/repo

Para excluir os diretórios, supondo que seus arquivos não tenham novas linhas incorporadas em nomes e supondo que cooperem com xargs (ou seja, um com a opção -d ):

./script.py /path/to/CVS/repo | xargs -d '\n' rm -rf

Com um xargs não cooperante, você pode modificar o script para imprimir sequências NUL -terminadas:

#!/usr/bin/env python

from __future__ import print_function
import os, sys

for topdir in sys.argv:
    for root, dirs, files in os.walk(topdir):
        if not files and len(dirs) == 1 and dirs[0] == 'Attic':
            print(os.path.join(root), end="
./script.py /path/to/CVS/repo | xargs -0 rm -rf
")

Então você usaria xargs -0 para matar os diretórios:

find /path/to/CVS/repo -depth -type d -empty -delete

Para matar diretórios vazios depois disso:

#!/usr/bin/env python

import os, sys

for topdir in sys.argv:
    for root, dirs, files in os.walk(topdir):
        if not files and len(dirs) == 1 and dirs[0] == 'Attic':
            print os.path.join(root)
    
por 02.08.2016 / 12:54
3

com zsh :

twoormore () {                                            
set -- $REPLY/*(D[2])
(($#))
}

A função avalia true se houver mais de um item em $REPLY ( D[2] seleciona o segundo item de qualquer que seja o glob expandido para). Ele pode ser usado por meio de qualificadores glob:

print -rl -- **/*(D/e_'[[ -d $REPLY/Attic ]]'_^+twoormore)

Isso pesquisa recursivamente ( **/* ) para todos os diretórios ( / ) - incluindo os ocultos ( D ) - e lista apenas aqueles para os quais a função e e negada ( ^ ) Avalie true , ou seja, há um diretório filho chamado Attic e é o único item em $REPLY .

Da mesma forma, com find , você pode executar:

find . -type d -exec sh -c '
if [ -d "$0"/Attic ]; then
set -- "$0"/*
if [ $# -eq 1 ]; then
printf %s\n "$0"
fi
fi
' {} \;
    
por 02.08.2016 / 14:34
3

Encontre todas as pastas Attic em . sem irmãos, no bash:

find . -type d -name Attic -print0 | while read -d $'
find . -type d -name Attic -print0 | while read -d $'%pre%' DIR ;\
    do [[ $(ls -1 "$DIR/.." | wc -l) -eq 1 ]] && echo "$DIR" ; done
' DIR ;\ do [[ $(ls -1 "$DIR/.." | wc -l) -eq 1 ]] && echo "$DIR" ; done

Substitua echo pelo seu comando favorito de manipulação de arquivos; -).

    
por 02.08.2016 / 15:17
2

Tente este comando

find $(find . -type d -exec bash -c "echo -ne '{} '; ls '{}' | wc -l" \; |  awk '$NF==1{print $1}') -name Attic -exec rm -r {} \;
    
por 02.08.2016 / 13:47
2

Usando ksh / bash :

find /cvs/myrepository_copy -type d -name "Attic" -print |
while read -r attic; do
  things=( $( dirname "$attic" )/* )
  if (( ${#things[@]} == 1 )); then
    echo rm -rf "$( dirname "$attic" )"
  fi
done

Faça uma cópia de todo o repositório e execute-o (na cópia, de preferência). Inspecione a saída com seus olhos e cérebro e remova o echo se achar que ele faz a coisa certa.

Você pode ter que executá-lo várias vezes para remover diretórios de nível mais alto que ficaram vazios (contendo apenas um diretório Attic ) em execuções anteriores do loop.

Não tenho certeza de como isso lida com nomes de arquivos exóticos, mas como * usado apenas para verificar se há qualquer coisa diferente de Attic na pasta, isso pode não ser um problema.

Não me responsabilizo pela perda de dados.

    
por 02.08.2016 / 13:06
1

Usando uma função bash f para filtrar a lista de pastas do Attic com find :

f(){ [ $(ls $(dirname $1)|wc -l|xargs echo) == 1 ] && dirname $1; }
export -f f
find . -wholename "*/Attic" -type d -exec bash -c 'f "$0"' {} \;

xargs echo é usado para cortar a string retornada por wc -l (pode não ser necessária em alguns sistemas).

Também pode ser escrito como um oneliner separando a linha acima por ponto e vírgula.

    
por 02.08.2016 / 15:54