Come rimuovere più newline su EOF?


25

Ho dei file che finiscono in una o più righe e che dovrebbero terminare in una riga. Come posso farlo con gli strumenti Bash / Unix / GNU?

Esempio di file errato:

1\n
\n
2\n
\n
\n
3\n
\n
\n
\n

Esempio di file corretto:

1\n
\n
2\n
\n
\n
3\n

In altre parole: dovrebbe esserci esattamente una nuova riga tra l'EOF e l'ultimo carattere non newline del file.

Implementazione di riferimento

Leggi il contenuto del file, taglia una nuova riga finché non ci sono altre due righe alla fine, riscrivilo:

#! /bin/python

import sys

with open(sys.argv[1]) as infile:
    lines = infile.read()

while lines.endswith("\n\n"):
    lines = lines[:-1]

with open(sys.argv[2], 'w') as outfile:
    for line in lines:
        outfile.write(line)

Chiarimento: ovviamente, le tubazioni sono consentite, se è più elegante.

Risposte:


16
awk '/^$/ {nlstack=nlstack "\n";next;} {printf "%s",nlstack; nlstack=""; print;}' file

2
+1: le soluzioni di awk sono (quasi) sempre eleganti e leggibili!
Olivier Dulac il

@OlivierDulac Effettivamente. Quando ho visto la sedproposta ho pensato a OMG ...
Hauke ​​Laging,

1
questo non funziona su OSX Mavericks usando l'ultimo awk disponibile di Homebrew. Errori con awk: illegal statement. brew install mawke cambiando il comando in mawkfunziona però.
tjmcewan,

@noname Non capisco nemmeno la domanda ...
Hauke ​​Laging,

Qualsiasi awk in cui lo script non funziona è un awk gravemente interrotto: smetti di usarlo e ottieni un nuovo awk perché se non può farlo, chissà quale altra rottura ha.
Ed Morton,

21

Da utili script a una riga per sed .

# Delete all trailing blank lines at end of file (only).
sed -e :a -e '/^\n*$/{$d;N;};/\n$/ba' file

4
Grazie, ho usato il seguente per farlo sul posto per più file: find . -type f -name '*.js' -exec sed --in-place -e :a -e '/^\n*$/{$d;N;};/\n$/ba' {} \;
jakub.g

@ jakub.g sul posto e ricorsivo è esattamente ciò di cui avevo bisogno. grazie.
Buttle Butkus,

Per aggiungere all'eccellente commento di @ jakub.g puoi invocare il comando in questo modo su OS X:find . -type f -name '*.js' -exec sed -i '' -e :a -e '/^\n*$/{$d;N;};/\n$/ba' {} \;
davejagoda,

18

Dal momento che hai già le risposte con gli strumenti più adatti sed e awk; potresti trarre vantaggio dal fatto che si $(< file)stacca dalle righe vuote finali.

a=$(<file); printf '%s\n' "$a" > file

Quell'hack economico non funzionerebbe per rimuovere le righe vuote finali che possono contenere spazi o altri caratteri non stampabili, solo per rimuovere le righe vuote finali. Inoltre non funzionerà se il file contiene byte null.

Nelle shell diverse da bash e zsh, usare al $(cat file)posto di $(<file).


