Perché i file di testo dovrebbero terminare con una nuova riga?

Presumo che tutti qui abbiano familiarità con l’adagio secondo cui tutti i file di testo dovrebbero terminare con una nuova riga. Conosco questa “regola” da anni ma mi sono sempre chiesto: perché?

Perché è così che lo standard POSIX definisce una linea :

3.206 Linea
Una sequenza di zero o più caratteri non più un carattere di fine riga .

Pertanto, le righe che non terminano con un carattere di nuova riga non sono considerate righe effettive. Ecco perché alcuni programmi hanno problemi nell’elaborare l’ultima riga di un file se non è terminata la nuova riga.

C’è almeno un duro vantaggio per questa linea guida quando si lavora su un emulatore di terminale: tutti gli strumenti Unix si aspettano questa convenzione e funzionano con esso. 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 *.txt foobar baz 

E, come dimostra anche l’esempio precedente, quando si visualizza il file sulla riga di comando (ad es. Tramite more ), un file terminato con una 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, si incorre in un lavoro extra quando si gestiscono gli strumenti Unix predefiniti.

Ora, su sistemi non conformi a POSIX (al giorno d’oggi è principalmente Windows), il punto è discutibile: i file generalmente non terminano con una nuova riga e la definizione (informale) di una linea potrebbe essere ad esempio “testo separato da una nuova riga” (notare l’enfasi). Questo è completamente valido. Tuttavia, per i dati strutturati (ad esempio il codice di programmazione) rende l’analisi minimamente più complicata: generalmente significa che i parser devono essere riscritti. Se un parser è stato scritto originariamente con la definizione POSIX in mente, potrebbe essere più semplice modificare il token stream piuttosto che il parser – in altre parole, aggiungere un token “newline artificiale” alla fine dell’input.

Ogni riga deve essere terminata con un carattere di nuova riga, incluso l’ultimo. Alcuni programmi hanno problemi nell’elaborare l’ultima riga di un file se non è terminata la nuova riga.

GCC avvisa a riguardo non perché non può elaborare il file, ma perché deve fare parte dello standard.

Lo standard di linguaggio C dice che Un file sorgente non vuoto termina con un carattere di nuova riga, che non deve essere immediatamente preceduto da un carattere barra rovesciata.

Poiché questa è una clausola “must”, dobbiamo emettere un messaggio diagnostico per una violazione di questa regola.

Questo è nella sezione 2.1.1.2 dello standard ANSI C 1989. Sezione 5.1.1.2 dello standard ISO C 1999 (e probabilmente anche lo standard ISO C 1990).

Riferimento: l’archivio di posta GCC / GNU .

Questa risposta è un tentativo di una risposta tecnica piuttosto che un’opinione.

Se vogliamo essere puristi di POSIX, definiamo una linea come:

Una sequenza di zero o più caratteri non più un carattere di fine riga .

Fonte: http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_206

Una riga incompleta come:

Una sequenza di uno o più caratteri non alla fine del file.

Fonte: http://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 NUL caratteri e nessuno può superare {LINE_MAX} byte di lunghezza, incluso il carattere . Sebbene POSIX.1-2008 non distingua tra file di testo e file binari (vedere lo standard ISO C), molte utilità producono solo risultati prevedibili o significativi quando si opera su file di testo. Le utilità standard che hanno tali restrizioni specificano sempre “file di testo” nelle loro sezioni STDIN o INPUT FILES.

Fonte: http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_397

Una stringa come:

Una sequenza contigua di byte terminata da e includendo il primo byte null.

Fonte: http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_396

Da questo, quindi, possiamo dedurre che l’unica volta in cui potremmo incontrare qualsiasi tipo di problema è se trattiamo il concetto di una linea di un file o di un file come un file di testo (essendo che un file di testo è un’organizzazione di zero o più linee e una linea che sappiamo deve terminare con una ).

Caso in questione: wc -l filename .

Dal manuale del wc leggiamo:

Una linea è definita come una stringa di caratteri delimitata da un carattere .

Quali sono le implicazioni per i file JavaScript, HTML e CSS che sono file di testo ?

Nei browser, negli IDE moderni e in altre applicazioni front-end non ci sono problemi con l’eliminazione di EOL in EOF. Le applicazioni analizzeranno correttamente i file. Dal momento che non tutti i sistemi operativi sono conformi allo standard POSIX, quindi non sarebbe pratico per strumenti non OS (ad esempio i browser) gestire i file secondo lo standard POSIX (o qualsiasi standard di livello OS).

Di conseguenza, possiamo essere relativamente sicuri che EOL in EOF non avrà praticamente alcun impatto negativo a livello di applicazione, indipendentemente dal fatto che sia eseguito su un sistema operativo UNIX.

A questo punto possiamo dire con sicurezza 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 nessuna è sicura.

Possiamo fare un ulteriore passo avanti e affermare che, per quanto riguarda NodeJS, anche questo non può aderire allo standard POSIX perché può essere eseguito in ambienti non POSIX compatibili.

