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?
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.
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
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)
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.
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
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.
La soluzione più veloce è:
[ -n "$(tail -c1 file)" ] && printf '\n' >>file
È 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
Funziona in ash, bash, lksh, mksh, ksh93, attsh e zsh ma non in yash.
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
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.
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 ...?
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.
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.
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 .
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.
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).
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
Puoi usarlo ad esempio come:
that-script *.txt
o:
git ls-files -z | xargs -0 that-script
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.
echo $'' >> <FILE_NAME>
aggiungerà una riga vuota alla fine del file.
echo $'\n\n' >> <FILE_NAME>
aggiungerà 3 righe vuote alla fine del file.
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.
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
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.