it-swarm.it

Come aggiungere una nuova riga alla fine di un file?

Usando i sistemi di controllo della versione mi infastidisco il rumore quando il diff dice No newline at end of file.

Quindi mi chiedevo: come aggiungere una nuova riga alla fine di un file per sbarazzarsi di quei messaggi?

208
k0pernikus

Per disinfettare in modo ricorsivo un progetto, utilizzo questo oneliner:

git ls-files -z | while IFS= read -rd '' f; do tail -c1 < "$f" | read -r _ || echo >> "$f"; done

Spiegazione:

  • git ls-files -z elenca i file nel repository. Prende un modello opzionale come parametro aggiuntivo che potrebbe essere utile in alcuni casi se si desidera limitare l'operazione a determinati file/directory. In alternativa, puoi utilizzare find -print0 ... o programmi simili per elencare i file interessati - assicurati solo che emetta NUL - voci delimitate.

  • while IFS= read -rd '' f; do ... done scorre le voci, gestendo in modo sicuro i nomi dei file che includono spazi bianchi e/o newline.

  • tail -c1 < "$f" legge l'ultimo carattere da un file.

  • read -r _ esce con uno stato di uscita diverso da zero se manca una nuova riga finale.

  • || echo >> "$f" aggiunge una nuova riga al file se lo stato di uscita del comando precedente era diverso da zero.

46
Patrick Oscity

Ecco qua :

sed -i -e '$a\' file

E in alternativa per OS X sed:

sed -i '' -e '$a\' file

Questo aggiunge \n alla fine del file solo se non termina già con una nuova riga. Quindi, se lo esegui due volte, non aggiungerà un'altra nuova riga:

$ cd "$(mktemp -d)"
$ printf foo > test.txt
$ sed -e '$a\' test.txt > test-with-eol.txt
$ diff test*
1c1
< foo
\ No newline at end of file
---
> foo
$ echo $?
1
$ sed -e '$a\' test-with-eol.txt > test-still-with-one-eol.txt
$ diff test-with-eol.txt test-still-with-one-eol.txt
$ echo $?
0
215
l0b0

Dare un'occhiata:

$ echo -n foo > foo 
$ cat foo
foo$
$ echo "" >> foo
$ cat foo
foo

così echo "" >> noeol-file dovrebbe fare il trucco. (O intendevi chiedere di identificare questi file e risolverli?)

modifica rimosso il "" a partire dal echo "" >> foo (vedi il commento di @ yuyichao) edit2 ha aggiunto il "" di nuovo ( ma vedi il commento di @Keith Thompson)

41
sr_

Un'altra soluzione che utilizza ed. Questa soluzione ha effetto solo sull'ultima riga e solo se manca \n:

ed -s file <<< w

Funziona essenzialmente aprendo il file per la modifica tramite uno script, lo script è il singolo comando w, che riscrive il file sul disco. Si basa su questa frase trovata in ed(1) man page:

 LIMITAZIONI 
 (...) 
 
 Se un file di testo (non binario) non termina con un carattere di nuova riga, 
 Quindi accoda uno a leggerlo/scriverlo. Nel caso di un file binario 
, Ed non aggiunge una nuova riga in lettura/scrittura. 
16
enzotib

Aggiungi newline indipendentemente da:

echo >> filename

Ecco un modo per verificare se esiste una nuova riga alla fine prima di aggiungerne una, usando Python:

f=filename; python -c "import sys; sys.exit(open(\"$f\").read().endswith('\n'))" && echo >> $f
15
Alexander

Un modo semplice, portatile, conforme a POSIX per aggiungere una nuova riga finale assente a un file sarebbe:

[ -n "$(tail -c1 file)" ] && echo >> file

Questo approccio non ha bisogno di leggere l'intero file; può semplicemente cercare di EOF e lavorare da lì.

Inoltre, questo approccio non ha bisogno di creare file temporanei dietro la schiena (ad es. Sed -i), quindi i collegamenti fisici non sono interessati.

echo aggiunge una nuova riga al file solo quando il risultato della sostituzione del comando è una stringa non vuota. Si noti che ciò può accadere solo se il file non è vuoto e l'ultimo byte non è una nuova riga.

