Comment lire la ligne complète dans la boucle 'for' avec des espaces

130

J'essaye de lancer une forboucle pour le fichier et je veux afficher la ligne entière. Mais au lieu de cela, il n’affiche que le dernier mot. Je veux la ligne complète.

for j in `cat ./file_wget_med`

do
echo $j

done

résultat après exécution:

Found.

Voici mes données:

$ cat file_wget_med
2013-09-11 14:27:03 ERROR 404: Not Found.
utilisateur192118
la source
liées: stackoverflow.com/questions/9084257/…
Ciro Santilli a annoncé le

Réponses:

179

forLa boucle se scinde lorsqu'elle voit des espaces tels que des espaces, des tabulations ou des nouvelles lignes. Donc, vous devriez utiliser 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!
Radu Rădeanu
la source
5
Et faites très attention aux guillemets simples sur IFS car IFS = $ "\ n" divisera également les chaînes "nn". J'ai trouvé l'ensemble IFS plus fiable que d'utiliser une syntaxe ou des fonctions plus compliquées.
erm3nda
23
apparemment, il est conseillé de le faire unset IFSaprès, afin que les autres commandes ne soient pas influencées par ce même shell.
Boris Däppen
2
Qu'est-ce que cela $signifie IFS=$'\n'?
Ren
2
$fait que les sauts de ligne fonctionnent à l'intérieur de la chaîne. Cela devrait également être fait à l' local IFS=$'\n'intérieur d'une fonction.
Andy Ray
1
@Renn c'est $'...'connu " ANSI-C
Quotation
82

forles boucles divisées sur tous les espaces (espace, tabulation, nouvelle ligne) par défaut; Le moyen le plus simple de travailler sur une ligne à la fois est d'utiliser une while readboucle, qui se divise en nouvelles lignes:

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

Je m'attendrais à ce que votre commande crache un mot par ligne (c'est ce qui s'est passé lorsque je l'ai testé avec mon propre fichier). Si quelque chose d'autre se passe, je ne suis pas sûr de ce qui pourrait en être la cause.

maux de tête
la source
8
Ceci est également une bonne alternative à for i in `ls`; do echo $1; done, par redirigeant la sortie vers la commande while: ls|while read i; do echo $i; done. Cela fonctionne sur les noms de fichiers / dossiers avec des espaces, sans qu'il soit nécessaire de modifier les variables d'environnement. Très bonne réponse.
jishi
le tout n'a pas très bien pris en charge les première et dernière lignes, pas aussi bien que la boucle for avec IFS
grantbow le
@jishi Puisqu'il est principalement conçu pour l'affichage et qu'il réagit à la taille de la fenêtre du terminal, il lsn'est pas considéré comme sûr à utiliser avec |(l'opérateur de canalisation). findest plus flexible en plus d’être plus sûr à cet effet.
SeldomNeedy
3
C’est une excellente réponse. Je l’ai utilisée pour transformer une liste que j’avais en entrées PLIST pour une application IOS que je développais sur mac OSX. J'ai remplacé l'entrée du fichier en pipingant le clipboad à l'aide de pbpaste ->pbpaste | while read t; do echo "<string>$t</string>"; done
Big Rich
3
ls -1peut être utilisé avec |pour garantir une sortie non formatée par ligne
AndreyS Scherbakov
19
#!/bin/bash
files=`find <subdir> -name '*'`
while read -r fname; do
    echo $fname
done <<< "$files"

Vérifié le travail, pas la ligne que vous voulez probablement mais il est impossible de faire si élégamment.

Mitchell Currie
la source
1
une chaîne ici! la plus élégante de toutes les autres solutions IMO
Eliran Malka
5

Voici une légère extension de la réponse de Mitchell Currie, que j'aime bien à cause du petit nombre d'effets secondaires, ce qui évite de devoir définir une variable:

#!/bin/bash
while read -r fname; do
    echo $fname
done <<< "`find <subdir>`"
Dandalf
la source
1
Vous pouvez enlever -name '*'et ce serait la même chose, non?
Wjandrea
1

Je l'écrirais comme ceci:

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

car cela nécessite le moins de changements dans le script d'origine (à l'exception de la solution utilisant IFS, mais il modifie le bashcomportement non seulement pour cette instruction de contrôle de boucle).

mik
la source
1
Tuyau et catsont inutiles ici. whileboucle accepte très bien la redirection, raison pour laquelle ceci est généralement écritwhile IFS= read -r line; do...done < input.txt
Sergiy Kolodyazhnyy
0

Mapfile est un moyen pratique de lire les lignes d'un fichier dans un tableau indexé, pas aussi portable que lu mais légèrement plus rapide. En utilisant la boucle for, vous évitez de créer un sous-shell.

#!/bin/bash

mapfile -t < file.txt

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

Gardez à l'esprit lorsque vous utilisez des pipelines, cela mettra la boucle while dans un sous-shell. Les modifications à l'intérieur de la boucle while, comme les variables, ne se propagent pas à la partie externe du script.

Exemple:

#!/bin/bash

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

(Meilleure solution):

#!/bin/bash

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

echo $b # 'b' equal to 6 (works as expected).
bac0n
la source
-1

Dandalf s’est vraiment approché d’une solution fonctionnelle, mais il ne faut JAMAIS essayer d’attribuer le résultat de quantités d’entrées inconnues (c. find ~/.gdfuse -name '*'-à- d. ) À des variables! Ou, du moins, essayez de faire une telle chose via une variable tableau; si vous insistez pour être aussi paresseux! Alors, voici la solution de Dandalf faite sans la manœuvre dangereuse; et en une seule ligne

while read -r fname; do
  echo $fname;
done <<< `find ~/.gdfuse -name '*'
Odoncaoa
la source
Hey @odoncaoa, j'ai essayé de faire la commande de recherche sans les guillemets, mais tous les résultats apparaissent sous la forme d'une ligne. Je suis confus quant aux "quantités d'entrée inconnues" associées à cette commande et aux dangers encourus. Pouvez-vous donner un exemple des dangers et comment ils pourraient théoriquement se produire? Je ne veux vraiment pas être paresseux, je suis simplement plus à l'aise dans d'autres langages de programmation.
Dandalf