Presumo che tutti qui abbiano familiarità con il detto che tutti i file di testo dovrebbero finire con una nuova riga. Conosco questa "regola" da anni ma mi sono sempre chiesto: perché?
Presumo che tutti qui abbiano familiarità con il detto che tutti i file di testo dovrebbero finire con una nuova riga. Conosco questa "regola" da anni ma mi sono sempre chiesto: perché?
Risposte:
Perché è così che lo standard POSIX definisce una linea :
- Linea 3.206
- Una sequenza di zero o più caratteri non <newline> più un carattere <newline> che termina.
Pertanto, le linee che non terminano con un carattere di nuova riga non sono considerate linee effettive. Ecco perché alcuni programmi hanno problemi nell'elaborazione dell'ultima riga di un file se non è terminato a capo.
C'è almeno un grande vantaggio in questa linea guida quando si lavora su un emulatore di terminale: tutti gli strumenti Unix prevedono questa convenzione e lavorano con essa. Ad esempio, quando si concatenano file con cat
, un file terminato da newline avrà un effetto diverso da uno senza:
$ more a.txt
foo
$ more b.txt
bar$ more c.txt
baz
$ cat {a,b,c}.txt
foo
barbaz
E, come dimostra anche l'esempio precedente, quando si visualizza il file sulla riga di comando (ad es. Via more
), un file con terminazione di nuova riga produce una visualizzazione corretta. Un file terminato in modo errato potrebbe essere confuso (seconda riga).
Per coerenza, è molto utile seguire questa regola - altrimenti farebbe un lavoro extra quando si ha a che fare con gli strumenti Unix predefiniti.
Pensaci diversamente: se le linee non sono terminate da newline, creare comandi come cat
utili è molto più difficile: come fai un comando per concatenare i file in modo tale che
b.txt
e c.txt
?Naturalmente questo è risolvibile, ma è necessario rendere l'uso di cat
più complesso (aggiungendo argomenti posizionali nella riga di comando, ad esempio cat a.txt --no-newline b.txt c.txt
), e ora il comando anziché ogni singolo file controlla come viene incollato insieme ad altri file. Questo non è quasi certamente conveniente.
... O è necessario introdurre un carattere sentinella speciale per contrassegnare una linea che dovrebbe essere continuata anziché terminata. Bene, ora sei bloccato con la stessa situazione di POSIX, tranne che invertito (continuazione della linea anziché carattere di terminazione della linea).
Ora, su sistemi non compatibili con POSIX (al giorno d'oggi principalmente Windows), il punto è controverso: i file generalmente non terminano con una nuova riga e la definizione (informale) di una riga potrebbe essere ad esempio "testo separato da nuove righe" (notare l'enfasi). Questo è del tutto valido. Tuttavia, per i dati strutturati (ad es. Codice di programmazione) rende l'analisi più minimamente più complicata: generalmente significa che i parser devono essere riscritti. Se un parser è stato originariamente scritto tenendo presente la definizione POSIX, potrebbe essere più semplice modificare il flusso di token anziché il parser; in altre parole, aggiungere un token "newline artificiale" alla fine dell'input.
cat
utili e coerenti.
Ogni riga deve essere terminata con un carattere di nuova riga, incluso l'ultimo. Alcuni programmi hanno problemi nell'elaborazione dell'ultima riga di un file se non è terminato da nuova riga.
GCC lo avverte non perché non è in grado di elaborare il file, ma perché deve far parte dello standard.
Lo standard del linguaggio C dice che un file sorgente che non è vuoto deve finire con un carattere di nuova riga, che non deve essere immediatamente preceduto da una barra rovesciata.
Poiché si tratta di una clausola "deve", dobbiamo emettere un messaggio diagnostico per violazione di questa regola.
Questo è nella sezione 2.1.1.2 della norma ANSI C 1989. Sezione 5.1.1.2 della norma ISO C 1999 (e probabilmente anche della norma ISO C 1990).
Riferimento: l'archivio di posta GCC / GNU .
wc -l
non conterà l'ultima riga di un file se non è terminato a capo. Inoltre, cat
unirà l'ultima riga di un file con la prima riga del file successivo in una se l'ultima riga del primo file non è terminata a nuova riga. Praticamente qualsiasi programma che cerca newline come delimitatore ha il potenziale per rovinare tutto.
wc
è già stato menzionato ....
cat
e wc
)?
Questa risposta è un tentativo di risposta tecnica piuttosto che opinione.
Se vogliamo essere puristi POSIX, definiamo una linea come:
Una sequenza di zero o più caratteri non <newline> più un carattere <newline> che termina.
Fonte: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_206
Una linea incompleta come:
Una sequenza di uno o più caratteri non <newline> alla fine del file.
Fonte: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_195
Un file di testo come:
Un file che contiene caratteri organizzati in zero o più righe. Le righe non contengono caratteri NUL e nessuna può superare i {LINE_MAX} byte di lunghezza, incluso il carattere <newline>. Sebbene POSIX.1-2008 non distingua tra file di testo e file binari (vedere lo standard ISO C), molte utility producono output prevedibili o significativi solo quando si opera su file di testo. Le utility standard che hanno tali restrizioni specificano sempre "file di testo" nelle loro sezioni STDIN o INPUT FILES.
Fonte: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_397
Una stringa come:
Una sequenza contigua di byte terminata da e incluso il primo byte null.
Fonte: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_396
Da ciò, quindi, possiamo derivare che l'unica volta in cui potremmo potenzialmente incontrare qualsiasi tipo di problema è se trattiamo il concetto di una riga di un file o di un file come file di testo (essendo che un file di testo è un'organizzazione pari a zero o più righe e una riga che conosciamo deve terminare con una <newline>).
Caso in questione: wc -l filename
.
Dal wc
manuale si legge:
Una linea è definita come una stringa di caratteri delimitati da un carattere <newline>.
Quali sono le implicazioni per i file JavaScript, HTML e CSS allora che sono file di testo ?
Nei browser, IDE moderni e altre applicazioni front-end non ci sono problemi con saltare EOL su EOF. Le applicazioni analizzeranno correttamente i file. Dal momento che non tutti i sistemi operativi devono essere conformi allo standard POSIX, pertanto non sarebbe pratico per gli strumenti non OS (ad esempio i browser) gestire i file secondo lo standard POSIX (o qualsiasi standard a livello di sistema operativo).
Di conseguenza, possiamo essere relativamente certi che EOL presso EOF non avrà praticamente alcun impatto negativo a livello di applicazione, indipendentemente dal fatto che sia in esecuzione su un sistema operativo UNIX.
A questo punto possiamo affermare con certezza che saltare EOL su EOF è sicuro quando si ha a che fare con JS, HTML, CSS sul lato client. In realtà, possiamo affermare che la minimizzazione di uno di questi file, che non contiene <newline> è sicura.
Possiamo fare un ulteriore passo avanti e dire che anche per NodeJS non può aderire allo standard POSIX in quanto può essere eseguito in ambienti non POSIX compatibili.
Cosa ci rimane allora? Strumenti a livello di sistema.
Ciò significa che gli unici problemi che possono sorgere riguardano gli strumenti che fanno uno sforzo per aderire alla loro funzionalità alla semantica di POSIX (ad es. Definizione di una linea come mostrato in wc
).
Anche così, non tutte le shell aderiranno automaticamente a POSIX. Bash, ad esempio, per impostazione predefinita non si comporta POSIX. C'è un interruttore per attivarlo: POSIXLY_CORRECT
.
Spunti di riflessione sul valore di EOL <newline>: https://www.rfc-editor.org/old/EOLstory.txt
Rimanendo sulla pista degli utensili, a tutti gli effetti pratici, consideriamo questo:
Lavoriamo con un file che non ha EOL. Al momento della stesura di questo documento, il file in questo esempio è un JavaScript minimizzato senza EOL.
curl http://cdnjs.cloudflare.com/ajax/libs/AniJS/0.5.0/anijs-min.js -o x.js
curl http://cdnjs.cloudflare.com/ajax/libs/AniJS/0.5.0/anijs-min.js -o y.js
$ cat x.js y.js > z.js
-rw-r--r-- 1 milanadamovsky 7905 Aug 14 23:17 x.js
-rw-r--r-- 1 milanadamovsky 7905 Aug 14 23:17 y.js
-rw-r--r-- 1 milanadamovsky 15810 Aug 14 23:18 z.js
Si noti che la cat
dimensione del file è esattamente la somma delle sue singole parti. Se la concatenazione dei file JavaScript è un problema per i file JS, la preoccupazione più appropriata sarebbe quella di avviare ogni file JavaScript con un punto e virgola.
Come qualcun altro ha menzionato in questo thread: cosa succede se si desidera cat
due file il cui output diventa solo una riga anziché due? In altre parole, cat
fa quello che dovrebbe fare.
L' man
di cat
sola lettura menzioni d'ingresso fino a EOF, non <newline>. Si noti che lo -n
switch di cat
stamperà anche una linea terminata non <nuova> (o linea incompleta ) come linea - essendo che il conteggio inizia da 1 (secondo il man
.)
-n Numerare le righe di output, iniziando da 1.
Ora che comprendiamo come POSIX definisce una linea , questo comportamento diventa ambiguo o davvero non conforme.
Comprendere lo scopo e la conformità di un determinato strumento aiuterà a determinare quanto sia fondamentale terminare i file con un EOL. In C, C ++, Java (JAR), ecc ... alcuni standard dettano una nuova linea per la validità - non esiste uno standard simile per JS, HTML, CSS.
Ad esempio, invece di usarne wc -l filename
uno si potrebbe fare awk '{x++}END{ print x}' filename
, e sii certo che il successo dell'attività non è compromesso da un file che potremmo voler elaborare e che non abbiamo scritto (ad esempio una libreria di terze parti come la JS minimizzata che curl
d) - a meno che il nostro l'intenzione era davvero quella di contare le linee nel senso conforme a POSIX.
Conclusione
Ci saranno pochissimi casi d'uso reali in cui saltare EOL su EOF per determinati file di testo come JS, HTML e CSS avrà un impatto negativo, se non del tutto. Se facciamo affidamento sulla presenza di <newline>, stiamo limitando l'affidabilità dei nostri strumenti solo ai file che creiamo e ci apriamo a potenziali errori introdotti da file di terze parti.
Morale della storia: strumenti di ingegneria che non hanno la debolezza di fare affidamento su EOL presso EOF.
Sentiti libero di pubblicare casi d'uso quando si applicano a JS, HTML e CSS in cui possiamo esaminare in che modo saltare EOL ha un effetto negativo.
Potrebbe essere correlato alla differenza tra :
Se ogni riga termina con una fine riga, questo evita, ad esempio, che concatenare due file di testo farebbe eseguire l'ultima riga della prima alla prima riga della seconda.
Inoltre, un editor può verificare al caricamento se il file termina in una riga di fine, lo salva nella sua opzione locale 'eol' e lo usa durante la scrittura del file.
Alcuni anni fa (2005), molti editori (ZDE, Eclipse, Scite, ...) "dimenticarono" quell'EOL finale, che non era molto apprezzato .
Non solo, ma hanno interpretato in modo errato quella EOL finale, come "avvia una nuova riga" e in realtà iniziano a visualizzare un'altra riga come se esistesse già.
Ciò era molto visibile con un file di testo "corretto" con un editor di testo ben educato come vim, rispetto all'apertura in uno dei suddetti editor. Ha visualizzato una riga aggiuntiva sotto l'ultima riga reale del file. Vedi qualcosa del genere:
1 first line
2 middle line
3 last line
4
Alcuni strumenti si aspettano questo. Ad esempio, si wc
aspetta questo:
$ echo -n "Line not ending in a new line" | wc -l
0
$ echo "Line ending with a new line" | wc -l
1
wc
non lo si aspetta , in quanto sta semplicemente lavorando all'interno della definizione POSIX di "linea" in contrapposizione alla comprensione intuitiva di "linea" della maggior parte delle persone.
wc -l
stampa 1
in entrambi i casi, ma alcune persone potrebbero dire che il secondo caso dovrebbe essere stampato 2
.
\n
a un terminatore di linea, piuttosto che a un separatore di linea, come fa POSIX / UNIX, aspettarsi che il secondo caso stampi 2 è assolutamente folle.
Fondamentalmente ci sono molti programmi che non elaboreranno i file correttamente se non ottengono l'EOF EOL finale.
GCC ti avverte perché è previsto come parte dello standard C. (apparentemente la sezione 5.1.1.2)
Ciò ha origine fin dai primissimi tempi in cui venivano utilizzati semplici terminali. Il carattere newline è stato utilizzato per attivare un 'flush' dei dati trasferiti.
Oggi, il carattere newline non è più richiesto. Certo, molte app hanno ancora problemi se la newline non è presente, ma considererei un bug in quelle app.
Se tuttavia hai un formato di file di testo in cui è richiesta la nuova riga, otterrai una semplice verifica dei dati molto economica: se il file termina con una riga che non ha una nuova riga alla fine, sai che il file è interrotto. Con solo un byte in più per ogni linea, è possibile rilevare file rotti con elevata precisione e quasi nessun tempo di CPU.
Un caso d'uso separato: quando il tuo file di testo è controllato dalla versione (in questo caso specificamente sotto git sebbene si applichi anche ad altri). Se il contenuto viene aggiunto alla fine del file, la riga che era in precedenza l'ultima riga sarà stata modificata per includere un carattere di nuova riga. Ciò significa che blame
il file ing per scoprire quando l'ultima riga è stata modificata mostrerà l'aggiunta del testo, non il commit prima che tu volessi davvero vedere.
\n
). Problema risolto.
Oltre ai motivi pratici di cui sopra, non mi sorprenderebbe se i creatori di Unix (Thompson, Ritchie, et al.) Oi loro predecessori Multics si rendessero conto che esiste un motivo teorico per usare i terminatori di linea anziché i separatori di linea: Con la linea terminatori, è possibile codificare tutti i possibili file di linee. Con i separatori di linea, non c'è differenza tra un file di zero linee e un file contenente una singola riga vuota; entrambi sono codificati come file contenente zero caratteri.
Quindi, i motivi sono:
wc -l
non conterà una "linea" finale se non termina con una nuova riga.cat
funziona e funziona senza complicazioni. Copia solo i byte di ciascun file, senza alcuna necessità di interpretazione. Non credo che ci sia un DOS equivalente a cat
. L'uso copy a+b c
finirà per fondere l'ultima riga del file a
con la prima riga del file b
.Me lo sono chiesto da anni. Ma oggi ho trovato una buona ragione.
Immagina un file con un record su ogni riga (es: un file CSV). E che il computer stava scrivendo i record alla fine del file. Ma si è schiantato improvvisamente. Accidenti era l'ultima riga completa? (non è una bella situazione)
Ma se terminiamo sempre l'ultima riga, lo sapremmo (controlla semplicemente se l'ultima riga è terminata). Altrimenti dovremmo probabilmente scartare l'ultima riga ogni volta, solo per sicurezza.
Presumibilmente semplicemente che un codice di analisi si aspettava che fosse lì.
Non sono sicuro che lo considererei una "regola", e certamente non è qualcosa a cui aderisco religiosamente. Il codice più sensato saprà come analizzare il testo (comprese le codifiche) riga per riga (qualsiasi scelta di terminazioni di riga), con o senza una nuova riga sull'ultima riga.
Anzi - se finisci con una nuova linea: esiste (in teoria) una linea finale vuota tra l'EOL e l'EOF? Uno su cui riflettere ...
C'è anche un problema di programmazione pratica con i file privi di nuove righe alla fine: il read
Bash integrato (non conosco altre read
implementazioni) non funziona come previsto:
printf $'foo\nbar' | while read line
do
echo $line
done
Questo stampa solofoo
! Il motivo è che quando read
incontra l'ultima riga, scrive il contenuto $line
ma restituisce il codice di uscita 1 perché ha raggiunto EOF. Questo interrompe il while
ciclo, quindi non raggiungiamo mai la echo $line
parte. Se vuoi gestire questa situazione, devi fare quanto segue:
while read line || [ -n "${line-}" ]
do
echo $line
done < <(printf $'foo\nbar')
Cioè, esegui echo
if se read
fallito a causa di una riga non vuota alla fine del file. Naturalmente, in questo caso ci sarà una nuova riga in più nell'output che non era nell'input.
Perché i file (di testo) dovrebbero terminare con una nuova riga?
Così espresso da molti, perché:
Molti programmi non si comportano bene o falliscono senza di essa.
Anche i programmi che gestiscono bene un file mancano di un finale '\n'
, la funzionalità dello strumento potrebbe non soddisfare le aspettative dell'utente, il che può essere poco chiaro in questo caso d'angolo.
I programmi raramente non consentono final '\n'
(non ne conosco nessuno).
Tuttavia, ciò pone la domanda successiva:
Cosa dovrebbe fare il codice sui file di testo senza una nuova riga?
Più importante: non scrivere codice che presuppone che un file di testo termini con una nuova riga . Supponendo che un file sia conforme a un formato porta a corruzione dei dati, attacchi di hacker e arresti anomali. Esempio:
// Bad code
while (fgets(buf, sizeof buf, instream)) {
// What happens if there is no \n, buf[] is truncated leading to who knows what
buf[strlen(buf) - 1] = '\0'; // attempt to rid trailing \n
...
}
Se '\n'
è necessario il finale finale , avvisare l'utente della sua assenza e delle azioni intraprese. IOW, convalida il formato del file. Nota: questo può includere un limite alla lunghezza massima della linea, alla codifica dei caratteri, ecc.
Definire chiaramente, documentare, la gestione del codice di un finale mancante '\n'
.
Non, per quanto possibile, generare un file in cui manca la fine '\n'
.
È molto tardi qui, ma ho appena affrontato un bug nell'elaborazione dei file e questo è venuto perché i file non terminavano con una nuova riga vuota. Stavamo elaborando file di testo sed
e sed
omettevamo l'ultima riga dall'output che causava una struttura json non valida e l'invio dello stato del resto del processo.
Tutto quello che stavamo facendo era:
C'è un file di esempio che dice: foo.txt
con alcuni json
contenuti al suo interno.
[{
someProp: value
},
{
someProp: value
}] <-- No newline here
Il file è stato creato nella macchina delle vedove e gli script delle finestre stavano elaborando quel file usando i comandi di PowerShell. Tutto bene.
Quando abbiamo elaborato lo stesso file usando il sed
comandosed 's|value|newValue|g' foo.txt > foo.txt.tmp
Il file appena generato era
[{
someProp: value
},
{
someProp: value
e boom, ha fallito il resto dei processi a causa del JSON non valido.
Quindi è sempre una buona pratica terminare il file con una nuova riga vuota.
Ho sempre avuto l'impressione che la regola venisse dai giorni in cui era difficile analizzare un file senza terminare una nuova riga. Cioè, si finirà per scrivere il codice in cui una fine della linea è stata definita dal carattere EOL o EOF. Era solo più semplice supporre che una linea terminasse con EOL.
Comunque credo che la regola derivi dai compilatori C che richiedono la newline. E come sottolineato nell'avvertimento del compilatore “Nessuna nuova riga alla fine del file” , #include non aggiungerà una nuova riga.
Immagina che il file sia in fase di elaborazione mentre il file è ancora generato da un altro processo.
Potrebbe avere a che fare con quello? Un flag che indica che il file è pronto per essere elaborato.
Personalmente mi piacciono le nuove righe alla fine dei file di codice sorgente.
Potrebbe avere la sua origine con Linux o tutti i sistemi UNIX per quella materia. Ricordo che c'erano errori di compilazione (gcc se non sbaglio) perché i file di codice sorgente non terminavano con una nuova riga vuota. Perché è stato fatto in questo modo si lascia a chiedersi.
IMHO, è una questione di stile e opinione personale.
Ai vecchi tempi, non avevo messo quella nuova riga. Un personaggio salvato significa più velocità attraverso quel modem 14.4K.
Successivamente, ho inserito quella nuova riga in modo che sia più facile selezionare la riga finale usando shift + downarrow.