Se l'ultimo byte del file è una nuova riga, tail lo restituisce, quindi la sostituzione del comando lo spoglia; il risultato è una stringa vuota. Il test -n fallisce e l'eco non viene eseguito.

Se il file è vuoto, il risultato della sostituzione del comando è anche una stringa vuota e di nuovo l'eco non viene eseguito. Ciò è auspicabile, poiché un file vuoto non è un file di testo non valido, né è equivalente a un file di testo non vuoto con una riga vuota.

12
Barefoot IO

La soluzione più veloce è:

[ -n "$(tail -c1 file)" ] && printf '\n' >>file 

  1. È veramente veloce.
    Su un file di medie dimensioni seq 99999999 >file questo richiede millisecondi.
    Altre soluzioni richiedono molto tempo:

    [ -n "$(tail -c1 file)" ] && printf '\n' >>file  0.013 sec
    vi -ecwq file                                    2.544 sec
    paste file 1<> file                             31.943 sec
    ed -s file <<< w                             1m  4.422 sec
    sed -i -e '$a\' file                         3m 20.931 sec
    
  2. Funziona in ash, bash, lksh, mksh, ksh93, attsh e zsh ma non in yash.

  3. Non modifica il timestamp del file se non è necessario aggiungere una nuova riga.
    Tutte le altre soluzioni presentate qui cambiano il timestamp del file.
  4. Tutte le soluzioni sopra sono POSIX valide.

Se hai bisogno di una soluzione portatile per lo yash (e tutte le altre shell elencate sopra), potrebbe diventare un po 'più complesso:

f=file
if       [ "$(tail -c1 "$f"; echo x)" != "$(printf '\nx')" ]
then     printf '\n' >>"$f"
fi
8
Isaac

Il modo più rapido per verificare se l'ultimo byte di un file è una nuova riga consiste nel leggere solo l'ultimo byte. Questo potrebbe essere fatto con tail -c1 file. Tuttavia, il modo semplicistico di verificare se il valore del byte è una nuova riga, a seconda della solita rimozione da parte di Shell di una nuova riga finale all'interno di un'espansione del comando, fallisce (ad esempio) in yash, quando l'ultimo carattere nel file è un UTF- 8 valore.

Il modo corretto, conforme a POSIX, tutto (ragionevole) conchiglie per scoprire se l'ultimo byte di un file è una nuova riga è usare xxd o hexdump:

tail -c1 file | xxd -u -p
tail -c1 file | hexdump -v -e '/1 "%02X"'

Quindi, confrontando l'output di sopra con 0A fornirà un test affidabile.
È utile evitare di aggiungere una nuova riga a un file altrimenti vuoto.
File che non fornirà un ultimo carattere di 0A, ovviamente:

f=file
a=$(tail -c1 "$f" | hexdump -v -e '/1 "%02X"')
[ -s "$f" -a "$a" != "0A" ] && echo >> "$f"

Breve e dolce Questo richiede pochissimo tempo poiché legge solo l'ultimo byte (cerca in EOF). Non importa se il file è grande. Quindi aggiungere solo un byte se necessario.

Non sono necessari né utilizzati file temporanei. Nessun hardlink è interessato.

Se questo test viene eseguito due volte, non aggiungerà un'altra riga.

7
sorontar

Faresti meglio a correggere l'editor dell'utente che ha modificato il file per ultimo. Se sei l'ultima persona ad aver modificato il file, quale editor stai usando, immagino che compagno di testo ...?

4
AD7six

A condizione che non vi siano null nell'input:

paste - <>infile >&0

... basterebbe sempre aggiungere una nuova riga solo alla fine di un file se non ne aveva già uno. E basta leggere il file di input solo una volta per farlo bene.

3
user157018

Se si desidera aggiungere rapidamente una nuova riga durante l'elaborazione di una pipeline, utilizzare questo:

outputting_program | { cat ; echo ; }

è anche conforme POSIX.

Quindi, ovviamente, puoi reindirizzarlo su un file.

3
MichalH

Sebbene non risponda direttamente alla domanda, ecco uno script correlato che ho scritto per rilevare i file che non terminano con la nuova riga. È molto veloce.

