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

10

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

durrrutti
fonte

Respostas:

10

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

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

Como na resposta de Stephane , remova o echoquando estiver satisfeito com o que será removido.


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

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

-typeé um dos find's primárias ; é um teste que será realizado para cada arquivo e diretório que é recursivamente encontrado (dentro .), eo resto das primárias na linha só são avaliadas Se isto resultar em "verdade".

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


O -execprimário (de find) chama um comando externo e só prossegue para o próximo primário se o comando externo sair com êxito (status de saída de "0"). O {}é substituído pelo nome do arquivo "considerado" pelo findcomando. Portanto, a primeira -execchamada é equivalente ao seguinte comando shell, executado para cada arquivo por sua 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 iniciam com as letras NR) são executados para cada linha de um arquivo de texto. (Loop implícito.)

Para aprender o Awk completamente, recomendo o Tutorial Grymoire , mas explicarei os recursos do Awk usados ​​no comando acima.


O -vsinalizador para Awk permite definir uma variável do Awk (uma vez) antes que os comandos do Awk sejam executados (para cada linha do arquivo). Nesse caso, configuramos xpara 10.


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

(Note que é possível, embora incomum, para usar um "diferente R ecord S eparator" do que o padrão de um caractere de nova linha, por definição RS. Aqui está um exemplo de jogar com separadores de registro. )


Os scripts awk geralmente consistem em condições (chaves externas) combinadas com ações (chaves internas). Pode haver condições e ações compostas, e há uma condição padrão (true) e uma ação padrão (print), mas precisamos não se preocupe com isso.

A condição aqui é: "Essa é a décima linha?" Se for esse o caso, saímos com um status de saída diferente de zero, que em scripts de shell significa "finalização malsucedida do comando".

Portanto, a única maneira de este comando Awk sair com êxito é se o final do arquivo for alcançado antes da 10ª linha.

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


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

rm -f ./somefilename
Curinga
fonte
5

Supondo que uma findimplementação seja compatível com o -readablepredicado (se o seu findnão for compatível, remova-o, você 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 echose estiver feliz.

Isso não é particularmente eficiente em que conta todas as linhas em cada arquivo enquanto ele só precisa parar no xth um e ele corre um wc(e potencialmente um rm) comando para cada arquivo.

Com o GNU awk, você pode torná-lo muito mais eficiente com:

x=10
find . -type f -readable -exec awk -v x="$x" -v ORS='\0' '
  FNR == x {nextfile}
  ENDFILE {if (FNR < x) print FILENAME}' {} +|
  xargs -r0 echo rm -f

(novamente, remova echoquando estiver feliz).

O mesmo com perl:

x=10 find . -type f -readable -exec perl -Tlne '
  if ($. == $ENV{x}) {close ARGV}
  elsif (eof) {print $ARGV; close ARGV}' {} +

Substitua printpor unlinkse estiver feliz.

Stéphane Chazelas
fonte
1. Qual é a última sh? 2. É wc -l < "$file"mais rápido que wc -l "$file"? 3. Como sh sabe o valor de $x, definido no shell Bash de chamada?
3
@tomas, o último shé o que existe nesse script embutido $0, para ser usado para mensagens de erro, por exemplo. wc -l "$file"imprimiria o nome do arquivo que não queremos aqui e seria executado wcmesmo que o arquivo não possa ser aberto. $xé exportado para find( x=10 find...) que por si só o passa sh.
Stéphane Chazelas
Obrigado! Mas acho que esse erro que recebo no OSX significa que minha versão do Bash não suporta o sinalizador legível? find: -readable: unknown primary or operator.
durrrutti
1
@ durrrutti, isso não se resume a bash. bashé apenas um intérprete de linha de comando, mas da findimplementação. -readableé uma extensão GNU, não está disponível no OS / X find. É usado apenas para limitar os arquivos que são legíveis (você não seria capaz de obter a contagem de linhas para arquivos não legíveis). Você pode omiti-lo para o primeiro, então você receberá apenas mensagens de erro ao abrir os arquivos wcpara os que não são legíveis.
Stéphane Chazelas
@ StéphaneChazelas, esta resposta é tão complicada que eu fico imaginando: eu perdi alguns casos extremos com minha resposta? :)
Caractere curinga
2

Por uma questão de integridade, 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) 
zepelim
fonte