2009-10-30 20 views
6

Sto provando a scorrere una directory di file di testo e combinarli in un unico documento. Funziona alla grande, ma i file di testo contengono frammenti di codice e tutta la mia formattazione viene compressa a sinistra. Tutti gli spazi bianchi iniziali su una riga vengono eliminati.Conservazione dello spazio bianco iniziale durante la lettura di >> scrittura di un file riga per riga in bash

#!/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 

Io sono certamente un noob bash, ma dopo aver cercato di alta e bassa non riuscivo a trovare una soluzione adeguata. Apparentemente BASH odia lo spazio bianco principale in generale.

risposta

3

Invece di:

cat $f | while read line; do 
    echo "$line">>$OUTPUT 
done 

Fate questo:

cat $f >>$OUTPUT 

(. Se c'è un motivo è necessario fare la linea le cose per linea sarebbe bene includere che nella questione)

+0

Anche questo uccide lo spazio. Sono passato a riga per riga per vedere se forse mi avrebbe dato più opzioni per salvare lo spazio principale. –

+1

d'oh NON uccide lo spazio bianco. Grazie uomo:> –

+1

Interessante. Questa risposta è stata downvoted due volte senza spiegazione. Se hai intenzione di downvotare, dì perché. (E se è perché stai pensando "questo potrebbe essere solo un comando di gatto": 1. Non proprio, nota le linee vuote in più inserite tra i file e 2. Sto assumendo (forse erroneamente) che questo è stato uno script ridotto per semplicità e la versione reale potrebbe avere qualche logica aggiuntiva per file.) –

3

questo è un modo eccessivamente costoso di combinare i file.

cat ../best-practices/*.textile > ../best_practices.textile 

se si desidera aggiungere uno spazio (nuova riga) per ogni file come si concatenano, uso awk

awk 'FNR==1{print "">"out.txt"}{print > "out.txt" }' *.textile 

O

awk 'FNR==1{print ""}{print}' file* > out.txt 
+0

d'oh mi hai battuto di 1 secondo. –

+1

bello. Grazie. Amo davvero concettualmente Bash. Ora ho solo bisogno di mettere le mie conoscenze in linea con il mio affetto. Saluti –

+0

il titolo dei miei documenti mi sembra ironico .. heh –

1

Questo ti permette di intervallare a capo tra ogni ingresso file come hai fatto nel tuo script originale:

for f in $FILES; do echo -ne '\n\n' | cat "$f" -; done > $OUTPUT 

Notare che per questa operazione non è quotato $FILES (altrimenti le nuove righe aggiuntive vengono visualizzate una sola volta alla fine di tutto l'output), ma è necessario quotare $f per proteggere gli spazi nei nomi di file, se presenti.

40

Come altri hanno sottolineato, l'uso di cat o awk invece di un ciclo read-echo è un modo molto migliore per farlo - evita il problema della correzione degli spazi bianchi (e un paio di altri su cui non sei incappato) , corre più veloce, e almeno con cat, è semplicemente un codice più pulito. Ciò nonostante, mi piacerebbe provare a far funzionare correttamente il ciclo di lettura-eco.

In primo luogo, il problema di taglio degli spazi bianchi: il comando di lettura taglia automaticamente gli spazi bianchi iniziali e finali; questo può essere risolto cambiando la sua definizione di spazio bianco impostando la variabile IFS in bianco. Inoltre, read presuppone che un backslash alla fine della riga significhi che la riga successiva è una continuazione e che 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 sequenze di escape nella stringa (ad esempio possono trasformarsi in una nuova riga effettiva); per risolvere questo problema, usa invece printf. Infine, proprio come regola generale di igiene dello scripting, non dovresti usare il gatto quando non ne hai realmente bisogno; utilizzare invece il reindirizzamento dell'input. Con queste 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 che circonda: la linea che cerca di definire i file come la lista dei file disponibili .textile ha cita intorno ad esso, il che significa che non viene mai espanso in un vero elenco di file.Il modo migliore per farlo è quello di usare un array:

FILES=(../best-practices/*.textile) 
... 
for f in "${FILES[@]}" 

(e tutte le occorrenze di $ f dovrebbe essere tra virgolette nel caso in cui uno qualsiasi dei nomi di file hanno spazi o altri caratteri divertenti in loro - dovrebbe davvero fai questo anche con $ OUTPUT, anche se è definito sicuro nello script.

Infine, c'è uno echo "">$OUTPUT vicino alla parte superiore dei file loop-over che cancellerà il file di output ogni time through (cioè alla fine, contiene solo l'ultimo file .textile); questo deve essere spostato prima del ciclo. Non sono sicuro se l'intento qui fosse quello di mettere una riga vuota all'inizio del file, o tre righe vuote tra i file (e uno all'inizio e due alla fine), quindi non sono sicuro di cosa sia esattamente la sostituzione appropriata è. Comunque, ecco quello che posso con dopo aver fissato 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 
+0

Grazie per aver dedicato del tempo a questo Gordon, è stato estremamente istruttivo. –

+0

fantastico, guardato dappertutto, grazie –

+0

La migliore risposta qui! Grazie mille per aver avuto il tempo di spiegare in modo così chiaro. :) – Arnlen

0

La risposta corretta, IMO, è this, riprodotto qui di seguito:

while IFS= read line; do 
    check=${line:0:1} 
done < file.txt 

noti che si prenderà cura di situazioni dove l'input viene convogliato da un altro comando e non solo da un file reale.

Si noti che è anche possibile 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 
Problemi correlati