Cosa ci rimane allora? Utensili a livello di sistema.

Ciò significa che gli unici problemi che possono sorgere sono gli strumenti che fanno uno sforzo per far rispettare la loro funzionalità alla semantica di POSIX (ad esempio la definizione di una linea come mostrato in wc ).

Anche così, non tutte le shell aderiranno automaticamente a POSIX. Bash, per esempio, non ha il comportamento POSIX predefinito. C’è un interruttore per abilitarlo: POSIXLY_CORRECT .

Spunti di riflessione sul valore di EOL : http://www.rfc-editor.org/EOLstory.txt

Rimanendo sulla traccia di utensili, per tutti gli scopi pratici, consideriamo questo:

Lavoriamo con un file che non ha EOL. Al momento della stesura di questo file il file in questo esempio è un JavaScript minifatto 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 dimensione del file cat è esattamente la sum delle sue singole parti. Se la concatenazione dei file JavaScript è una preoccupazione 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 vuoi 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 menziona solo l’input di lettura fino a EOF, non . Notare che l’ -n di cat stamperà anche una linea terminata non- (o riga incompleta ) come una linea – essendo che il conteggio inizia da 1 (secondo l’ man ).

-n Numera le linee di uscita, a partire da 1.

Ora che comprendiamo in che modo POSIX definisce una linea , questo comportamento diventa ambiguo o, in realtà, non conforms.

Comprendere lo scopo e la conformità di uno strumento determinato aiuterà a determinare quanto sia critico terminare i file con un EOL. In C, C ++, Java (JAR), ecc … alcuni standard dettano una nuova riga per la validità – non esiste uno standard simile per JS, HTML, CSS.

Ad esempio, invece di usare wc -l filename si potrebbe fare awk '{x++}END{ print x}' filename , e stare tranquilli che il successo dell’attività non è compromesso da un file che potremmo voler elaborare che non abbiamo scritto ( ad es. una libreria di terze parti come il minisito JS che curl d) – a meno che il nostro intento fosse davvero quello di contare le righe nel senso conforms a POSIX.

Conclusione

Ci saranno pochissimi casi di utilizzo della vita reale in cui saltare EOL in EOF per determinati file di testo come JS, HTML e CSS avrà un impatto negativo – se non del tutto. Se ci affidiamo alla presenza di , limitiamo l’affidabilità dei nostri strumenti solo ai file che produciamo e ci apriamo a potenziali errori introdotti da file di terze parti.

Morale della trama: strumenti ingegneristici che non hanno la debolezza di affidarsi a EOL in EOF.

Sentiti libero di postare casi d’uso come si applicano a JS, HTML e CSS dove possiamo esaminare come saltare EOL ha un effetto negativo.

Potrebbe essere correlato alla differenza tra :

  • file di testo (ogni riga dovrebbe terminare in un fine riga)
  • file binario (non ci sono vere “linee” di cui parlare e la lunghezza del file deve essere preservata)

Se ogni riga termina con un fine riga, questo evita, per esempio, che il concatenamento di due file di testo renderebbe l’ultima riga della prima esecuzione nella prima riga del secondo.

Inoltre, un editor può controllare a caricamento se il file termina con un fine linea, lo salva nella sua opzione locale ‘eol’ e lo usa durante la scrittura del file.

Alcuni anni fa (2005), molti editori (ZDE, Eclipse, Scite, …) hanno “dimenticato” l’EOL finale, che non era molto apprezzato .
Non solo, ma hanno interpretato l’EOL finale in modo errato, come ‘inizia una nuova linea’, e in realtà iniziano a mostrare un’altra linea come se esistesse già.
Questo era molto visibile con un file di testo “corretto” con un editor di testo ben educato come vim, rispetto ad aprirlo in uno degli editor di cui sopra. Ha visualizzato una riga in più 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, 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 

Fondamentalmente ci sono molti programmi che non elaborano i file correttamente se non ottengono l’EOL EOL finale.

GCC ti avvisa di questo perché è previsto come parte dello standard C. (sezione 5.1.1.2 apparentemente)

Avviso del compilatore “No newline at end of file”

Ciò ha origine dai primissimi giorni in cui venivano utilizzati terminali semplici. Il carattere newline è stato utilizzato per triggersre un ‘flush’ dei dati trasferiti.

Oggi, il carattere di nuova riga non è più richiesto. Certo, molte app hanno ancora problemi se il newline non c’è, ma considererei un bug in quelle app.

Se invece hai un formato di file di testo in cui richiedi la nuova riga, ottieni 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 è rotto. Con un solo byte in più per ogni riga, è ansible rilevare file danneggiati con elevata precisione e quasi nessun tempo di CPU.

Un caso d’uso separato: quando il tuo file di testo è controllato in 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 precedentemente era l’ultima riga sarà stata modificata per includere un carattere di nuova riga. Ciò significa che blame la blame al file per scoprire quando quella riga è stata modificata per l’ultima volta mostrerà l’aggiunta del testo, non il commit prima di quello che volevi effettivamente vedere.

