2011-09-09 14 views
30

Vorrei rimuovere tutte le righe vuote da un file, ma solo quando sono alla fine/all'inizio di un file (cioè, se ci sono non ci sono linee non vuote prima di esse, all'inizio, e se non ci sono linee non vuote dopo di esse, alla fine.)Rimozione di trailing/avvio di newline con sed, awk, tr e friends

È possibile ciò al di fuori di un linguaggio di scripting completo come Perl o Ruby? Preferirei farlo con sed o awk se possibile. Fondamentalmente, qualsiasi strumento UNIX-y leggero e ampiamente disponibile andrebbe bene, specialmente uno che posso imparare di più rapidamente (Perl, quindi, non incluso.)

risposta

43

Da Useful one-line scripts for sed:

# Delete all leading blank lines at top of file (only). 
sed '/./,$!d' file 

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

Pertanto, per rimuovere entrambe le iniziali e finali righe vuote da un file, può combinare quanto sopra comandi in:

sed -e :a -e '/./,$!d;/^\n*$/{$d;N;};/\n$/ba' file 
+0

Secondo la nota di quel sito, lo script trailing-blank-line non funzionerà per gsed 3.02. *. Funzionerà: 'sed -e: a -e '/^\ n * $/{ $ d; N; ba '-e'} '' – BryanH

+0

Se fallisce, prova a fare dos2unix in precedenza.Questo riferimento è un insieme completo di esempi utili. –

+0

Questo non è appropriato per file di grandi dimensioni – ExceptionSlayer

1

In bash, utilizzando cat, wc, grep, sed, coda e testa:

# number of first line that contains non-empty character 
i=`grep -n "^[^\B*]" <your_file> | sed -e 's/:.*//' | head -1` 
# number of hte last one 
j=`grep -n "^[^\B*]" <your_file> | sed -e 's/:.*//' | tail -1` 
# overall number of lines: 
k=`cat <your_file> | wc -l` 
# how much empty lines at the end of file we have? 
m=$(($k-$j)) 
# let strip last m lines! 
cat <your_file> | head -n-$m 
# now we have to strip first i lines and we are done 8-) 
cat <your_file> | tail -n+$i 

Uomo, vale sicuramente la pena imparare un linguaggio di programmazione "reale" per evitare quella bruttezza!

+0

Bene * quella * parte è abbastanza facile con sed! Lasciami giocare con esso, e prova a tornare qui con un comando completato. Grazie! – ELLIOTTCABLE

+0

In realtà, ciò non funzionerà per le ultime righe, perché rimuove * tutte * le nuove righe nella fase grep, eliminando il conteggio alla fine./= – ELLIOTTCABLE

+0

No: dopo aver eseguito questi comandi hai ancora il tuo file originale. Secondo comando stampa tutti i pre-pacchi non vuoti con i loro numeri di riga. Quindi avrai il numero dell'ultimo non vuoto. –

1

Utilizzando bash

$ filecontent=$(<file) 
$ echo "${filecontent/$'\n'}" 
+0

Rimuove solo una singola riga vuota dall'inizio e nessuna dalla fine. –

+3

