2013-06-14 9 views
11

Ho un file di testo che contiene qualcosa di simile:Perché non si può usare cat per leggere un file riga per riga in cui ogni linea ha delimitatori

abc 123, comma 
the quick brown fox 
jumped over the lazy dog 
comma, comma 

Ho scritto uno script

for i in `cat file` 
do 
    echo $i 
done 

Per qualche motivo, l'output dello script non emette il file riga per riga, ma lo interrompe alle virgole, così come la nuova riga. Perché cat o "for blah in cat xyz" facendo questo e come posso farlo NON farlo? So che posso utilizzare un

while read line 
do 
    blah balh blah 
done < file 

ma voglio sapere perché gatto o il "per blah in" sta facendo questo per promuovere la mia comprensione di comandi UNIX. La pagina man di Cat non mi è stata d'aiuto e la ricerca o il loop nel manuale di bash non ha prodotto alcuna risposta (http://www.gnu.org/software/bash/manual/bashref.html). Grazie in anticipo per il vostro aiuto.

+2

Cercare "IFS". – Kevin

risposta

13

Il problema non è in cat, né nel for ciclo di per sé; è nell'uso delle citazioni posteriori. Quando si scrive uno:

for i in `cat file` 

o (meglio):

for i in $(cat file) 

o (in bash):

for i in $(<file) 

la shell esegue il comando e cattura l'output sotto forma di stringa, separando le parole dai caratteri in $IFS. Se si desidera immettere le righe su $i, è necessario intervenire con IFS o utilizzare il ciclo while. Il ciclo while è migliore se c'è il pericolo che i file elaborati siano grandi; non ha bisogno di leggere l'intero file in memoria tutto in una volta, a differenza delle versioni che usano $(...).

IFS=' 
' 
for i in $(<file) 
do echo "$i" 
done 

Le virgolette intorno alla "$i" sono generalmente una buona idea. In questo contesto, con il $IFS modificato, in realtà non è fondamentale, ma le buone abitudini sono buone abitudini anche così.Si conta alla seguente script:

old="$IFS" 
IFS=' 
' 
for i in $(<file) 
do 
    (
    IFS="$old" 
    echo "$i" 
    ) 
done 

quando il file di dati contiene più spazi tra le parole:

$ cat file 
abc     123,   comma 
the quick brown fox 
jumped over the lazy dog 
comma, comma 
$ 

uscita:

$ sh bq.sh 
abc     123,   comma 
the quick brown fox 
jumped over the lazy dog 
comma, comma 
$ 

Senza le virgolette:

$ cat bq.sh 
old="$IFS" 
IFS=' 
' 
for i in $(<file) 
do 
    (
    IFS="$old" 
    echo $i 
    ) 
done 
$ sh bq.sh 
abc 123, comma 
the quick brown fox 
jumped over the lazy dog 
comma, comma 
$ 
+0

thx per il vostro aiuto e risposta. Sono un po 'confuso qui con bash/* nix. Non ho cambiato IFS. È impostato come newline per impostazione predefinita. L'ho controllato con echo "IFS = $ IFS word test" e la stringa "word test" è stata stampata sulla riga seguente, così sappiamo che è \ n di default. In ogni caso, utilizzando l'IFS predefinito, interrompe la mia riga alla virgola anche se IFS = \ n. Quando faccio come suggerito sopra, impostando l'IFS in modo esplicito su \ n, quindi stampa tutta la mia riga senza rompere la virgola. Qualche idea sul perché funzioni quando è impostata esplicitamente come \ n e non funziona quando per impostazione predefinita IFS è già \ n? Grazie ancora. – Classified

+1

Il valore predefinito di IFS è (usando un pezzo di 'bash'-speak)' $ '\ t \ n''; cioè, consiste di vuoto, tab, newline. Questo probabilmente altera la tua analisi. Quando dici "interruzioni nella virgola", intendi che si interrompe nello spazio dopo la virgola, credo, che è coerente con IFS che contiene spazio (e tab e newline). –

+0

grazie ancora per la spiegazione. =) – Classified

1

IFS - Il separatore di campo interno può essere impostato per ottenere ciò che si desidera.

Per leggere una linea intera in una volta, l'uso: IFS = ""

1

il ciclo accoppiato con un cambiamento del separatore campo interno (IFS) leggerà file come inteso

per un ingresso

abc 123, comma 
the quick brown fox 
jumped over the lazy dog 
comma, comma 

ciclo For accoppiato con una variazione IFS

old_IFS=$IFS 
IFS=$'\n' 
for i in `cat file` 
do 
     echo $i 
done 
IFS=$old_IFS 

risultati in

abc 123, comma 
the quick brown fox 
jumped over the lazy dog 
comma, comma 
+2

Basta usare 'IFS = read -r line' per conservare tutti gli spazi nella riga. – chepner

+1

L'unico motivo per cui la spaziatura è "persa" con il ciclo "while" è perché hai usato "echo $ line" piuttosto che "echo" $ line "". Se la spaziatura è importante, racchiudere la variabile di riferimento tra virgolette. –

+1

Come dice chepner, questo dovrebbe essere 'read -r' per evitare effetti collaterali indesiderati (valutazione delle sequenze di escape backslash). –

5

È possibile utilizzare IFS variabile specifica che si desidera una nuova riga come separatore di campo:

+1

Non sicuro: hai impedito la divisione delle stringhe, ma non hai impedito l'espansione globale. Se una riga contiene '*', verrà espansa a un elenco di nomi nella directory corrente durante l'eco. –