it-swarm.dev

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   n   t  >  \n

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

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

Qualche idea su come fare questo?

142
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 originalmente -pie, ma, come notato da diversi commentatori e spiegato da @hvd, quest'ultimo non funziona.]

Questo è stato descritto come una "bestemmia Perl" sul sito web di awk che ho visto.

Ma, in una prova, ha funzionato.

201
pavium

Puoi approfittare del fatto che Shell sostituzioni di comandi rimuovi caratteri di fine riga finali:

Semplice modulo 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:

  • Se in.txt termina con multiplo carattere di nuova riga, la sostituzione di comando rimuove tutti di loro - grazie, @Sparhawk. (Non rimuove i caratteri degli spazi bianchi se non quelli finali).
  • 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 sia aggiunto all'output (è l'alternativa conforme a POSIX allo echo -n non standard; vedere http://pubs.opengroup.org/onlinepubs/009696799/utilities/echo.html e https://unix.stackexchange.com/a/65819 )

A guida alle altre risposte:

  • Se Perl è disponibile, scegli la risposta accept - è simple e memory-efficient (non legge l'intero file di input in una sola volta).

  • Altrimenti, prendi in considerazione ghostdog74's Awk answer - è oscuro, ma anche efficiente in memoria; un equivalente più leggibile (conforme 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 \n finale a causa dell'impostazione del separatore dei record di output (OFS) su una stringa vuota.
  • Se vuoi una verbose, ma una soluzione veloce e robusta che modifica veramente sul posto (invece di creare un file temporaneo che sostituisce l'originale), considera jrockway's Perl script .

52
mklement0

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 verificare la fine di una nuova 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 utilizzare sponge da moreutils per eseguire la modifica "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 in Sorentar's answer , truncate --size=-1 può sostituire head -c-1 e supporta la modifica sul posto.

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

Modifica 2: 

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

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

16

guardare con aria sciocca

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

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

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

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à seeked 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.

8
jrockway

Ecco una soluzione Python bella e ordinata. 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 <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)

Post scriptum 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 come il Perl; Non riesci a battere Perl per cose poco ingannevoli come questa.

Rimuovi "\ n" dalla chiamata a .rstrip() e rimuoverà 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"))
5
steveha

Ancora un altro Perl WTDI:

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

Vedi anche Corrisponde a qualsiasi carattere (incluse le nuove righe) in sed .

3
Sinan Ünür
Perl -pi -e 's/\n$// if(eof)' your_file
2
Vijay

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
cpit

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

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

Non funzionerà su più righe nuove ...

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

2
LoranceStinson

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
1
Nicholas Wilson

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.
1
Oleg Mazko

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).

1
sorontar

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
0
cadrian

Rubino:

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

o:

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

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

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

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 se c'è una nuova riga finale o no.

# 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 all'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: la variabile head predefinita di Mac non supporta l'opzione -c. Puoi fare brew install coreutils e usare invece ghead.

0
wisbucky