@me_and: Mentre sei corretto solo rimuovendo _one_ una linea vuota dall'inizio, questo in realtà elimina tutte le nuove righe finali, perché la sostituzione di comando ('$ ( mklement0

+0

@ mklement0: Huh, così fa. Impara una cosa nuova ogni giorno! –

2

utilizzando awk:

awk '{a[NR]=$0;if($0 && !s)s=NR;} 
    END{e=NR; 
     for(i=NR;i>1;i--) 
      if(a[i]){ e=i; break; } 
     for(i=s;i<=e;i++) 
      print a[i];}' yourFile 
+0

Mi chiedo se c'è un modo per ridurre/refactarlo per gestirlo in un solo passaggio?(Non ho molta familiarità con awk, posso leggere quello che hai scritto, ma non sono sicuro di come rifattarlo.) – ELLIOTTCABLE

+0

in pratica si tratta di un comando a una riga, l'unica parte dinamica è 'yourFile', che è il nome del file che si desidera elaborare. perché hai bisogno di ridurre/refactoring? – Kent

+1

Perché è lungo e complesso, anche se non ha bisogno di una nuova riga? Diversi per cicli, più istruzioni; complessità inutile. (= – ELLIOTTCABLE

3

Ecco una soluzione in un solo passaggio in awk: esso non inizia a stampare fino a quando non vede una linea non vuota e quando vede una riga vuota, si ricorda fino a quando la riga successiva non vuota

awk ' 
    /[[:graph:]]/ { 
     # a non-empty line 
     # set the flag to begin printing lines 
     p=1  
     # print the accumulated "interior" empty lines 
     for (i=1; i<=n; i++) print "" 
     n=0 
     # then print this line 
     print 
    } 
    p && /^[[:space:]]*$/ { 
     # a potentially "interior" empty line. remember it. 
     n++ 
    } 
' filename 

nota, a causa del meccanismo che sto usando per prendere in considerazione le linee vuote/non vuote (con [[:graph:]] e /^[[:space:]]*$/), linee interne con solo spazi bianchi verrà troncato per diventare veramente vuota.

+0

+1 per un single-pass, soluzione single-utility che è anche efficiente in termini di memoria (sebbene, come notato, il suo comportamento differisca leggermente da quello che è stato chiesto) – mklement0

10

Quindi ho intenzione di prendere in prestito parte della risposta di @ dogbane per questo, dal momento che la linea sed per rimuovere le righe vuote che portano è così breve ...

tac is part of coreutils, e inverte un file. Quindi farlo due volte:

tac file | sed -e '/./,$!d' | tac | sed -e '/./,$!d' 

Non è certamente il più efficiente, ma a meno che bisogno efficienza, lo trovo più leggibile rispetto a tutto il resto finora.

+0

Sto lasciando l'originale accettato, ma questo è certamente elegante. di aver imparato a conoscere 'tac'. Che bel nome: D – ELLIOTTCABLE

+1

C'è un caso limite che vale la pena menzionare: se il file non ha un trattino' \ n', l'ultima riga non verrà gestita correttamente: prova 'tac <(printf 'a \ nb') '. Probabilmente, questo comportamento è imperfetto, influenza anche l'equivalente OSX di' tac', 'tail -r'. – mklement0

2

Come indicato in another answer, tac is part of coreutils e inverte un file. Combinando l'idea di farlo due volte con the fact that command substitution will strip trailing new lines, otteniamo

echo "$(echo "$(tac "$filename")" | tac)" 

, che non dipende da sed. È possibile utilizzare echo -n per rimuovere la riga rimanente finale rimanente.

+0

+1 per semplicità (relativa) (anche se a scapito dell'efficienza); Versione OSX (dove 'tac' non è disponibile di default):' echo "$ (echo" $ (tail -r "$ nomefile") "| tail -r)" ' Ho eseguito test per confrontare la velocità relativa di esecuzione con un File da 1 milione di righe per più risposte (non prestato attenzione all'utilizzo della memoria); prima significa più veloce: OSX 10.10: sed (dogbane) mklement0

+1

C'è un caso limite che vale la pena menzionare: se il file non ha un trattino '\ n', l'ultima riga non verrà gestita correttamente: prova' echo "$ (echo" $ (printf 'a \ nb' | tac) "| tac)" '. Questo è inerente al comportamento - discutibilmente imperfetto di 'tac' (e anche' tail -r' su OSX) con input che non termina in '\ n'. – mklement0

+0

Uso di 'echo" $ (echo "$ (cat" $ nomefile ")" | tac) "| tac' corregge il caso limite che @ mklement0 ha menzionato. – rivy

0

A bash soluzione.

Nota: solo utile se il file è sufficientemente piccolo da leggere in memoria in una sola volta.

[[ $(<file) =~ ^$'\n'*(.*)$ ]] && echo "${BASH_REMATCH[1]}" 
  • $(<file) legge l'intero file e taglia trailing a capo, perché la sostituzione di comando ($(....)) implicitamente fa.
  • =~ è di bash espressioni regolari corrispondente operatore, e =~ ^$'\n'*(.*)$ partite opzionalmente eventuali leader ritorni a capo (avidità), e cattura tutto ciò che viene dopo. Nota il potenzialmente confuso $'\n', che inserisce una nuova riga letterale utilizzando ANSI C quoting, perché la sequenza di escape \n non è supportata.
  • Si noti che questo particolare regex corrisponde sempre a corrispondenze, quindi il comando dopo && è sempre eseguito.
  • La variabile di memoria speciale BASH_REMATCH contiene i risultati della corrispondenza regex più recente e l'elemento di matrice [1] contiene ciò che viene catturato (primo e unico) sottoespressione parentesi (gruppo di cattura), che è la stringa di input con qualsiasi nuova riga iniziale rimossa. L'effetto netto è che ${BASH_REMATCH[1]} contiene il contenuto del file di input con le nuove righe iniziali e finali rimosse.
  • Si noti che la stampa con echo aggiunge una singola riga finale finale. Se vuoi evitarlo, usa invece echo -n (o usa il più portatile printf '%s').
0

vorrei introdurre un'altra variante per gawk v4.1 +

result=($(gawk ' 
    BEGIN { 
     lines_count   = 0; 
     empty_lines_in_head = 0; 
     empty_lines_in_tail = 0; 
    } 
    /[^[:space:]]/ { 
     found_not_empty_line = 1; 
     empty_lines_in_tail = 0; 
    } 
    /^[[:space:]]*?$/ { 
     if (found_not_empty_line) { 
      empty_lines_in_tail ++; 
     } else { 
      empty_lines_in_head ++; 
     } 
    } 
    { 
     lines_count ++; 
    } 
    END { 
     print (empty_lines_in_head " " empty_lines_in_tail " " lines_count); 
    } 
' "$file")) 

empty_lines_in_head=${result[0]} 
empty_lines_in_tail=${result[1]} 
lines_count=${result[2]} 

if [ $empty_lines_in_head -gt 0 ] || [ $empty_lines_in_tail -gt 0 ]; then 
    echo "Removing whitespace from \"$file\"" 
    eval "gawk -i inplace ' 
     { 
      if (NR > $empty_lines_in_head && NR <= $(($lines_count - $empty_lines_in_tail))) { 
       print 
      } 
     } 
    ' \"$file\"" 
fi 
0

@dogbane ha una risposta semplice piacevole per la rimozione di importanti linee vuote. Ecco un semplice comando awk che rimuove solo le righe finali. Usalo con il comando sed di @ dogbane per rimuovere sia spazi iniziali che finali.

awk '{ LINES=LINES $0 "\n"; } /./ { printf "%s", LINES; LINES=""; }' 

Questa operazione è piuttosto semplice.

  • Aggiungere ogni riga a un buffer mentre lo leggiamo.
  • Per ogni riga che contiene un carattere, stampare il contenuto del buffer e quindi cancellarlo.

Quindi le uniche cose che vengono bufferizzate e mai visualizzate sono spazi vuoti finali.

Ho usato printf invece di stampare per evitare l'aggiunta automatica di una nuova riga, poiché sto usando newline per separare già le righe nel buffer.

2

Ecco una versione sed adattata, che considera anche "vuote" quelle linee con solo spazi e tabulazioni su di essa.

sed -e :a -e '/[^[:blank:]]/,$!d; /^[[:space:]]*$/{ $d; N; ba' -e '}' 

E 'fondamentalmente la versione risposta accettata (considerando BryanH commenti), ma il punto . nel primo comando è stato cambiato a [^[:blank:]] (tutto ciò che non vuota) e il \n all'interno del secondo indirizzo di comando è stato cambiato a [[:space:]] per consentire newlines, spazi un tab.

Una versione alternativa, senza utilizzare le classi POSIX, ma il tuo sed deve supportare l'inserimento di \t e \n all'interno di […]. GNU sed fa, BSD sed no.

sed -e :a -e '/[^\t ]/,$!d; /^[\n\t ]*$/{ $d; N; ba' -e '}' 

Testing:

prompt$ printf '\n \t \n\nfoo\n\nfoo\n\n \t \n\n' 



foo 

foo 



prompt$ printf '\n \t \n\nfoo\n\nfoo\n\n \t \n\n' | sed -n l 
$ 
\t $ 
$ 
foo$ 
$ 
foo$ 
$ 
\t $ 
$ 
prompt$ printf '\n \t \n\nfoo\n\nfoo\n\n \t \n\n' | sed -e :a -e '/[^[:blank:]]/,$!d; /^[[:space:]]*$/{ $d; N; ba' -e '}' 
foo 

foo 
prompt$ 
1

Per una versione non ricorsiva efficiente della striscia nuove righe finali (compresi i caratteri "bianchi") ho sviluppato questo script sed.

sed -n '/^[[:space:]]*$/ !{x;/\n/{s/^\n//;p;s/.*//;};x;p;}; /^[[:space:]]*$/H' 

usa il buffer hold per memorizzare tutte le righe vuote e li stampa solo dopo che trova una riga non vuota. Se qualcuno vuole solo i ritorni a capo, è sufficiente per sbarazzarsi delle due [[:space:]]* parti:

sed -n '/^$/ !{x;/\n/{s/^\n//;p;s/.*//;};x;p;}; /^$/H' 

Ho provato un semplice confronto delle prestazioni con il noto script ricorsivo

sed -e :a -e '/^\n*$/{$d;N;};/\n$/ba' 

su un 3MB file con 1 MB di righe vuote casuali attorno a un testo base64 casuale.

shuf -re 1 2 3 | tr -d "\n" | tr 123 " \t\n" | dd bs=1 count=1M > bigfile 
base64 </dev/urandom | dd bs=1 count=1M >> bigfile 
shuf -re 1 2 3 | tr -d "\n" | tr 123 " \t\n" | dd bs=1 count=1M >> bigfile 

Lo script di streaming ha impiegato circa 0,5 secondi per completare, il ricorsivo non si è concluso dopo 15 minuti. Win :)

Per completezza della risposta, le linee guida che eliminano la scrittura sed stanno già andando bene. Usa il più adatto a te.

sed '/[^[:blank:]]/,$!d' 
sed '/./,$!d' 
Problemi correlati