Usando i sistemi di controllo della versione mi infastidisce il rumore quando dice il diff No newline at end of file
.
Quindi mi chiedevo: come aggiungere una nuova riga alla fine di un file per sbarazzarsi di quei messaggi?
Usando i sistemi di controllo della versione mi infastidisce il rumore quando dice il diff No newline at end of file
.
Quindi mi chiedevo: come aggiungere una nuova riga alla fine di un file per sbarazzarsi di quei messaggi?
Risposte:
Per disinfettare ricorsivamente 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, è possibile utilizzare find -print0 ...
o programmi simili per elencare i file interessati: è sufficiente assicurarsi 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.
find -name \*.java | while read f; do tail -n1 $f | read -r _ || echo >> $f; done
git ls-files
cui ti salverai ancora dalla modifica dei file che non sono monitorati nel controllo versione.
IFS=
per disinserire il separatore è buono per preservare lo spazio bianco circostante. Le voci nulle terminate sono rilevanti solo se hai file o directory con una nuova riga nel loro nome, che sembra un po 'inverosimile, ma è il modo più corretto di gestire il caso generico, sono d'accordo. Proprio come un piccolo avvertimento: l' -d
opzione per read
non è disponibile in POSIX sh.
tail -n1 < "$f"
per evitare problemi con i nomi di file che iniziano con -
( tail -n1 -- "$f"
non funziona per il file chiamato -
). Potresti voler chiarire che la risposta è ora specifica per zsh / bash.
Ecco qua :
sed -i -e '$a\' file
E in alternativa per OS X sed
:
sed -i '' -e '$a\' file
Ciò si 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
man sed
: $ Match the last line.
ma forse funziona solo per caso. La tua soluzione funziona anche.
$
. All'interno di una regex, come nel caso del modulo /<regex>/
, ha il solito significato di "corrispondenza fine linea". Altrimenti, usato come indirizzo, sed gli dà il significato speciale di "ultima riga nel file". Il codice funziona perché sed di default aggiunge una nuova riga al suo output se non è già presente. Il codice "$ a \" dice semplicemente "corrisponde all'ultima riga del file e non aggiunge nulla ad esso". Ma implicitamente, sed aggiunge la nuova riga a ogni linea che elabora (come questa $
linea) se non è già presente.
/regex/
dà un significato diverso. Le pagine di manuale di FreeBSD sono un po 'più informativo, penso: freebsd.org/cgi/man.cgi?query=sed
Dare un'occhiata:
$ echo -n foo > foo
$ cat foo
foo$
$ echo "" >> foo
$ cat foo
foo
quindi echo "" >> noeol-file
dovrebbe fare il trucco. (O intendevi chiedere di identificare questi file e risolverli?)
modifica rimosso il ""
da echo "" >> foo
(vedi il commento di @yuyichao )
edit2 aggiunto di ""
nuovo ( ma vedi il commento di @Keith Thompson)
""
non è necessario (almeno per bash) e tail -1 | wc -l
può essere utilizzato per scoprire il file senza una nuova riga alla fine
""
non è necessario per bash, ma ho visto echo
implementazioni che non stampano nulla quando invocate senza argomenti (anche se nessuna di quelle che trovo ora lo fanno). echo "" >> noeol-file
è probabilmente leggermente più robusto. printf "\n" >> noeol-file
è ancora di più.
csh
's echo
è quello noto per emettere nulla quando non superato ogni discussione. Ma se poi supporteremo shell non simili a Bourne, dovremmo farcela echo ''
invece di echo ""
come echo ""
farebbe ""<newline>
con rc
o es
per esempio.
tcsh
, a differenza csh
, stampa una nuova riga quando viene invocato senza argomenti, indipendentemente dall'impostazione di $echo_style
.
Un'altra soluzione che utilizza ed
. Questa soluzione ha effetto solo sull'ultima riga e solo se \n
manca:
ed -s file <<< w
Funziona essenzialmente aprendo il file per la modifica tramite uno script, lo script è il singolo w
comando, che riscrive il file su disco. Si basa su questa frase trovata nella ed(1)
pagina man:
LIMITAZIONI (...) Se un file di testo (non binario) non è terminato da un carattere di nuova riga, quindi ed aggiunge uno alla lettura / scrittura. Nel caso di un binario file, ed non aggiunge una nuova riga in lettura / scrittura.
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.
yash
se l'ultimo carattere nel file è un carattere multibyte (ad esempio nelle versioni locali UTF-8) o se la locale è C e l'ultimo byte nel file ha l'ottavo bit impostato. Con altre shell (tranne zsh), non aggiungerebbe una nuova riga se il file terminasse in un byte NUL (ma, di nuovo, ciò significherebbe che l'input sarebbe non testuale anche dopo l'aggiunta di una nuova riga).
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
echo ""
sembra più robusto di echo -n '\n'
. Oppure potresti usareprintf '\n'
La soluzione più veloce è:
[ -n "$(tail -c1 file)" ] && printf '\n' >>file
È veramente veloce.
Su un file di medie dimensioni seq 99999999 >file
ciò 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 sopra elencate), 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 della 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 linea è usare xxd o hexdump:
tail -c1 file | xxd -u -p
tail -c1 file | hexdump -v -e '/1 "%02X"'
Quindi, confrontando l'output di cui sopra 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.
xxd
né le hexdump
sono. Nel toolchest POSIX, c'è od -An -tx1
per ottenere il valore esadecimale di un byte.
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 ...?
emacs
non aggiungere una nuova riga alla fine del file.
(setq require-final-newline 'ask)
nel mio.emacs
Se si desidera aggiungere rapidamente una nuova riga durante l'elaborazione di una pipeline, utilizzare questo:
outputting_program | { cat ; echo ; }
è anche conforme a POSIX.
Quindi, ovviamente, puoi reindirizzarlo su un file.
cat file.csv | tr "\r" "\n" | { cat; echo; } | sed "/^[[:space:]]*$/d" | tail -n +2 | wc -l
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.
paste infile 1<> infile
invece.
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 dei file che contengono nuove righe. Se utilizzi un sistema GNU o BSD, puoi 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 }
'
Ovviamente, 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 bug negli script poiché alcune versioni della 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
vengono aggiunti automaticamente <EOL>
a 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? a SO
Perché questo è così importante? Per mantenere i nostri file POSIX compatibili .
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).
find .
elenca tutti i file, inclusi i file in .git
. Per escludere:find . -type f -not -path './.git/*' -exec sed -i -e '$a\' {} \;
Almeno nelle versioni GNU, semplicemente grep ''
oawk 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.
grep '' file 1<> file
, anche se questo continuerebbe a leggere e scrivere il file completamente.
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 wc
comando restituisce un valore di 2
e scriviamo una nuova riga.
Aggiungendo alla risposta di Patrick Oscity , se vuoi solo applicarla a una directory specifica, puoi anche usare:
find -type f | while read f; do tail -n1 $f | read -r _ || echo >> $f; done
Esegui questo all'interno della directory a 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.
Se il file è terminato con terminazioni di linea di Windows\r\n
e ci si trova in Linux, è possibile utilizzare questo sed
comando. Si aggiunge \r\n
all'ultima riga solo 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à a, \r\n
la ricerca regexp non corrisponderà, quindi non accadrà nulla.
Puoi scrivere una fix-non-delimited-line
sceneggiatura come:
#! /bin/zsh -
zmodload zsh/system || exit
ret=0
for file do
if sysopen -rwu0 -- "$file"; then
if sysseek -w end -1; then
read -r x || print -u0
else
syserror -p "Can't seek in $file before the last byte: "
ret=1
fi
else
ret=1
fi
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
POSIX, potresti fare qualcosa di funzionalmente equivalente
export LC_ALL=C
ret=0
for file do
[ -s "$file" ] || continue
{
c=$(tail -c 1 | od -An -vtc)
case $c in
(*'\n'*) ;;
(*[![:space:]]*) printf '\n' >&0 || ret=$?;;
(*) ret=1;; # tail likely failed
esac
} 0<> "$file" || ret=$? # record failure to open
done