C’è anche un problema pratico di programmazione con file mancanti di newline alla fine: il read Bash integrato (non so di altre implementazioni di read ) non funziona come previsto:

 printf $'foo\nbar' | while read line do echo $line done 

Questo stampa solo foo ! Il motivo è che quando read incontra l’ultima riga, scrive il contenuto in $line ma restituisce il codice di uscita 1 perché ha raggiunto EOF. Questo interrompe il ciclo while , quindi non raggiungiamo mai la parte echo $line . Se vuoi gestire questa situazione, devi fare quanto segue:

 while read line || [ -n "${line-}" ] do echo $line done < <(printf $'foo\nbar') 

Cioè, fai l' echo se la read non è riuscita a causa di una riga non vuota alla fine del file. Naturalmente, in questo caso ci sarà una nuova riga aggiuntiva nell'output che non era nell'input.

Presumibilmente semplicemente che qualche 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ù sensibile saprà come analizzare il testo (incluse le codifiche) riga per riga (qualsiasi scelta di terminazioni di riga), con o senza una nuova riga sull’ultima riga.

Infatti – se finisci con una nuova linea: c’è (in teoria) una linea finale vuota tra EOL e EOF? Uno su cui riflettere …

Oltre alle suddette ragioni pratiche, 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 utilizzare i terminatori di linea anziché i separatori di riga: With line terminatori, puoi codificare tutti i possibili file di linee. Con i separatori di linee, non c’è differenza tra un file di zero righe e un file contenente una singola riga vuota; entrambi sono codificati come un file che contiene zero caratteri.

Quindi, le ragioni sono:

  1. Perché è così che definisce POSIX.
  2. Perché alcuni strumenti lo aspettano o “si comportano male” senza di esso. Ad esempio, wc -l non conterà una “linea” finale se non termina con una nuova riga.
  3. Perché è semplice e conveniente. Su Unix, cat funziona e funziona senza complicazioni. Copia solo i byte di ciascun file, senza alcuna necessità di interpretazione. Non penso che ci sia un equivalente DOS per cat . L’uso di copy a+bc finirà per fondere l’ultima riga del file a con la prima riga del file b .
  4. Perché un file (o stream) di linee zero può essere distinto da un file di una riga vuota.

Perché i file (di testo) dovrebbero terminare con una nuova riga?

Come ben express da molti, perché:

  1. Molti programmi non si comportano bene o falliscono senza di essi.

  2. Anche i programmi che gestiscono bene un file non hanno una terminazione '\n' , la funzionalità dello strumento potrebbe non soddisfare le aspettative dell’utente – cosa che potrebbe non essere chiara in questo caso d’angolo.

  3. I programmi raramente impediscono il definitivo '\n' (non ne conosco nessuno).


Eppure questo pone la prossima domanda:

Cosa dovrebbe fare il codice sui file di testo senza una nuova riga?

  1. Più importante: non scrivere codice che presuppone che un file di testo termini con una nuova riga . Supponendo che un file sia conforms 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 ... } 
  2. Se è necessario il finale finale '\n' , avvisare l’utente della sua assenza e dell’azione intrapresa. IOW, convalida il formato del file. Nota: questo può includere un limite alla lunghezza massima della linea, alla codifica dei caratteri, ecc.

  3. Definisci chiaramente, documenta, la gestione del codice di una finale mancante '\n' .

  4. Non creare , come ansible, un file manca il finale '\n' .

Me lo sono chiesto da anni. Ma mi sono imbattuto in una buona ragione oggi.

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 improvvisamente si è schiantato. Gee era l’ultima riga completa? (non una bella situazione)

Ma se terminassimo sempre l’ultima riga, allora sapremmo (basta controllare se l’ultima riga è terminata). Altrimenti dovremmo probabilmente scartare l’ultima linea ogni volta, solo per essere sicuri.

Ho sempre avuto l’impressione che la regola venisse dai giorni in cui l’analisi di un file senza una nuova riga finale era difficile. Cioè, si finirebbe per scrivere il codice in cui un fine riga era definito dal carattere EOL o EOF. Era più semplice ipotizzare una linea terminata con EOL.

Comunque credo che la regola sia derivata dai compilatori C che richiedono la nuova riga. E come indicato nell’avvertenza del compilatore “No newline at end of file” , # include un newline non incluso.

Immagina che il file sia in fase di elaborazione mentre il file viene 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 con tutti i sistemi UNIX. Ricordo errori di compilazione (gcc se non mi sbaglio) perché i file del codice sorgente non finivano con una nuova riga vuota. Perché è stato fatto in questo modo uno è lasciato a chiedersi.

IMHO, è una questione di stile e opinione personale.

Nei tempi antichi, non ho messo quella newline. Un personaggio salvato significa più velocità attraverso quel modem 14.4K.

Successivamente, inserisco quella nuova in modo che sia più facile selezionare la linea finale usando shift + downarrow.