J'ai un fichier texte nommé links.txt qui ressemble à ceci
link1
link2
link3
Je veux parcourir ce fichier ligne par ligne et effectuer une opération sur chaque ligne. Je sais que je peux le faire en utilisant la boucle while mais comme j'apprends, j'ai pensé utiliser une boucle for. J'ai utilisé la substitution de commandes comme celle-ci
a=$(cat links.txt)
Ensuite, j'ai utilisé la boucle comme ceci
for i in $a; do ###something###;done
Je peux aussi faire quelque chose comme ça
for i in $(cat links.txt); do ###something###; done
Maintenant, ma question est quand j'ai substitué la sortie de la commande cat dans une variable a, les nouveaux caractères de ligne entre link1 link2 et link3 sont supprimés et remplacés par des espaces
echo $a
les sorties
link1 link2 link3
puis j'ai utilisé la boucle for. Est-ce toujours qu'une nouvelle ligne est remplacée par un espace quand on fait une substitution de commande ??
Cordialement
Les sauts de ligne ont été perdus, car le shell avait effectué fractionnement de champ après la substitution de commande.
Dans POSIX Substitution de commandes section:
Le shell doit étendre la substitution de commande en exécutant la commande dans un environnement de sous-shell (voir Environnement d'exécution Shell) et en remplaçant la substitution de commande (le texte de la commande plus le "$ ()" ou les guillemets) qui l'entourent par la sortie standard de la commande, en supprimant séquences d'un ou plusieurs caractères à la fin de la substitution. Les caractères incorporés avant la fin de la sortie ne doivent pas être supprimés; cependant, ils peuvent être traités comme des délimiteurs de champ et éliminés lors de la division du champ, en fonction de la valeur de l'IFS et de la cotation en vigueur . Si la sortie contient des octets nuls, le comportement n'est pas spécifié.
Valeur par défaut IFS
(au moins dans bash
):
$ printf '%q\n' "$IFS"
$' \t\n'
Dans votre cas, vous ne définissez pas IFS
ou n'utilisez pas de guillemets doubles, donc le caractère de retour à la ligne sera éliminé lors du fractionnement du champ.
Vous pouvez conserver les retours à la ligne, par exemple en définissant IFS
sur vide:
$ IFS=
$ a=$(cat links.txt)
$ echo "$a"
link1
link2
link3
Les retours à la ligne sont échangés à certains moments car ce sont des caractères spéciaux. Afin de les conserver, vous devez vous assurer qu'ils sont toujours interprétés, en utilisant des guillemets:
$ a="$(cat links.txt)"
$ echo "$a"
link1
link2
link3
Maintenant, puisque j'ai utilisé des guillemets chaque fois que je manipulais les données, les caractères de nouvelle ligne (\n
) a toujours été interprété par le Shell, et est donc resté. Si vous oubliez de les utiliser à un moment donné, ces caractères spéciaux seront perdus.
Le même comportement se produira si vous utilisez votre boucle sur des lignes contenant des espaces. Par exemple, étant donné le fichier suivant ...
mypath1/file with spaces.txt
mypath2/filewithoutspaces.txt
La sortie dépendra de l'utilisation ou non de guillemets:
$ for i in $(cat links.txt); do echo $i; done
mypath1/file
with
spaces.txt
mypath2/filewithoutspaces.txt
$ for i in "$(cat links.txt)"; do echo "$i"; done
mypath1/file with spaces.txt
mypath2/filewithoutspaces.txt
Maintenant, si vous ne voulez pas utiliser de guillemets, il existe une variable Shell spéciale qui peut être utilisée pour changer le séparateur de champ Shell (IFS
). Si vous définissez ce séparateur sur le caractère de nouvelle ligne, vous vous débarrasserez de la plupart des problèmes.
$ IFS=$'\n'; for i in $(cat links.txt); do echo $i; done
mypath1/file with spaces.txt
mypath2/filewithoutspaces.txt
Par souci d'exhaustivité, voici un autre exemple, qui ne repose pas sur la substitution de sortie de commande. Après un certain temps, j'ai découvert que cette méthode était considérée comme plus fiable par la plupart des utilisateurs en raison du comportement même de l'utilitaire read
.
$ cat links.txt | while read i; do echo $i; done
Voici un extrait de la page de manuel de read
:
L'utilitaire de lecture doit lire une seule ligne à partir de l'entrée standard.
Puisque read
obtient son entrée ligne par ligne, vous êtes sûr qu'il ne se cassera pas chaque fois qu'un espace apparaîtra. Passez-lui simplement la sortie de cat
à travers un tube, et il itérera très bien sur vos lignes.
Edit: Je peux voir dans d'autres réponses et commentaires que les gens hésitent beaucoup à utiliser cat
. Comme jasonwryan l'a dit dans son commentaire, une manière plus plus appropriée de lire un fichier dans Shell est d'utiliser la redirection de flux (<
), comme vous pouvez le voir dans la réponse de val0x00ff ici . Cependant, comme la question n'est pas "comment lire/traiter un fichier dans la programmation Shell", ma réponse se concentre plus sur le comportement des guillemets, et non le reste.
Pour ajouter mon accent, for
boucles itèrent sur mots. Si votre dossier est:
one two
three four
Ensuite, cela émettra quatre lignes:
for Word in $(cat file); do echo "$Word"; done
Pour parcourir les lignes d'un fichier, procédez comme suit:
while IFS= read -r line; do
# do something with "$line" <-- quoted almost always
done < file
Vous pouvez utiliser read
depuis bash. Recherchez également le mapfile
while read -r link
do
printf '%s\n' "$link"
done < links.txt
Ou en utilisant mapfile
mapfile -t myarray < links.txt
for link in "${myarray[@]}"; do printf '%s\n' "$link"; done