Come posso eliminare una nuova riga se è l'ultimo carattere di un file?


162

Ho alcuni file che vorrei cancellare l'ultima riga se è l'ultimo carattere di un file. od -cmi mostra che il comando che eseguo scrive il file con una nuova riga finale:

0013600   n   t  >  \n

Ho provato alcuni trucchi con sed, ma il meglio che mi viene in mente non è fare il trucco:

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

Qualche idea su come fare questo?


4
newline è solo un carattere per unix newline. Newline DOS sono due caratteri. Ovviamente, "\ n" letterale è composto da due caratteri. Quale stai effettivamente cercando?
In pausa fino a nuovo avviso.

3
Anche se la rappresentazione potrebbe essere \n, in Linux è un personaggio
padiglione

10
Puoi approfondire il motivo per cui vuoi farlo? I file di testo dovrebbero terminare con un fine riga, a meno che non siano completamente vuoti. Mi sembra strano che tu voglia avere un file così troncato?
Thomas Padron-McCarthy,

La solita ragione per fare una cosa del genere è eliminare una virgola finale dall'ultima riga di un file CSV. Sed funziona bene, ma le nuove linee devono essere trattate in modo diverso.
padiglione

9
@ ThomasPadron-McCarthy "Nell'informatica, per ogni buona ragione c'è qualcosa da fare, esiste una buona ragione per non farlo e viceversa." -Gesù - "non dovresti farlo" è una risposta orribile, non importa la domanda. Il formato corretto è: [come farlo] ma [perché potrebbe essere una cattiva idea]. #sacrilege
Cory Mawhorter

Risposte:


223
perl -pe 'chomp if eof' filename >filename2

oppure, per modificare il file in atto:

perl -pi -e 'chomp if eof' filename

