Remova quaisquer linhas ou linhas em branco finais com espaços em branco do final do arquivo

3

Eu quero excluir todas as linhas e linhas em branco com espaços (se houver alguma (APENAS na parte inferior do arquivo)) e, em seguida, remover mais uma linha (também SOMENTE da parte inferior do arquivo). / p>

Eu tenho este código:

while [[ "$last_line" =~ $ ]] || [[ "$last_line" =~ ^[[:space:]]+$ ]]
do
    sed -i -e '${/$/d}' "./file.txt"
done
    sed -i -e '${/$/d}' "./file.txt"

Por algum motivo, o loop não para e elimina tudo no arquivo. Qual é o problema?

    
por jackson 05.08.2015 / 13:32

4 respostas

4

Esta tarefa é mais facilmente realizada processando as linhas do arquivo na ordem oposta.

tac infile | awk 'flag {print} {if(NF) flag=1}' | tac | sponge infile

Como apontado nos comentários de Malte Skoruppa e zwets, o Ubuntu não vem com o pacote moreutils pré-instalado, que contém sponge ; uma solução alternativa é usar uma substituição de comando dentro de um travessão para ler o arquivo de entrada, para que, sendo a substituição do comando processada primeiro, o arquivo seja seguro para ser truncado pelo segundo comando tac :

<<<"$(< infile)" tac | awk 'flag {print} {if(NF) flag=1}' | tac > infile
  • tac infile : ... faz o oposto de cat infile (!): imprime o arquivo em stdout invertendo a ordem da linha;
  • awk [...] : processa o arquivo;
  • tac : ... faz o oposto de cat (!): imprime o arquivo em stdout invertendo a ordem da linha;
  • sponge infile : envia para infile apenas quando o lado esquerdo do canal terminou sua execução, para evitar truncar infile antes de ser lido pelo primeiro comando tac ;

% de colapso do comandoawk:

  • flag {print} : se flag estiver definido, a linha será impressa; flag não será definido até que um registro cujo NF corresponda a um número maior que 0 seja processado, portanto, até que um registro cujo valor NF corresponda a um número maior que 0 não encontrado, a% O comandoprint será ignorado;
  • {if(NF) flag=1} : se enquanto flag ainda não foi definido um registro cujo NF corresponde a um número maior que 0 é processado, ele não será impresso e flag será definido como 1 , então o primeiro registro cujo valor NF corresponde a um número maior que 0 não será impresso;

Teste em um arquivo de teste (lembre-se de que a linha 4 e a linha 7 contêm 5 espaços, enquanto a linha 5 e a linha 8 estão vazias):

user@debian ~ % cat infile                                           
line1
line2
line3


line6


user@debian ~ % tac infile | awk 'flag {print} {if(NF) flag=1}' | tac
line1
line2
line3


user@debian ~ % 

A linha 7 e a linha 8 foram removidas porque estavam ambas no final do arquivo, contendo apenas espaços (linha 7) ou contendo nada (linha 8); a linha 6 foi excluída porque foi a primeira, lendo as linhas do arquivo na ordem oposta, para ter pelo menos um campo (portanto, não estando vazio ou contendo apenas espaços)

    
por kos 12.08.2015 / 06:22
3

Seu script deve funcionar se for corrigido da seguinte forma:

while
 last_line=$(tail -1 "./file.txt")
 [[ "$last_line" =~ ^$ ]] || [[ "$last_line" =~ ^[[:space:]]+$ ]]
do
 sed -i '$d' "./file.txt"
done

Seu script teve dois problemas principais: (1) você nunca atualizou $last_line , então a guarda do loop sempre avaliaria a mesma coisa; (2) o seu teste [[ "$last_line" =~ $ ]] correspondeu a qualquer linha, uma vez que qualquer linha tem um fim. (Esta é a razão pela qual o seu script esvaziou seu arquivo completamente). Você provavelmente deseja combinar com ^$ , que corresponde apenas a linhas vazias. Além disso, simplifiquei o comando sed para excluir a última linha no corpo do loop (simplesmente $d faz o trabalho).

No entanto, esse script é desnecessariamente complicado. sed está lá apenas para esse tipo de coisa! Este one-liner fará o mesmo que o script acima:

sed -i ':a;/^[ \n]*$/{$d;N;ba}' ./file.txt

