Come posso eliminare una nuova riga finale in bash?


10

Sto cercando qualcosa che si comporti come quello di Perl chomp. Sto cercando un comando che stampa semplicemente il suo input, meno l'ultimo carattere se è una nuova riga:

$ printf "one\ntwo\n" | COMMAND_IM_LOOKING_FOR ; echo " done"
one
two done
$ printf "one\ntwo" | COMMAND_IM_LOOKING_FOR ; echo " done"
one
two done

(La sostituzione dei comandi in Bash e Zsh elimina tutte le nuove righe finali, ma sto cercando qualcosa che elimini al massimo una nuova riga finale.)

Risposte:


9

Questo dovrebbe funzionare:

printf "one\ntwo\n" | awk 'NR>1{print PREV} {PREV=$0} END{printf("%s",$0)}' ; echo " done"

Lo script stampa sempre la riga precedente anziché quella corrente e l'ultima riga viene trattata in modo diverso.

Cosa fa in modo più dettagliato:

  1. NR>1{print PREV} Stampa la riga precedente (tranne la prima volta).
  2. {PREV=$0}Memorizza la riga corrente in PREVvariabile.
  3. END{printf("%s",$0)} Infine, stampa l'ultima riga senza interruzione di riga.

Si noti inoltre che ciò rimuoverà al massimo una riga vuota alla fine (nessun supporto per la rimozione "one\ntwo\n\n\n").


15

Puoi usare perlsenza chomp:

$ printf "one\ntwo\n" | perl -0 -pe 's/\n\Z//'; echo " done"
one
two done

$ printf "one\ntwo" | perl -0 -pe 's/\n\Z//'; echo " done"
one
two done

Ma perché non usare chompse stesso:

$ printf "one\ntwo\n" | perl -pe 'chomp if eof'; echo " done"

4

Se vuoi un equivalente esatto chomp, il primo metodo che mi viene in mente è la fantastica soluzione che LatinSuD ha già pubblicato . Aggiungerò alcuni altri metodi che non implementano chompma implementano alcune attività comuni per cui chompviene spesso utilizzato.

Quando si inserisce del testo in una variabile, tutte le righe alla fine vengono rimosse. Quindi tutti questi comandi producono lo stesso output a riga singola:

echo "$(printf 'one\ntwo') done"
echo "$(printf 'one\ntwo\n') done"
echo "$(printf 'one\ntwo\n\n') done"
echo "$(printf 'one\ntwo\n\n\n\n\n\n\n\n\n\n') done"

Se si desidera aggiungere del testo all'ultima riga di un file o dell'output di un comando, sedpuò essere utile. Con GNU sed e la maggior parte delle altre implementazioni moderne, questo funziona anche se l'input non termina con una nuova riga¹; tuttavia, ciò non aggiungerà una nuova riga se non ce n'era già una.

sed '$ s/$/ done/'

¹ Tuttavia, ciò non funziona con tutte le implementazioni di sed: sed è uno strumento di elaborazione del testo e un file che non è vuoto e non termina con un carattere di nuova riga non è un file di testo.


Questo non è esattamente equivalente a chomp, poiché chompelimina solo al massimo una nuova riga finale.
Flimm,

@Flimm Sì, l'equivalente esatto più ovvio a chompsarebbe la soluzione awk che LatinSuD ha già pubblicato. Ma in molti casi chompè solo uno strumento per fare un lavoro e fornisco modi per svolgere alcune attività comuni. Vorrei aggiornare la mia risposta per chiarire questo.
Gilles 'SO- smetti di essere malvagio' l'

1

Un altro perlapproccio. Questo legge l'intero input in memoria, quindi potrebbe non essere una buona idea per grandi quantità di dati (usare cuonglm o l' awkapproccio per quello):

$ printf "one\ntwo\n" | perl -0777pe 's/\n$//'; echo " done"
one
two done

Grazie, @ StéphaneChazelas, risolto. Per qualche ragione, questo interruttore mi confonde sempre !
terdon

0

L'ho preso da un repository github da qualche parte, ma non riesco a trovare dove

eliminare-finali-blank-lines-sed