find . -type f | # sort |        # sort file names if you like
/usr/bin/Perl -lne '
   open FH, "<", $_ or do { print " error: $_"; next };
   $pos = sysseek FH, 0, 2;                     # seek to EOF
   if (!defined $pos)     { print " error: $_"; next }
   if ($pos == 0)         { print " empty: $_"; next }
   $pos = sysseek FH, -1, 1;                    # seek to last char
   if (!defined $pos)     { print " error: $_"; next }
   $cnt = sysread FH, $c, 1;
   if (!$cnt)             { print " error: $_"; next }
   if ($c eq "\n")        { print "   EOL: $_"; next }
   else                   { print "no EOL: $_"; next }
'

Lo script Perl legge un elenco di nomi di file (ordinati facoltativamente) da stdin e per ogni file legge l'ultimo byte per determinare se il file termina in una nuova riga o meno. È molto veloce perché evita di leggere l'intero contenuto di ciascun file. Emette una riga per ogni file che legge, preceduto da "errore:" se si verifica un tipo di errore, "vuoto:" se il file è vuoto (non termina con la nuova riga!), "EOL:" ("fine di line ") se il file termina con newline e" no EOL: "se il file non termina con newline.

Nota: lo script non gestisce i nomi di file che contengono nuove righe. Se sei su un GNU o BSD, potresti gestire tutti i possibili nomi di file aggiungendo -print0 per trovare, -z per ordinare e -0 per Perl, in questo modo:

find . -type f -print0 | sort -z |
/usr/bin/Perl -ln0e '
   open FH, "<", $_ or do { print " error: $_"; next };
   $pos = sysseek FH, 0, 2;                     # seek to EOF
   if (!defined $pos)     { print " error: $_"; next }
   if ($pos == 0)         { print " empty: $_"; next }
   $pos = sysseek FH, -1, 1;                    # seek to last char
   if (!defined $pos)     { print " error: $_"; next }
   $cnt = sysread FH, $c, 1;
   if (!$cnt)             { print " error: $_"; next }
   if ($c eq "\n")        { print "   EOL: $_"; next }
   else                   { print "no EOL: $_"; next }
'

Naturalmente, dovresti comunque trovare un modo per codificare i nomi dei file con nuove righe nell'output (lasciato come esercizio per il lettore).

L'output potrebbe essere filtrato, se lo si desidera, per aggiungere una nuova riga a quei file che non ne hanno uno, molto semplicemente con

 echo >> "$filename"

La mancanza di una nuova riga finale può causare errori negli script poiché alcune versioni di Shell e altre utilità non gestiranno correttamente una nuova riga finale mancante durante la lettura di tale file.

Nella mia esperienza, la mancanza di una nuova riga finale è causata dall'utilizzo di varie utilità di Windows per modificare i file. Non ho mai visto VIM causare una nuova riga finale mancante durante la modifica di un file, anche se riporterà su tali file.

Infine, ci sono script molto più brevi (ma più lenti) che possono passare in rassegna gli input dei nomi dei file per stampare quei file che non terminano con una nuova riga, come:

/usr/bin/Perl -ne 'print "$ARGV\n" if /.\z/' -- FILE1 FILE2 ...

Gli editor vi/vim/ex aggiungono automaticamente <EOL> at EOF a meno che il file non lo abbia già.

Quindi prova uno di questi:

vi -ecwq foo.txt

che equivale a:

ex -cwq foo.txt

Test:

$ printf foo > foo.txt && wc foo.txt
0 1 3 foo.txt
$ ex -scwq foo.txt && wc foo.txt
1 1 4 foo.txt

Per correggere più file, selezionare: Come correggere 'Nessuna nuova riga alla fine del file' per molti file? in SO

Perché questo è così importante? Per conservare i nostri file POSIX compatibile .

1
kenorb

Se il tuo file termina con Windows terminazioni di riga \r\n e sei su Linux puoi usare questo comando sed. Aggiunge solo \r\n all'ultima riga se non è già presente:

sed -i -e '$s/\([^\r]\)$/\1\r\n/'

Spiegazione:

-i    replace in place
-e    script to run
$     matches last line of a file
s     substitute
\([^\r]\)$    search the last character in the line which is not a \r
\1\r\n    replace it with itself and add \r\n

Se l'ultima riga contiene già un \r\n allora la ricerca regexp non corrisponderà, quindi non accadrà nulla.

0
masgo

Per applicare la risposta accettata a tutti i file nella directory corrente (oltre alle sottodirectory):

$ find . -type f -exec sed -i -e '$a\' {} \;

Funziona su Linux (Ubuntu). Su OS X probabilmente devi usare -i '' (non testato).

0
friederbluemle

Potresti scrivere un fix-non-delimited-line script come:

#! /bin/zsh -
zmodload zsh/system || exit
ret=0
for file do
  (){
    sysseek -w end -1 || {
      syserror -p "Can't seek before the last byte: "
      return 1
    }
    read -r x || print -u0
  } <> $file || ret=$?
done
exit $ret

Contrariamente ad alcune delle soluzioni fornite qui, esso

  • dovrebbe essere efficiente in quanto non esegue il fork di alcun processo, legge solo un byte per ogni file e non riscrive il file (basta aggiungere una nuova riga)
  • non interromperà i collegamenti simbolici/hardlink o influenzerà i metadati (inoltre, i ctime/mtime vengono aggiornati solo quando viene aggiunta una nuova riga)
  • dovrebbe funzionare correttamente anche se l'ultimo byte è un NUL o fa parte di un carattere multi-byte.
  • dovrebbe funzionare correttamente indipendentemente da quali caratteri o non caratteri possono contenere i nomi dei file
  • Dovrebbe gestire correttamente file illeggibili o non scrivibili o non ricercabili (e riportare gli errori di conseguenza)
  • Non dovrebbe aggiungere una nuova riga a file vuoti (ma segnala un errore su una ricerca non valida in quel caso)

Puoi usarlo ad esempio come:

that-script *.txt

o:

git ls-files -z | xargs -0 that-script
0

Aggiungendo a la risposta di Patrick Oscity , se vuoi solo applicarlo a una directory specifica, puoi anche usare:

find -type f | while read f; do tail -n1 $f | read -r _ || echo >> $f; done

Eseguilo nella directory in cui desideri aggiungere nuove righe.

0
cipher

echo $'' >> <FILE_NAME> aggiungerà una riga vuota alla fine del file.

echo $'\n\n' >> <FILE_NAME> aggiungerà 3 righe vuote alla fine del file.

0
user247137

Almeno nelle versioni GNU, semplicemente grep '' o awk 1 canonicalizza il suo input, aggiungendo una nuova riga finale se non già presente. Copiano il file nel processo, il che richiede tempo se grande (ma il sorgente non dovrebbe essere troppo grande da leggere comunque?) E aggiorna il modtime a meno che tu non faccia qualcosa di simile

 mv file old; grep '' <old >file; touch -r old file

(sebbene ciò possa andare bene su un file che si sta effettuando il check-in perché è stato modificato) e perde collegamenti, permessi non predefiniti e ACL ecc. a meno che non si sia ancora più attenti.

0

Un sacco di ottimi suggerimenti qui, ma un'idea sarebbe quella di rimuovere una nuova riga e aggiungerne una in modo da sapere che non le aggiungi continuamente:

Prendi il file "pippo":

Rimuovi una nuova linea se ce n'è una:

  truncate -s $(($(stat -c '%s' foo)-1)) foo

Quindi aggiungine uno:

  sed -i -e '$a\' foo

Quindi foo conterrà sempre almeno una nuova riga.

o fai la coda del file e cerca una nuova riga, se non contiene aggiungi uno ..

  grep -q "[^0-9a-z-]" <<< $(tail -1 ./foo) && echo " " >> ./foo
0
Mike Q

Funziona in AIX ksh:

lastchar=`tail -c 1 *filename*`
if [ `echo "$lastchar" | wc -c` -gt "1" ]
then
    echo "/n" >> *filename*
fi

Nel mio caso, se nel file manca la nuova riga, il comando wc restituisce un valore di 2 e scriviamo una nuova riga.

0
Daemoncan