Preservare i principali spazi bianchi durante la lettura di > > scrivendo un file riga per riga in bash
-
22-07-2019 - |
Domanda
Sto cercando di scorrere una directory di file di testo e combinarli in un unico documento. Funziona benissimo, ma i file di testo contengono frammenti di codice e tutta la mia formattazione viene compressa a sinistra. Tutto lo spazio bianco principale su una linea viene rimosso.
#!/bin/sh
OUTPUT="../best_practices.textile"
FILES="../best-practices/*.textile"
for f in "$FILES"
do
echo "Processing $f file..."
echo "">$OUTPUT
cat $f | while read line; do
echo "$line">>$OUTPUT
done
echo >>$OUTPUT
echo >>$OUTPUT
done
Devo ammettere che sono un indovino, ma dopo aver cercato in alto e in basso non sono riuscito a trovare una soluzione adeguata. Apparentemente BASH odia lo spazio bianco principale in generale.
Soluzione
Invece di:
cat $f | while read line; do
echo "$line">>$OUTPUT
done
Fai questo:
cat $f >>$OUTPUT
(Se c'è una ragione per cui devi fare le cose riga per riga, sarebbe bene includerlo nella domanda.)
Altri suggerimenti
Come altri hanno sottolineato, usare cat o awk invece di un ciclo read-echo è un modo molto migliore per farlo - evita il problema del taglio degli spazi bianchi (e un paio di altri su cui non ti sei imbattuto), corre più veloce, e almeno con cat, è semplicemente un codice più pulito. Tuttavia, mi piacerebbe prendere una pugnalata affinché il ciclo di lettura dell'eco funzioni correttamente.
In primo luogo, il problema del taglio degli spazi bianchi: il comando read taglia automaticamente gli spazi bianchi iniziali e finali; questo può essere risolto modificando la sua definizione di spazio bianco impostando la variabile IFS su vuoto. Inoltre, read presuppone che una barra rovesciata alla fine della riga significhi che la riga successiva è una continuazione e dovrebbe essere unita insieme a questa; per risolvere questo problema, usa il suo flag -r (raw). Il terzo problema qui è che molte implementazioni di echo interpretano le sequenze di escape nella stringa (ad esempio possono trasformare \ n in una nuova riga effettiva); per risolvere questo problema, utilizzare invece printf. Infine, proprio come una regola generale di igiene dello scripting, non dovresti usare cat quando non ne hai davvero bisogno; utilizzare invece il reindirizzamento dell'input. Con tali modifiche, il ciclo interno è simile al seguente:
while IFS='' read -r line; do
printf "%s\n" "$line">>$OUTPUT
done <$f
... ci sono anche un paio di altri problemi con lo script circostante: la linea che cerca di definire FILES come l'elenco dei file .textile disponibili ha delle virgolette attorno, il che significa che non viene mai espanso in un vero elenco di file . Il modo migliore per farlo è usare un array:
FILES=(../best-practices/*.textile)
...
for f in "${FILES[@]}"
(e tutte le occorrenze di $ f devono essere racchiuse tra virgolette nel caso in cui uno qualsiasi dei nomi di file contenga spazi o altri caratteri divertenti in essi - dovrebbe farlo anche con $ OUTPUT, sebbene dato che è definito nello script effettivamente sicuro di smettere.)
Infine, c'è un echo " " > $ OUTPUT
vicino alla parte superiore dei file di loop-over che cancellerà il file di output ogni volta (cioè alla fine, esso contiene solo l'ultimo file .textile); questo deve essere spostato prima del ciclo. Non sono sicuro se l'intento qui era quello di mettere una singola riga vuota all'inizio del file o tre righe vuote tra i file (e una all'inizio e due alla fine), quindi non sono sicuro di cosa la sostituzione appropriata è. Ad ogni modo, ecco cosa posso fare dopo aver risolto tutti questi problemi:
#!/bin/sh
OUTPUT="../best_practices.textile"
FILES=(../best-practices/*.textile)
: >"$OUTPUT"
for f in "${FILES[@]}"
do
echo "Processing $f file..."
echo >>"$OUTPUT"
while IFS='' read -r line; do
printf "%s\n" "$line">>"$OUTPUT"
done <"$f"
echo >>"$OUTPUT"
echo >>"$OUTPUT"
done
è un modo troppo costoso di combinare i file.
cat ../best-practices/*.textile > ../best_practices.textile
se si desidera aggiungere uno spazio vuoto (nuova riga) a ciascun file durante la concatenazione, utilizzare awk
awk 'FNR==1{print "">"out.txt"}{print > "out.txt" }' *.textile
o
awk 'FNR==1{print ""}{print}' file* > out.txt
Ciò ti consente di separare le nuove righe tra ogni file di input come hai fatto nello script originale:
for f in $FILES; do echo -ne '\n\n' | cat "$f" -; done > $OUTPUT
Nota che $ FILES
non è quotato perché funzioni (altrimenti le nuove righe aggiuntive appaiono solo una volta alla fine di tutto l'output), ma $ f
deve essere citato per proteggere gli spazi nei nomi dei file, se presenti.
La risposta corretta, imo, è questa , riprodotta di seguito:
while IFS= read line; do
check=${line:0:1}
done < file.txt
Tieni presente che si occuperà delle situazioni in cui l'input viene reindirizzato da un altro comando e non solo da un file reale.
Nota che puoi anche semplificare il reindirizzamento come mostrato di seguito.
#!/bin/bash
OUTPUT="../best_practices.textile"
FILES="../best-practices/*.textile"
for f in "$FILES"
do
echo "Processing $f file..."
{
echo
while IFS= read line; do
echo "$line"
done < $f
echo
echo;
} > $OUTPUT
done