Come posso cancellare una nuova riga se è l’ultimo carattere in un file?

Ho alcuni file che vorrei eliminare l’ultima riga se è l’ultimo carattere in un file. od -c mi mostra che il comando che eseguo scrive il file con una nuova riga finale:

 0013600 nt > \n 

Ho provato alcuni trucchi con sed, ma il meglio che ho potuto pensare non è il trucco:

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

Qualche idea su come fare questo?

 perl -pe 'chomp if eof' filename >filename2 

o, per modificare il file sul posto:

 perl -pi -e 'chomp if eof' filename 

[Nota del redattore: -pi -e era originariamente -pie , ma, come notato da diversi commentatori e spiegato da @hvd, quest’ultimo non funziona.]

Questo è stato descritto come un “perl blasphemy” sul sito web di awk che ho visto.

Ma, in una prova, ha funzionato.

È ansible sfruttare il fatto che le sostituzioni dei comandi della shell rimuovono i caratteri di fine riga finali :

Semplice modulo che funziona in bash, ksh, zsh:

 printf %s "$(< in.txt)" > out.txt 

Alternativa portatile (conforms a POSIX) (leggermente meno efficiente):

 printf %s "$(cat in.txt)" > out.txt 

Nota:

  • Se in.txt termina con più caratteri di nuova riga, la sostituzione di comando li rimuove tutti , grazie, @Sparhawk. (Non rimuove i caratteri di spaziatura oltre a quelli di fine riga).
  • Poiché questo approccio legge l’intero file di input in memoria , è consigliabile solo per file più piccoli.
  • printf %s garantisce che nessun output di fine riga venga aggiunto all’output (è l’alternativa conforms a POSIX echo -n standard non standard; vedere http://pubs.opengroup.org/onlinepubs/009696799/utilities/echo.html e https: //unix.stackexchange.com/a/65819 )

Una guida alle altre risposte :

  • Se Perl è disponibile, scegli la risposta accettata : è semplice ed efficiente in termini di memoria (non legge l’intero file di input contemporaneamente).

  • Altrimenti, considera la risposta Awk di ghostdog74 : è oscura, ma anche efficiente in termini di memoria ; un equivalente più leggibile (conforms a POSIX) è:

    • awk 'NR > 1 { print prev } { prev=$0 } END { ORS=""; print }' in.txt
    • La stampa viene ritardata di una riga in modo che la riga finale possa essere gestita nel blocco END , dove viene stampata senza un trailing \n causa dell’impostazione del separatore dei record di uscita ( OFS ) su una stringa vuota.
  • Se si desidera una soluzione dettagliata, ma veloce e robusta che modifichi realmente sul posto (anziché creare un file temporaneo che sostituisca l’originale), si consideri lo script Perl di jrockway .

Puoi farlo con head da GNU coreutils, supporta argomenti relativi alla fine del file. Quindi per evitare l’ultimo byte usare:

 head -c -1 

Per testare un finale di fine riga puoi usare tail e wc . L’esempio seguente salva il risultato in un file temporaneo e successivamente sovrascrive l’originale:

 if [[ $(tail -c1 file | wc -l) == 1 ]]; then head -c -1 file > file.tmp mv file.tmp file fi 

Puoi anche usare la sponge di moreutils per fare editing “sul posto”:

 [[ $(tail -c1 file | wc -l) == 1 ]] && head -c -1 file | sponge file 

Puoi anche fare una funzione generale riutilizzabile inserendola nel tuo file .bashrc :

 # Example: remove-last-newline < multiline.txt function remove-last-newline(){ local file=$(mktemp) cat > $file if [[ $(tail -c1 $file | wc -l) == 1 ]]; then head -c -1 $file > $file.tmp mv $file.tmp $file fi cat $file } 

Aggiornare

Come notato da KarlWilbur nei commenti e utilizzato nella risposta di Sorentar , truncate --size=-1 può sostituire head -c-1 e supporta la modifica sul posto.

 head -n -1 abc > newfile tail -n 1 abc | tr -d '\n' >> newfile 

Modifica 2:

Ecco una versione di awk (corretta) che non accumula un array potenzialmente enorme:

awk ‘{if (line) print line; riga = $ 0} END {printf $ 0} ‘abc

guardare con aria sciocca

  awk '{q=p;p=$0}NR>1{print q}END{ORS = ""; print p}' file 

Se vuoi farlo bene, hai bisogno di qualcosa del genere:

 use autodie qw(open sysseek sysread truncate); my $file = shift; open my $fh, '+>>', $file; my $pos = tell $fh; sysseek $fh, $pos - 1, 0; sysread $fh, my $buf, 1 or die 'No data to read?'; if($buf eq "\n"){ truncate $fh, $pos - 1; } 

Apriamo il file per la lettura e l’accodamento; aprire per accodare significa che siamo già seek fino alla fine del file. Otteniamo quindi la posizione numerica della fine del file con tell . Usiamo quel numero per cercare un personaggio, e poi leggiamo quel personaggio. Se è una nuova riga, tronchiamo il file sul carattere prima di quella nuova, altrimenti non facciamo nulla.

Funziona in tempo costante e spazio costante per qualsiasi input e non richiede più spazio su disco.

Un metodo molto semplice per i file a riga singola, che richiede GNU echo da coreutils:

 /bin/echo -n $(cat $file) 

Ecco una soluzione Python bella e in ordine. Non ho fatto nessun tentativo di essere conciso qui.

Questo modifica il file sul posto, piuttosto che fare una copia del file e rimuovere la nuova riga dall’ultima riga della copia. Se il file è grande, sarà molto più veloce della soluzione Perl scelta come migliore risposta.

Tronca un file di due byte se gli ultimi due byte sono CR / LF o di un byte se l’ultimo byte è LF. Non tenta di modificare il file se l’ultimo byte (s) non è (CR) LF. Gestisce gli errori. Testato in Python 2.6.

Metti questo in un file chiamato “striplast” e chmod +x striplast .

 #!/usr/bin/python # strip newline from last line of a file import sys def trunc(filename, new_len): try: # open with mode "append" so we have permission to modify # cannot open with mode "write" because that clobbers the file! f = open(filename, "ab") f.truncate(new_len) f.close() except IOError: print "cannot write to file:", filename sys.exit(2) # get input argument if len(sys.argv) == 2: filename = sys.argv[1] else: filename = "--help" # wrong number of arguments so print help if filename == "--help" or filename == "-h" or filename == "/?": print "Usage: %s " % sys.argv[0] print "Strips a newline off the last line of a file." sys.exit(1) try: # must have mode "b" (binary) to allow f.seek() with negative offset f = open(filename, "rb") except IOError: print "file does not exist:", filename sys.exit(2) SEEK_EOF = 2 f.seek(-2, SEEK_EOF) # seek to two bytes before end of file end_pos = f.tell() line = f.read() f.close() if line.endswith("\r\n"): trunc(filename, end_pos) elif line.endswith("\n"): trunc(filename, end_pos + 1) 

PS Nello spirito di “Perl golf”, ecco la mia soluzione Python più breve. Scambia l’intero file dall’input standard alla memoria, elimina tutte le nuove linee dalla fine e scrive il risultato sullo standard output. Non così conciso quanto il Perl; non riesci a battere Perl per cose poco ingannevoli come questa.

Rimuovi “\ n” dalla chiamata a .rstrip() e .rstrip() tutti gli spazi bianchi dalla fine del file, incluse più righe vuote.

Metti questo in “slurp_and_chomp.py” e poi esegui python slurp_and_chomp.py < inputfile > outputfile .

 import sys sys.stdout.write(sys.stdin.read().rstrip("\n")) 

Ancora un altro WTDI perl:

 perl -i -p0777we's/\n\z//' filename 
 $ perl -e 'locale $ /;  $ _ = <>;  s / \ n $ //;  stampa 'a-text-file.txt

Vedi anche Abbinare qualsiasi carattere (incluse le nuove linee) in sed .

Usando dd:

 file='/path/to/file' [[ "$(tail -c 1 "${file}" | tr -dc '\n' | wc -c)" -eq 1 ]] && \ printf "" | dd of="${file}" seek=$(($(stat -f "%z" "${file}") - 1)) bs=1 count=1 #printf "" | dd of="${file}" seek=$(($(wc -c < "${file}") - 1)) bs=1 count=1 
 perl -pi -e 's/\n$// if(eof)' your_file 

Supponendo il tipo di file Unix e si desidera solo l’ultima newline funziona.

 sed -e '${/^$/d}' 

Non funzionerà su più newline …

* Funziona solo se l’ultima riga è una riga vuota.

Ancora un’altra risposta FTR (e la mia preferita!): Echo / cat la cosa che vuoi spogliare e catturare l’output attraverso i backtick. L’ultima riga finale verrà spogliata. Per esempio:

 # Sadly, outputs newline, and we have to feed the newline to sed to be portable echo thingy | sed -e 's/thing/sill/' # No newline! Happy. out=`echo thingy | sed -e 's/thing/sill/'` printf %s "$out" # Similarly for files: file=`cat file_ending_in_newline` printf %s "$file" > file_no_newline 

Una soluzione rapida sta usando l’utility gnu truncate:

 [ -z $(tail -c1 file) ] && truncate -s-1 

Il test sarà vero se il file ha una nuova riga finale.

La rimozione è molto veloce, veramente a posto, non è necessario nuovo file e la ricerca sta anche leggendo dalla fine solo un byte (tail -c1).

L’unica volta che ho voluto farlo è per il codice golf, e quindi ho appena copiato il mio codice dal file e incollato in una dichiarazione di echo -n 'content'>file .

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

Ho avuto un problema simile, ma stavo lavorando con un file Windows e ho bisogno di mantenere quelli CRLF – la mia soluzione su Linux:

 sed 's/\r//g' orig | awk '{if (NR>1) printf("\r\n"); printf("%s",$0)}' > tweaked 
 sed -n "1 x;1 !H $ {x;s/\n*$//p;} " YourFile 

Dovrebbe rimuovere qualsiasi ultima occorrenza di \ n nel file. Non funziona su file enormi (a causa della limitazione del buffer sed)

ruby:

 ruby -ne 'print $stdin.eof ? $_.strip : $_' 

o:

 ruby -ane 'q=p;p=$_;puts q if $.>1;END{print p.strip!}' 

POSIX SED:

‘$ {/ ^ $ / d}’

 $ - match last line { COMMANDS } - A group of commands may be enclosed between { and } characters. This is particularly useful when you want a group of commands to be triggered by a single address (or address-range) match. 

Questa è una buona soluzione se ne hai bisogno per lavorare con pipe / redirezione invece di leggere / emettere da o su un file. Funziona con linee singole o multiple. Funziona indipendentemente dal fatto che ci sia una nuova riga finale o meno.

 # with trailing newline echo -en 'foo\nbar\n' | sed '$s/$//' | head -c -1 # still works without trailing newline echo -en 'foo\nbar' | sed '$s/$//' | head -c -1 # read from a file sed '$s/$//' myfile.txt | head -c -1 

Dettagli:

  • head -c -1 tronca l’ultimo carattere della stringa, indipendentemente da quale sia il carattere. Quindi se la stringa non termina con una nuova riga, si perderebbe un personaggio.
  • Quindi per risolvere questo problema, aggiungiamo un altro comando che aggiungerà una nuova riga finale se non ce n’è una: sed '$s/$//' . Il primo $ significa solo applicare il comando sull’ultima riga. s/$// significa sostituire la “fine della linea” con “niente”, che in pratica non fa nulla. Ma ha un effetto collaterale di aggiungere una nuova riga finale non ce n’è uno.

Nota: il head predefinito di Mac non supporta l’opzione -c . Puoi fare brew install coreutils e usare invece ghead .