Maneira rápida de excluir arquivos com menos de x linhas

9

O que é uma maneira rápida e não muito complicada de excluir todos os arquivos em um diretório com x linhas de comprimento, no bash?

    
por durrrutti 01.12.2016 / 18:38

3 respostas

9

Aqui está uma solução POSIX que deve ser bem simples de entender:

find . -type f -exec awk -v x=10 'NR==x{exit 1}' {} \; -exec echo rm -f {} \;

Como em a resposta de Stephane , remova o echo quando estiver satisfeito com o que será removido.

Explicações, escritas para aqueles totalmente novos no Unix / Linux:

O ponto . representa o diretório atual. find encontra arquivos e diretórios recursivamente dentro de . e pode fazer coisas com eles.

-type é uma das primárias de find ; é um teste que será executado para cada arquivo e diretório encontrado recursivamente (dentro de . ), e o restante das primárias na linha será avaliado apenas se isso resultar em "verdadeiro".

Nesse caso específico, só continuaremos se estivermos lidando com um arquivo regular , não um diretório ou outra coisa (por exemplo, um dispositivo de bloco).

O -exec primary (de find ) chama um comando externo e somente prossegue para o próximo primário se o comando externo sair com êxito (status de saída "0"). O {} é substituído pelo nome do arquivo "considerado" pelo comando find . Portanto, a primeira chamada -exec é equivalente ao comando shell a seguir, executado para cada arquivo por vez:

awk -v x=10 'NR==x{exit 1}' ./somefilename

O awk é um idioma inteiro, desenvolvido para lidar com arquivos de texto delimitados, como CSVs. Os condicionais e comandos do Awk (que estão contidos entre aspas simples e começam com as letras NR ) são executados para cada linha de um arquivo de texto. (Looping implícito).

Para aprender o Awk por completo, eu recomendo o Tutorial do Grymoire , mas vou explicar os recursos do Awk usados em o comando acima.

O sinalizador -v para Awk nos permite definir uma variável Awk (uma vez) antes de os comandos Awk serem executados (para cada linha do arquivo). Nesse caso, definimos x para 10 .

NR é uma variável Awk especial que se refere ao " N umber do atual R ecord." Em outras palavras, é o número da linha que estamos vendo em qualquer passagem específica através do loop.

(Note que é possível, embora incomum, usar um R ecord S eparador "do que o padrão de uma nova linha personagem, definindo RS . Aqui está um exemplo de brincar com separadores de registro. )

Scripts Awk em geral consistem em condições (fora de chaves) combinados com ações (dentro de chaves). Pode haver condições compostas e ações compostas, e há uma condição padrão (true) e uma ação padrão (print), mas não precisamos nos incomodar com isso.

A condição aqui é: "Esta é a décima linha?" Se este for o caso, saímos com um status de saída diferente de zero, o que, em script de shell, significa "terminação de comando malsucedida".

Assim, a única maneira de o comando Awk sair com sucesso é se o final do arquivo for alcançado antes que a décima linha seja alcançada.

Portanto, se o script Awk sair com sucesso, significa que você tem um arquivo com menos de dez linhas.

A próxima chamada -exec (se você remover o echo ) removerá cada arquivo (que chega tão longe na avaliação das primárias do find ) executando:

rm -f ./somefilename
    
por 01.12.2016 / 20:46
5

Supondo uma implementação de find que suporte o predicado -readable (se o seu find não der suporte, apenas remova-o, você só receberá mensagens de erro para arquivos não legíveis ou substitua por -exec test -r {} \; ):

x=10 find . -type f -readable -exec sh -c '
  for file do
    lines=$(wc -l < "$file") && [ "$((lines))" -lt "$x" ] && echo rm -f "$file"
  done' sh {} +

Remova o echo , se feliz.

Isso não é particularmente eficiente, pois conta todas as linhas em todos os arquivos, enquanto precisa parar apenas no x th e ele executa um wc (e potencialmente um rm ) para cada arquivo.

Com o% GNUawk, você pode torná-lo muito mais eficiente com:

x=10
find . -type f -readable -exec awk -v x="$x" -v ORS='
x=10 find . -type f -readable -exec perl -Tlne '
  if ($. == $ENV{x}) {close ARGV}
  elsif (eof) {print $ARGV; close ARGV}' {} +
' ' FNR == x {nextfile} ENDFILE {if (FNR < x) print FILENAME}' {} +| xargs -r0 echo rm -f

(novamente, remova echo quando feliz).

O mesmo com perl :

x=10 find . -type f -readable -exec sh -c '
  for file do
    lines=$(wc -l < "$file") && [ "$((lines))" -lt "$x" ] && echo rm -f "$file"
  done' sh {} +

Substitua print por unlink , se feliz.

    
por 01.12.2016 / 18:43
2

Para completar, além do AWK, você também pode usar o GNU sed para obter o mesmo resultado:

find . -type f -exec sed 11q1 '{}' ';' -exec echo rm -f '{}' ';'

O que resulta em uma linha de comando um pouco mais concisa.

Explicação

11 - is the address, i.e. "the eleventh line"
q - is for _q_uit (abort the execution)
1 - is the exit code parameter for q (GNU sed extension) 
    
por 05.12.2016 / 22:44

Tags