Come leggere la riga completa nel ciclo 'for' con spazi


130

Sto cercando di eseguire un forciclo per il file e voglio visualizzare l'intera riga. Ma invece mostra solo l'ultima parola. Voglio la linea completa.

for j in `cat ./file_wget_med`

do
echo $j

done

risultato dopo l'esecuzione:

Found.

Ecco i miei dati:

$ cat file_wget_med
2013-09-11 14:27:03 ERROR 404: Not Found.

Risposte:


179

foril ciclo si divide quando vede spazi bianchi come spazio, tabulazione o newline. Quindi, dovresti usare IFS (Internal Field Separator) :

IFS=$'\n'       # make newlines the only separator
for j in $(cat ./file_wget_med)    
do
    echo "$j"
done
# Note: IFS needs to be reset to default!

5
E fai attenzione alle virgolette singole su IFS perché IFS = $ "\ n" dividerà anche le stringhe "nn". Ho trovato IFS più affidabile rispetto all'uso di sintassi o funzioni più complicate.
erm3nda,

23
apparentemente si consiglia in unset IFSseguito, in modo che altri comandi non siano influenzati in questa stessa shell.
Boris Däppen,

2
Cosa $significa IFS=$'\n'?
Ren

2
$fa funzionare le interruzioni di riga all'interno della stringa. Questo dovrebbe essere fatto anche local IFS=$'\n'all'interno di una funzione.
Andy Ray,

1
@Renn che è $'...'noto " ANSI-C
Quoting

82

forloop divisi su qualsiasi spazio bianco (spazio, tabulazione, nuova riga) per impostazione predefinita; il modo più semplice di lavorare su una riga alla volta è utilizzare un while readloop, che si divide su newline:

while read i; do echo "$i"; done < ./file_wget_med

Mi aspetto che il tuo comando sputi una parola per riga (è quello che è successo quando l'ho provato con un mio file). Se sta succedendo qualcos'altro, non sono sicuro di cosa potrebbe causarlo.


8
Questa è anche una buona alternativa for i in `ls`; do echo $1; done, reindirizzando l'output al comando while: ls|while read i; do echo $i; done. Funziona su nomi di file / cartelle con spazi, senza la necessità di modificare le variabili di ambiente. Ottima risposta
jishi,

il tempo non ha gestito molto la prima e l'ultima riga, non così come il ciclo for con IFS
grantbow

@jishi Poiché è progettato principalmente per la visualizzazione e risponde alle dimensioni della finestra del terminale e simili, lsnon è considerato sicuro per l'uso con |(l'operatore del tubo). findè più flessibile oltre ad essere più sicuro per questo scopo.
SeldomNeedy,

3
Questa è un'ottima risposta, l'ho usata per trasformare un elenco che avevo in voci PLIST per un'app IOS che stavo sviluppando su mac OSX. Ho sostituito l'input del file eseguendo il piping dello clipboad usando pbpaste ->pbpaste | while read t; do echo "<string>$t</string>"; done
Big Rich

3
ls -1può essere utilizzato con |per garantire un output non formattato di una riga
AndreyS Scherbakov

19
#!/bin/bash
files=`find <subdir> -name '*'`
while read -r fname; do
    echo $fname
done <<< "$files"

Funzionamento verificato, non quello che probabilmente vorrai, ma non è possibile farlo in modo elegante.


1
una stringa qui! più elegante di tutte le altre soluzioni IMO
Eliran Malka

5

Ecco una leggera estensione della risposta di Mitchell Currie, che mi piace a causa del limitato raggio di effetti collaterali, che evita di dover impostare una variabile:

#!/bin/bash
while read -r fname; do
    echo $fname
done <<< "`find <subdir>`"

1
Potresti rimuovere -name '*'e sarebbe lo stesso, no?
wjandrea,

1

Lo scriverei così:

cat ./file_wget_med | while read -r j
do
    echo $j
done

poiché richiede il minimo cambiamento nello script originale (ad eccezione della soluzione che utilizza IFS, ma modifica il bashcomportamento non solo per questa istruzione di controllo del ciclo).


1
Pipe e catnon sono necessari qui. whileloop accetta il reindirizzamento bene, motivo per cui questo di solito è scritto comewhile IFS= read -r line; do...done < input.txt
Sergiy Kolodyazhnyy

0

Mapfile è un modo conveniente per leggere le righe da un file in un array indicizzato, non portatile come letto ma leggermente più veloce. Usando per il ciclo eviti di creare una subshell.

#!/bin/bash

mapfile -t < file.txt

for line in "${MAPFILE[@]}"; do
    echo $line
done

Tenere presente quando si utilizzano pipeline, inserirà il ciclo while in una subshell. Le modifiche all'interno del ciclo while come le variabili non si propagano alla parte esterna dello script.

Esempio:

#!/bin/bash

a=0
printf %s\\n {0..5} | while read; do
  ((a++))
done
echo $a # 'a' will always be 0.

(Soluzione migliore):

#!/bin/bash

b=0
while read; do
  ((b++))
done < <(printf %s\\n {0..5})

echo $b # 'b' equal to 6 (works as expected).

-1

Dandalf si è avvicinato molto a una soluzione funzionale, ma NON si dovrebbe MAI cercare di assegnare il risultato di quantità sconosciute di input (cioè find ~/.gdfuse -name '*') a variabili! O, almeno, provando a fare una cosa del genere tramite una variabile array; se insisti per essere così pigro! Quindi, ecco la soluzione di Dandalf fatta senza la pericolosa manovra; e in tutto in una riga

while read -r fname; do
  echo $fname;
done <<< `find ~/.gdfuse -name '*'

Ehi @odoncaoa, ho provato a fare il comando find senza le doppie virgolette, ma fa apparire tutti i risultati come una riga. Sono confuso sulle "quantità sconosciute di input" con questo comando e sui pericoli coinvolti. Puoi fornire un esempio dei pericoli e come potrebbero teoricamente accadere? Sinceramente non desidero essere pigro al riguardo, mi sento solo più a mio agio in altri linguaggi di programmazione.
Dandalf,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.