Preservare i principali spazi bianchi durante la lettura di > > scrivendo un file riga per riga in bash

StackOverflow https://stackoverflow.com/questions/1648055

  •  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.

È stato utile?

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
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top