[Nota del redattore: -pi -eoriginariamente -pie, ma, come notato da molti commentatori e spiegato da @hvd, quest'ultimo non funziona.]

Questo è stato descritto come una "bestemmia perl" sul sito Web awk che ho visto.

Ma, in un test, ha funzionato.


11
Puoi renderlo più sicuro usando chomp. E batte battendo il file.
Sinan Ünür,

6
Anche se è una bestemmia, funziona molto bene. perl -i -pe 'chomp if eof' nomefile. Grazie.
Todd Partridge 'Gen2ly'

13
La cosa divertente della blasfemia e dell'eresia è di solito odiata perché è corretta. :)
Ether

8
Piccola correzione: è possibile utilizzare perl -pi -e 'chomp if eof' filename, per modificare un file sul posto anziché creare un file temporaneo
Romuald Brunet

7
perl -pie 'chomp if eof' filename-> Impossibile aprire lo script perl "chomp if eof": nessun file o directory; perl -pi -e 'chomp if eof' filename-> funziona
aditsu ha smesso perché SE è MALE

56

Puoi trarre vantaggio dal fatto che le sostituzioni di comandi della shell rimuovono i caratteri di nuova riga finali :

Forma semplice che funziona in bash, ksh, zsh:

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

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

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

Nota:


Una guida alle altre risposte :

  • Se Perl è disponibile, cerca la risposta accettata : è semplice ed efficiente in termini di memoria (non legge l'intero file di input in una volta).

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

    • awk 'NR > 1 { print prev } { prev=$0 } END { ORS=""; print }' in.txt
    • La stampa è ritardata di una riga in modo che la riga finale possa essere gestita nel ENDblocco, dove viene stampata senza trascinamento a \ncausa dell'impostazione del separatore del record di output ( OFS) su una stringa vuota.
  • Se vuoi una soluzione dettagliata, ma veloce e robusta che modifichi davvero sul posto (invece di creare un file temporaneo che poi sostituisca l'originale), considera lo script Perl di jrockway .


3
NB se ci sono più newline alla fine del file, questo comando li cancellerà tutti.
Sparhawk,

47

Puoi farlo con headda GNU coreutils, supporta argomenti relativi alla fine del file. Quindi per lasciare l'ultimo byte usare:

head -c -1

Per verificare la presenza di una nuova riga finale è possibile utilizzare taile 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 spongeda moreutilsper eseguire modifiche "sul posto":

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

Puoi anche rendere una funzione riutilizzabile generale inserendola nel tuo .bashrcfile:

# 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=-1può sostituire head -c-1e supportare la modifica sul posto.


3
La migliore soluzione di tutte finora. Utilizza uno strumento standard che in realtà ha ogni distribuzione Linux ed è conciso e chiaro, senza alcuna procedura guidata sed o perl.
Dakkaron,

2
Bella soluzione. Una modifica è che penso che userei truncate --size=-1invece che head -c -1dal momento che ridimensiona il file di input anziché leggere nel file di input, scriverlo in un altro file, quindi sostituire l'originale con il file di output.
Karl Wilbur,

1
Nota che head -c -1rimuoverà l'ultimo carattere indipendentemente dal fatto che si tratti di una nuova riga o meno, ecco perché devi controllare se l'ultimo carattere è una nuova riga prima di rimuoverlo.
Wisbucky,

Sfortunatamente non funziona su Mac. Sospetto che non funzioni su nessuna variante di BSD.
Edward Falk,

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

Modifica 2:

Ecco una awkversione (corretta) che non accumula un array potenzialmente enorme:

awk '{if (line) linea di stampa; line = $ 0} END {printf $ 0} 'abc


Buon modo originale di pensarci. Grazie Dennis.
Todd Partridge 'Gen2ly'

Hai ragione. Rinvio alla tua awkversione. Ci vogliono due offset (e un test diverso) e ne ho usato solo uno. Tuttavia, è possibile utilizzare printfinvece di ORS.
In pausa fino a nuovo avviso.

è possibile trasformare l'output in pipe con la sostituzione del processo:head -n -1 abc | cat <(tail -n 1 abc | tr -d '\n') | ...
BCoates

2
Usare -c invece di -n per testa e coda dovrebbe essere ancora più veloce.
rudimeier

1
Per me, head -n -1 abc ha rimosso l'ultima riga effettiva del file, lasciando una nuova riga finale; head -c -1 abc sembra funzionare meglio
ChrisV

10

allocco

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

Mi sembra ancora un sacco di personaggi ... impararlo lentamente :). Fa il lavoro però. Grazie ghostdog.
Todd Partridge 'Gen2ly'

1
awk '{ prev_line = line; line = $0; } NR > 1 { print prev_line; } END { ORS = ""; print line; }' filequesto dovrebbe essere più facile da leggere.
Yevhen Pavliuk,

Che ne dite di: awk 'NR>1 {print p} {p=$0} END {printf $0}' file.
Isaac,

@sorontar Il primo argomento printfè l' argomento format . Pertanto, se il file di input avesse qualcosa che potrebbe essere interpretato come un identificatore di formato %d, si otterrebbe un errore. Una soluzione sarebbe cambiarlo inprintf "%s" $0
Robin A. Meade,

9

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

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

Questo è un modo decente se non è troppo costoso (ripetitivo).

Questo ha problemi quando \nè presente. Come viene convertito in una nuova linea.
Chris Stryczynski,

Sembra funzionare anche per i file multilinea che $(...)viene citato
Thor,

/bin/echo -n "$(cat infile)" ho sicuramente bisogno di citare che ... Inoltre, non sono sicuro di quale sia la lunghezza massima echoo la shell su os / versioni shell / distribuzioni (stavo solo cercando su Google ed era una tana di coniglio), quindi sono non sono sicuro di quanto sia portatile (o performante) in realtà sarebbe diverso da file di piccole dimensioni, ma per file di piccole dimensioni, fantastico.
michael

8

Se vuoi farlo nel modo giusto, 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'aggiunta; l'apertura per accodamento significa che siamo già stati modificati seekalla fine del file. Otteniamo quindi la posizione numerica della fine del file contell . Usiamo quel numero per cercare un personaggio, quindi leggiamo quel personaggio. Se si tratta di una nuova riga, tronciamo il file al personaggio prima di quella nuova riga, altrimenti non facciamo nulla.

Questo viene eseguito in tempo costante e spazio costante per qualsiasi input e non richiede neanche più spazio su disco.


2
ma questo ha lo svantaggio di non reimpostare la proprietà / i permessi per il file ... err, wait ...
ysth

1
Verbo, ma veloce e robusto - sembra essere l'unica vera risposta sul posto per la modifica dei file qui (e poiché potrebbe non essere ovvio per tutti: questo è uno script Perl ).
mklement0

6

Ecco una bella, ordinata soluzione Python. Non ho fatto alcun tentativo di essere conciso qui.

Ciò modifica il file sul posto, anziché creare una copia del file e rimuovere la nuova riga dall'ultima riga della copia. Se il file è di grandi dimensioni, sarà molto più veloce della soluzione Perl che è stata scelta come la 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 gli ultimi byte non sono (CR) LF. Gestisce errori. Testato in Python 2.6.

Mettilo 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 <filename>" % 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ù corta. Assorbe l'intero file dallo standard input in memoria, rimuove tutte le righe dalla fine e scrive il risultato sullo standard output. Non così conciso come il Perl; semplicemente non puoi battere Perl per piccole cose veloci difficili come questa.

Rimuovere "\ n" dalla chiamata a .rstrip() e tutto lo spazio bianco 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"))

os.path.isfile () ti dirà della presenza del file. L'uso di try / tranne potrebbe
rilevare

5

Una soluzione rapida sta usando l'utilità gnu truncate:

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

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

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


1
troncare: operando file mancante
Brian Hannay,

2
manca solo il nome del file finale nell'esempio, ovvero [ -z $(tail -c1 filename) ] && truncate -s -1 filename(anche, in risposta all'altro commento, il truncatecomando non funziona con stdin, è richiesto un nome file)
michael

4

Ancora un altro perl WTDI:

perl -i -p0777we's/\n\z//' filename

3
$ perl -e 'local $ /; $ _ = <>; s / \ n $ //; stampa 'a-text-file.txt

Vedi anche Abbina qualsiasi personaggio (comprese le nuove linee) in sed .


1
Questo elimina tutte le nuove linee. Equivalente atr -d '\n'
In pausa fino a nuovo avviso.

Anche questo funziona bene, probabilmente meno blasfemo di quello dei pavium.
Todd Partridge "Gen2ly",

Sinan, anche se Linux e Unix potrebbero definire file di testo da terminare con una nuova riga, Windows non pone questo requisito. Il Blocco note, ad esempio, scriverà solo i caratteri digitati senza aggiungere altro alla fine. I compilatori C potrebbero richiedere che un file di origine termini con un'interruzione di riga, ma i file di origine C non sono "solo" file di testo, quindi possono avere requisiti aggiuntivi.
Rob Kennedy,

in tal senso, la maggior parte dei minificatori javascript / css rimuoverà le nuove righe finali e produrrà comunque file di testo.
ysth

@Rob Kennedy e @ysth: C'è un argomento interessante sul perché tali file non sono in realtà file di testo e simili.
Sinan Ünür

2

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

2
perl -pi -e 's/\n$// if(eof)' your_file

In effetti è la stessa della risposta accettata, ma probabilmente più chiara per gli utenti non Perl. Si noti che non c'è bisogno per le go le parentesi attorno eof: perl -pi -e 's/\n$// if eof' your_file.
mklement0

2

Supponendo il tipo di file Unix e si desidera solo l'ultima riga che funzioni.

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

Non funzionerà su più newline ...

* Funziona solo se l'ultima riga è vuota.


Ecco una sedsoluzione che funziona anche per un ultima riga non vuota: stackoverflow.com/a/52047796
wisbucky

1

Ancora un'altra risposta FTR (e la mia preferita!): Echo / cat la cosa che vuoi spogliare e catturare l'output tramite backtick. La nuova riga finale verrà eliminata. 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

1
Ho scoperto la combo cat-printf per caso (stava cercando di ottenere il comportamento opposto). Nota che questo rimuoverà TUTTE le nuove righe finali, non solo l'ultima.
technosaurus,

1

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.

Penso che questo lo rimuoverà solo se l'ultima riga è vuota. Non rimuoverà la nuova riga finale se l'ultima riga non è vuota. Ad esempio, echo -en 'a\nb\n' | sed '${/^$/d}'non rimuoverà nulla. echo -en 'a\nb\n\n' | sed '${/^$/d}'rimuoverà poiché l'intera ultima riga è vuota.
Wisbucky,

1

Questa è una buona soluzione se ne hai bisogno per lavorare con pipe / reindirizzamento invece di leggere / produrre da o verso un file. Funziona con linee singole o multiple. Funziona indipendentemente dal fatto che esista o meno una riga finale.

# 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 -1tronca l'ultimo carattere della stringa, indipendentemente da quale sia il carattere. Quindi se la stringa non termina con una nuova riga, allora perdi un carattere.
  • Quindi, per l'indirizzo che il problema, aggiungiamo un altro comando che aggiunge un carattere di fine riga se non ce n'è uno: sed '$s/$//'. Il primo $significa applicare solo il comando all'ultima riga. s/$//significa sostituire la "fine della linea" con "niente", che sostanzialmente non sta facendo nulla. Ma ha un effetto collaterale nell'aggiungere una nuova riga finale se non ce n'è una.

Nota: l'impostazione predefinita del Mac headnon supporta l' -copzione. Puoi fare brew install coreutilse usare gheadinvece.


0

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


A metà strada lì; approccio completo qui .
mklement0


0

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

sed 's/\r//g' orig | awk '{if (NR>1) printf("\r\n"); printf("%s",$0)}' > tweaked

0
sed -n "1 x;1 !H
$ {x;s/\n*$//p;}
" YourFile

Dovrebbe rimuovere l'ultima occorrenza di \ n nel file. Non funziona su file di grandi dimensioni (a causa della limitazione del buffer sed)


0

rubino:

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

o:

ruby -ane 'q=p;p=$_;puts q if $.>1;END{print p.strip!}'
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.