+1 per sottolineare cosa mi sembra un bug: $ (<file) non sta davvero leggendo il file? perché scarta le nuove righe finali? (sì, l'ho appena provato, grazie per averlo sottolineato!)
Olivier Dulac,

2
@OlivierDulac $()scarta le nuove righe finali. Questa è una decisione di progettazione. Presumo che ciò faciliterà l'integrazione in altre stringhe: echo "On $(date ...) we will meet."sarebbe male con la newline che alla fine quasi tutti i comandi di shell generano.
Hauke ​​Laging,

@HaukeLaging: buon punto, probabilmente è la fonte di quel comportamento
Olivier Dulac

Ho aggiunto un caso speciale per evitare aggiungendo "\ n" per svuotare i file: [[ $a == '' ]] || printf '%s\n' "$a" >"$file".
davidchambers,

Per rimuovere più newline dall'inizio di un file, inserisci tac nel processo (utilizzo gnu coreutils su Mac, quindi gtac per me):a=$(gtac file.txt); printf '%s\n' "$a" | gtac > file.txt
r_alex_hall


4

Questa domanda è taggata con , ma nessuno ha proposto una edsoluzione.

Eccone uno:

ed -s file <<'ED_END'
a

.
?^..*?+1,.d
w
ED_END

o, equivalentemente,

printf '%s\n' a '' . '?^..*?+1,.d' w | ed -s file

ed ti posizionerà sull'ultima riga del buffer di modifica per impostazione predefinita all'avvio.

Il primo comando ( a) aggiunge una riga vuota alla fine del buffer (la riga vuota nello script di modifica è questa riga e il punto ( .) serve solo per tornare in modalità comando).

Il secondo comando ( ?) cerca la riga precedente più vicina che contiene qualcosa (anche i caratteri dello spazio bianco), quindi elimina tutto alla fine del buffer dalla riga successiva in poi.

Il terzo comando ( w) riscrive il file sul disco.

La riga vuota aggiunta protegge il resto del file dall'eliminazione nel caso in cui non vi siano righe vuote alla fine del file originale.


3

Ecco una soluzione Perl che non richiede la lettura di più di una riga alla volta:

my $n = 0;
while (<>) {
    if (/./) {
        print "\n" x $n, $_;
        $n = 0;
    } else {
        $n++;
    }
}

o, come una linea:

perl -ne 'if (/./) { print "\n" x $n, $_; $n = 0 } else { $n++ }'

Questo legge il file una riga alla volta e controlla ogni riga per vedere se contiene un carattere non di nuova riga. In caso contrario, incrementa un contatore; in tal caso, stampa il numero di nuove righe indicate dal contatore, seguito dalla riga stessa, quindi reimposta il contatore.

Tecnicamente, persino il buffering di una singola riga in memoria non è necessario; sarebbe possibile risolvere questo problema utilizzando una quantità costante di memoria leggendo il file in blocchi di lunghezza fissa ed elaborandolo carattere per carattere utilizzando una macchina a stati. Tuttavia, sospetto che sarebbe inutilmente complicato per il tipico caso d'uso.


1

Se il tuo file è abbastanza piccolo da essere assorbito dalla memoria, puoi usarlo

perl -e 'local($/);$f=<>; $f=~s/\n*$/\n/;print $f;' file

0

In python (so che non è quello che vuoi, ma è molto meglio in quanto è ottimizzato e preludio alla versione bash) senza riscrivere il file e senza leggere tutto il file (il che è una buona cosa se il file è molto largo):

#!/bin/python
import sys
infile = open(sys.argv[1], 'r+')
infile.seek(-1, 2)
while infile.read(1) == '\n':
  infile.seek(-2, 1)
infile.seek(1, 1)
infile.truncate()
infile.close()

Si noti che non funziona su file in cui il carattere EOL non è '\ n'.


0

Una versione bash, implementando l'algoritmo python, ma meno efficiente in quanto necessita di molti processi:

#!/bin/bash
n=1
while test "$(tail -n $n "$1")" == ""; do
  ((n++))
done
((n--))
truncate -s $(($(stat -c "%s" "$1") - $n)) "$1"

0

Questo è veloce da scrivere e, se conosci sed, è facile da ricordare:

tac < file | sed '/[^[:blank:]]/,$!d' | tac

Esso utilizza lo script sed per eliminare leader righe vuote da un utile script di linea per sed , a cui fa riferimento Alexey, al di sopra, e tac (cat inverso).

In un test rapido, su un file da 18 MB, 64.000 righe, l'approccio di Alexey è stato più veloce (0,036 vs 0,046 secondi).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.