Como truncar arquivo por linhas?

13

Eu tenho um grande número de arquivos, alguns dos quais são muito longos. Gostaria de truncá-los para um determinado tamanho, se eles forem maiores, removendo o final do arquivo. Mas eu só quero remover linhas inteiras. Como posso fazer isso? Parece o tipo de coisa que seria tratada pela cadeia de ferramentas Linux, mas não sei o comando certo.

Por exemplo, digamos que eu tenho um arquivo de 120.000 bytes com linhas de 300 bytes e estou tentando truncá-lo para 10.000 bytes. As primeiras 33 linhas devem permanecer (9900 bytes) e o restante deve ser cortado. Não quero cortar exatamente 10.000 bytes, pois isso deixaria uma linha parcial.

Obviamente, os arquivos têm diferentes comprimentos e as linhas não têm o mesmo comprimento.

Idealmente, os arquivos resultantes seriam reduzidos um pouco mais do que um pouco mais (se o ponto de interrupção estiver em uma longa linha), mas isso não for muito importante, poderá ser um pouco mais longo se for mais fácil. Gostaria que as alterações fossem feitas diretamente nos arquivos (bem, possivelmente o novo arquivo foi copiado em outro lugar, o original foi excluído e o novo arquivo foi movido, mas é o mesmo do POV do usuário). Uma solução que redireciona dados para vários lugares e depois convida a possibilidade de corromper o arquivo e eu gostaria de evitar isso ...

Charles
fonte
Excluiu minha resposta ... Acho que o tamanho do arquivo em bytes não estava muito claro, desculpe. Talvez você possa editar sua pergunta e esclarecer essa parte (por exemplo, com um exemplo)?
slhck 24/07/12
@slhck: Lamento ver você perder o representante só porque não estava claro ... deixe-me ver se consigo consertar isso.
Charles Charles
Não se preocupe, eu deveria apenas ter perguntado, desculpe :) #
slhck

Respostas:

1

A complexidade sed/ wcpode ser evitada nas respostas anteriores se awkfor usada. Usando o exemplo fornecido do OP (mostrando linhas completas antes de 10000 bytes):

awk '{i += (length() + 1); if (i <= 10000) print $ALL}' myfile.txt

Também mostrando a linha completa que contém 10000º byte, se esse byte não estiver no final da linha:

awk '{i += (length() + 1); print $ALL; if (i >= 10000) exit}' myfile.txt

A resposta acima assume:

  1. O arquivo de texto é do terminador de linha Unix ( \n). Para arquivos de texto do Dos / Windows ( \r\n), altere length() + 1paralength() + 2
  2. O arquivo de texto contém apenas caracteres de byte único. Se houver caracteres multibyte (como no ambiente unicode), defina o ambiente LC_CTYPE=Cpara forçar a interpretação no nível de bytes.
Abel Cheung
fonte
14

A sedabordagem é boa, mas fazer um loop em todas as linhas não é. Se você souber quantas linhas deseja manter (para ter um exemplo, eu uso 99 aqui), é possível fazer o seguinte:

sed -i '100,$ d' myfile.txt

Explicação: sedé um processador de expressão regular. Com a opção -ifornecida, ele processa um arquivo diretamente ("inline") - em vez de apenas lê-lo e gravar os resultados na saída padrão. 100,$significa apenas "da linha 100 até o final do arquivo" - e é seguido pelo comando d, que você provavelmente adivinhou corretamente como "delete". Portanto, em resumo, o comando significa: "Exclua todas as linhas da linha 100 até o final do arquivo do myfile.txt". 100 é a primeira linha a ser excluída, pois você deseja manter 99 linhas.

Editar: se, por outro lado, houver arquivos de log onde você deseja manter, por exemplo, as últimas 100 linhas:

[ $(wc -l myfile.txt) -gt 100 ] && sed -i "1,$(($(wc -l myfile.txt|awk '{print $1}') - 100)) d" myfile.txt

O que está acontecendo aqui:

  • [ $(wc -l myfile.txt) -gt 100 ]: faça o seguinte apenas se o arquivo tiver mais de 100 linhas
  • $((100 - $(wc -l myfile.txt|awk '{print $1}'))): calcula o número de linhas a serem excluídas (ou seja, todas as linhas do arquivo, exceto as (últimas) 100 a serem mantidas)
  • 1, $((..)) d: remova todas as linhas da primeira para a linha calculada