#!/bin/bash
#
# Delete all trailing blank lines.
# From http://sed.sourceforge.net/sed1line.txt
#
# Version: 1.3.0
# Created: 2011-01-02
# Updated: 2015-01-25
# Contact: Joel Parker Henderson (joel@joelparkerhenderson.com)
# License: GPL
##
set -euf
sed -e :a -e '/^\n*$/{$d;N;ba' -e '}'

0

astratto

Stampa righe senza newline, aggiungi una nuova riga solo se è presente un'altra riga da stampare.

$ printf 'one\ntwo\n' | 

     awk '{ printf( "%s%s" , NR>1?"\n":"" , $0 ) }';   echo " done"

one
two done

Altre soluzioni

Se stessimo lavorando con un file, possiamo semplicemente troncare un carattere da esso (se termina su una nuova riga):

removeTrailNewline () {[[$ (tail -c 1 "$ 1")]] || troncare -s-1 "$ 1"; }

Questa è una soluzione rapida in quanto deve leggere solo un carattere dal file e quindi rimuoverlo direttamente ( truncate) senza leggere l'intero file.

Tuttavia, mentre si lavora con i dati di stdin (un flusso) i dati devono essere letti, tutti. E, viene "consumato" non appena viene letto. Nessun backtrack (come con troncato). Per trovare la fine di un flusso dobbiamo leggere fino alla fine del flusso. A quel punto, non è possibile tornare indietro nel flusso di input, i dati sono già stati "consumati". Ciò significa che i dati devono essere archiviati in una qualche forma di buffer fino a quando non abbiniamo la fine del flusso e quindi facciamo qualcosa con i dati nel buffer.

La soluzione più ovvia è convertire lo stream in un file ed elaborare quel file. Ma la domanda richiede una sorta di filtro del flusso. Non sull'uso di file aggiuntivi.

variabile

La soluzione ingenua sarebbe quella di catturare l'intero input in una variabile:

FilterOne(){ filecontents=$(cat; echo "x");        # capture the whole input
             filecontents=${filecontents%x};       # Remove the "x" added above.
             nl=$'\n';                             # use a variable for newline.
             printf '%s' "${filecontents%"$nl"}";  # Remove newline (if it exists).
       }

printf 'one\ntwo'     | FilterOne ; echo 1done
printf 'one\ntwo\n'   | FilterOne ; echo 2done
printf 'one\ntwo\n\n' | FilterOne ; echo 3done

memoria

È possibile caricare un intero file in memoria con sed. In sed è impossibile evitare la nuova riga finale sull'ultima riga. GNU sed potrebbe evitare di stampare una nuova riga finale, ma solo se il file sorgente è già mancante. Quindi, no, la semplice sed non può aiutare.

Tranne che su GNU awk con l' -zopzione:

sed -z 's/\(.*\)\n$/\1/'

Con awk (qualsiasi awk), assorbi l'intero flusso e printfsenza la nuova riga finale.

awk '    { content = content $0 RS } 
     END { gsub( "\n$", "", content ); printf( "%s", content ) }
    '

Il caricamento di un intero file in memoria potrebbe non essere una buona idea, potrebbe consumare molta memoria.

Due righe in memoria

In awk, possiamo elaborare due righe per loop memorizzando la riga precedente in una variabile e stampando quella corrente:

awk 'NR>1{print previous} {previous=$0} END {printf("%s",$0)}'

Elaborazione diretta

Ma potremmo fare di meglio.

Se stampiamo la riga attuale senza una nuova riga e stampiamo una nuova riga solo quando esiste una riga successiva, elaboriamo una riga alla volta e l'ultima riga non avrà una riga nuova finale:

awk 'NR == 1 {printf ("% s", $ 0); successivo}; {printf ("\ n% s", $ 0)} '

Oppure, scritto in qualche altro modo:

awk 'NR>1{ print "" }; { printf( "%s", $0 ) }'

O:

awk '{ printf( "%s%s" , NR>1?"\n":"" , $0 ) }'

Così:

$ printf 'one\ntwo\n' | awk '{ printf( "%s%s" , NR>1?"\n":"" , $0 ) }'; echo " done"
one
two done
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.