Mais ou menos,

  1. Corresponda a linha atual com ^[ \n]*$ . (isto é, só pode conter espaços em branco e novas linhas)
  2. Se não corresponder, basta imprimi-lo. Leia na próxima linha e continue na etapa 1.
  3. Se corresponder,
    • Se estivermos no final do arquivo, exclua-o.
    • Se não estivermos no final do arquivo, anexe a próxima linha à linha atual, inserindo um caractere de nova linha entre os dois e retorne à etapa 1 com essa nova linha mais longa .

Existem muitos tutoriais sed incríveis na Internet. Por exemplo, posso recomendar este . Feliz aprendizado! : -)

Atualização: E, claro, se você também quiser remover a última linha (não em branco) do arquivo depois de ter truncado as linhas em branco à direita, basta usar outro sed -i '$d' ./file.txt após ou o seu script ou o one-liner acima. Eu intencionalmente não quis incluir isso no sed one-liner, pois achei que remover linhas em branco finais era um pedaço de código bastante reutilizável que pode ser interessante para outras pessoas; mas remover a última linha não vazia é realmente específico para o seu caso de uso, e de qualquer maneira trivial depois que você removeu as linhas em branco à direita.

    
por Malte Skoruppa 12.08.2015 / 03:35
0

Editar

Então, originalmente, perdi o ponto de que o OP deseja remover apenas a última linha em branco, o que a minha solução original também faz. No entanto, aqui está a versão que remove apenas a última e se está em branco.

awk -v numlines=$(wc -l file2|cut -f1 -d' ') 'NR < numlines; END {if (NF) print }' file2

O que o código faz é bastante simples - obtenha o número de linhas e imprima todas as linhas até o último. No último, verificamos se a linha contém algum campo; se houver algum texto, a NF avalia para um inteiro (verdadeiro) imprimindo assim a última linha, e se não houver nada ou apenas espaços - a NF avalia a zero (falso) e não imprime mais nada.

Quanto a remover mais uma linha, head -n -1 será suficiente.

Abaixo está a pequena demonstração. A nova linha final é designada com $ e o prompt *$

*$ cat -A file2                                                                                                                                      
212$
1231$
$
324234$
213$
$
*$ awk -v numlines=$(wc -l file2|cut -f1 -d' ')  'NR < numlines ; END {if ( NF ) print }' file2 | head -n -1                                         
212
1231

324234

Original

awk solution.

awk 'NF' file1 > /tmp/tmpfile && cat /tmp/tmpfile > file1

Aqui, usamos a variável Number of Fields como teste para impressão. Para linhas em branco, o número de campos é zero, portanto, as linhas em branco avaliadas como falsas não serão impressas. Agora, a menos que sua awk versão suporte a edição em linha (que é gnu awk or gawk , eu acho), você tem que redirecionar a saída para saída temporária e voltar para o arquivo original com cat , como eu fiz aqui

A variação no tema seria usar o regex para testar se as linhas contêm alguns dados específicos, como dígitos ou caracteres alfanuméricos, por exemplo,

awk '$0~/[[:digit:]]||[[:alpha:]]/ ' file1 > /tmp/tmpfile && cat /tmp/tmpfile > file1

    
por Sergiy Kolodyazhnyy 12.08.2015 / 04:45
0

Pelo que entendi

  • Remover linhas vazias do final do arquivo
  • Remover linhas com espaços do final do arquivo
  • E então remova mais uma linha

Usando awk e tac

tac foo | awk '! non_empty && ! /^$/ && ! /[ \t]/ {non_empty = 1} non_empty {skip++} skip > 1 {print}' | tac

Mais variações abaixo do exemplo…

Exemplo

% cat -n foo                                                                
     1  line1
     2  line2
     3  line3
     4  
     5  
     6  line6
     7  line 7 
     8  

% tac foo | awk '! non_empty && ! /^$/ && ! /[ \t]/ {non_empty = 1} non_empty {skip++} skip > 1 {print}' | tac > bar

% cat -n bar
     1  line1
     2  line2
     3  line3
     4  
     5  

Para remover apenas as linhas em branco no final do arquivo

tac foo | awk '! non_empty && ! /^$  {non_empty = 1} non_empty {print}' | tac

Para remover as linhas em branco no final e mais uma linha

tac foo | awk '! non_empty && ! /^$/ &&  {non_empty = 1} non_empty {skip++} skip > 1 {print}' | tac
    
por A.B. 12.08.2015 / 15:46