EDIT: como a pergunta foi editada para fornecer mais detalhes, incluirei essas informações adicionais também com a minha resposta. Os fatos adicionados são:

  • um tamanho específico deve permanecer com o arquivo (10.000 bytes)
  • cada linha tem um tamanho específico em bytes (300 bytes no exemplo)

A partir desses dados, é possível calcular o número de linhas para permanecer como "/", o que com o exemplo significaria 33 linhas. O termo do shell para o cálculo: $((size_to_remain / linesize))(pelo menos no Linux usando Bash, o resultado é um número inteiro). O comando ajustado agora seria:

# keep the start of the file (OPs question)
sed -i '34,$ d' myfile.txt
# keep the end of the file (my second example)
[ $(wc -l myfile.txt) -gt 33 ] && sed -i "1,33 d" myfile.txt

Como os tamanhos são conhecidos antecipadamente, não há mais necessidade de um cálculo incorporado ao sedcomando. Mas, para flexibilidade, dentro de algum script shell, podemos usar variáveis.

Para processamento condicional com base no tamanho do arquivo, pode-se usar a seguinte construção de "teste":

[ "$(ls -lk $file | awk ' {print $5}')" -gt 100 ] &&

o que significa: "se o tamanho $fileexceder 100kB, faça ..." ( ls -lklista o tamanho do arquivo em kB na posição 5, portanto, awké usado para extrair exatamente isso).

Izzy
fonte
O OP deseja cortar o arquivo com base em um determinado tamanho de byte - não apenas no comprimento em termos de linhas. Eu apaguei minha resposta envolvendo head -n.
slhck 24/07/12
@slhck Obrigado pela notificação. Sim, o OP acabou de editar sua pergunta para tornar a intenção mais clara. Como ele tem meios de calcular quantos bytes cada linha possui, minha resposta permanece válida em princípio - pois ele pode calcular o número de linhas a permanecer e, em seguida, usar minha abordagem para manipular os arquivos. Talvez eu faça uma breve observação sobre isso na minha resposta.
Izzy
Não - os tamanhos não são conhecidos antecipadamente. Esse foi um exemplo. Cada arquivo terá um tamanho diferente e as linhas terão um comprimento irregular. Alguns arquivos não precisam ser truncados.
Charles
Ah, novamente ... Bem, algumas coisas são difíceis de explicar claramente (muitas facetas). Quanto aos arquivos que não precisam ser truncados, isso provavelmente se baseia no tamanho do arquivo? Isso pode ser coberto. Mas se não há um tamanho médio de linha conhecido, essa parte fica difícil - não consigo pensar em uma solução fácil (sem muita sobrecarga) no momento.
Izzy
Tudo o que posso apresentar atualmente envolveria, por exemplo, obter as primeiras n linhas, calcular um comprimento médio com base nelas e usar esse valor. Isso te ajudaria?
Izzy
0

Não encontrei um comando para fazer isso, escrevi um script rápido (não testado):

#!/bin/sh

# Usage: $0 glob.* 25000
# where glob.* is a wildcard pattern and 25000 is the maximum number of bytes.

limit=20000
tmp=/tmp/trim
[[ "$2" == +([0-9]) ]] || limit=$2
limit=`expr $len + 1`
for file in $1;
do
    [[ `wc -c $file` -lt $limit ]] && continue
    head -c $file > $tmp
    sed '$d' $tmp
    $tmp > $file
done
Charles
fonte
-1

Você pode usar o comando linux sed para remover linhas de um arquivo. O comando a seguir exclui a última linha do nome do arquivo.txt:

sed '$d' filename.txt

Com awk ou find, você pode procurar por padrões que correspondam ao seu comando sed. Primeiro você pesquisa com o awk ou encontra os arquivos que deseja encurtar e depois pode remover as linhas com o sed.

kockiren
fonte
-1

Eu fiz algo semelhante com cauda. Para manter apenas as últimas 10.000 linhas neste caso:

TMP=$(tail -n 10000 /path/to/some/file 2>/dev/null) && echo "${TMP}" > /path/to/some/file
Bill M
fonte