OVH Cloud OVH Cloud

Problème avec bash

9 réponses
Avatar
Luc Martineau
Bonjour,
J'ai un petit problème avec mon script bash.

Je veux connaitre la ligne la plus longue d'un fichier texte: nom.txt
Il ressemble a ça:

=== Debut fichier ===
ILYA KOVALCHUK
BILL GUERIN
MARIUSZ CZERKAWSKI
MILAN HEJDUK
...
=== fin fichier ===

Voici le script que j'ai écrit:

=== Début du script ===

# /bin/bash

long_max=-1
nom="fooname"

cat < nom.txt | while true
do
read ligne
if [ "$ligne" = "" ];
then
echo "Le nom le plus long est : ${nom} avec ${long_max} caracteres."
break;
echo nothing!!!
fi

if [ ${#ligne} -gt ${long_max} ];
then
echo ========================================================
echo ${#ligne} $long_max $nom
nom=${ligne}
long_max=${#ligne}
echo ${#ligne} $long_max $nom
echo ---------------------------------------------------------
fi
done

echo "Le nom le plus long est : ${nom} avec ${long_max} caracteres."

exit

=== Fin script ===

Les valeurs des variables avant le "break" sont correctes.

Après la boucle, les variables contiennent les valeur initialisées:
fooname et -1

Pourquoi??

Red Hat 7.2

/bin/bash --version
GNU bash, version 2.05.8(1)-release (i386-redhat-linux-gnu)
Copyright 2000 Free Software Foundation, Inc.


Merci beaucoup

Luc

9 réponses

Avatar
Stephane Chazelas
2003/11/19, 02:56(-05), Luc Martineau:
Je veux connaitre la ligne la plus longue d'un fichier texte: nom.txt
Il ressemble a ça:

[...]

Voici le script que j'ai écrit:


C'est une erreur de "programmer" ça en shell. Les shells ne sont
pas faits pour ça, ils sont fait pour appeler des utilitaires
qui servent à faire le job.

awk est le plus approprié en l'occurrence :

awk 'length > m {m=length} END {printf "%.17gn", m}' < nom.txt

=== Début du script == >
# /bin/bash

long_max=-1
nom="fooname"

cat < nom.txt | while true


cat est l'outil pour concaténer il est assez rare d'en avoir
besoin. Ceci est un UUOC (useless use of cat) dans le jargon.

do
read ligne


Si tu regardes la page de man, si tu ne passes pas l'option -r
et si tu ne changes pas IFS, read fait des trucs en plus.

read lit un caractère à la fois (pour ne pas dépasser le n qui
termine la ligne) sur un pipe, c'est carrément pas optimum.

if [ "$ligne" = "" ];


Pour tester la fin de fichier, il vaut mieux tester le code de
retour de read. Il pourrait y avoir des lignes vides dans ton
fichier.

[ -n "$ligne" ] ou [ "" = "$ligne" sont plus safes encore
qu'avec les bash récents, il ne devrait pas y avoir de problème.

[...]
if [ ${#ligne} -gt ${long_max} ];


C'est toujours une bonne habitude de mettre des quotes autour
des variables. Quand ce sont des nombres, il y a peu de chance
qu'il y ait des problèmes, mais l'ajout des quotes devrait être
automatique au moins pour avoir du code sémantiquement plus
correct.

if [ "${#ligne}" -gt "${long_max}" ]

then
echo ======================================================= > echo ${#ligne} $long_max $nom


Même chose

[...]
Les valeurs des variables avant le "break" sont correctes.

Après la boucle, les variables contiennent les valeur initialisées:
fooname et -1

Pourquoi??


À cause de ton UUOC. Quand tu as un pipe, il faut que les deux
commandes ou groupes de commandes qui sont de part et d'autre
soient éxécutés dans un processus différent. Ici, pas de bol, ta
boucle while était éxécutée dans un fils, le père n'a pas accès
à ses variables.

max=0
while IFS= read <&3 -r ligne; do
[ "${#ligne}" -gt "$max" ] && max=${#ligne}
done 3< nom.txt

Si tu tiens à le faire en shell mais c'est une erreur amha.

--
Stéphane ["Stephane.Chazelas" arobase "free.fr"]

Avatar
Stephane Dupille
max=0
while IFS= read <&3 -r ligne; do
[ "${#ligne}" -gt "$max" ] && max=${#ligne}
done 3< nom.txt


Pourquoi un troisième fd ? Pour pas simplement :
max=0
while IFS= read -r ligne; do
[ "${#ligne}" -gt "$max" ] && max=${#ligne}
done < nom.txt

--
Subject: Re: gravier
j'ai acheter encarta 2001 de luxe j'aimerai le graver
impossible a faire pour le moment
-+- CP in <http://www.le-gnu.net> : À sec ou au gravier ? -+-

Avatar
Stephane Chazelas
2003/11/19, 13:50(+01), Stephane Dupille:
max=0
while IFS= read <&3 -r ligne; do
[ "${#ligne}" -gt "$max" ] && max=${#ligne}
done 3< nom.txt


Pourquoi un troisième fd ? Pour pas simplement :
max=0
while IFS= read -r ligne; do
[ "${#ligne}" -gt "$max" ] && max=${#ligne}
done < nom.txt


Si tu utilises le fd 0, l'entrée standard va être nom.txt pour
toutes les commandes de ta boucle while alors que seule une
(read) a besoin et surtout *doit* lire nom.txt. Ça ne pose pas
de problème dans ce cas précis, mais ça en poserait dans
d'autres, donc, c'est une bonne habitude d'utiliser un autre fd.

--
Stéphane ["Stephane.Chazelas" arobase "free.fr"]


Avatar
Luc Martineau
Stephane Chazelas wrote:
2003/11/19, 02:56(-05), Luc Martineau:


C'est une erreur de "programmer" ça en shell. Les shells ne sont
pas faits pour ça, ils sont fait pour appeler des utilitaires
qui servent à faire le job.

awk est le plus approprié en l'occurrence :

awk 'length > m {m=length} END {printf "%.17gn", m}' < nom.txt



Je savais qu'il existait des méthodes beaucoup plus performantes.

Je préfère de loin avoir un script facilement lisible prenant au pire
quelques secondes à s'exécuter.

Je vais faire un peu de lecture, awk, sed...
J'ai eu toute une leçon.

Merci beaucoup

Luc

Avatar
Pascal Bourguignon
Stephane Chazelas writes:

2003/11/19, 02:56(-05), Luc Martineau:
Je veux connaitre la ligne la plus longue d'un fichier texte: nom.txt
Il ressemble a ça:

[...]

Voici le script que j'ai écrit:


C'est une erreur de "programmer" ça en shell. Les shells ne sont
pas faits pour ça, ils sont fait pour appeler des utilitaires
qui servent à faire le job.

awk est le plus approprié en l'occurrence :

awk 'length > m {m=length} END {printf "%.17gn", m}' < nom.txt

[...]

max=0
while IFS= read <&3 -r ligne; do
[ "${#ligne}" -gt "$max" ] && max=${#ligne}
done 3< nom.txt

Si tu tiens à le faire en shell mais c'est une erreur amha.


Mais si c'est un petit fichier, c'est plus efficace de le faire dans
bash que de forker un nouveau processus!

Même si c'est un gros fichier, ça ne doit pas faire une grosse
différence: c'est que de l'entrée/sortie et awk est aussi interprété!

Ceci dit, je suis d'accord que pour faire de l'algorithme, les shells
ne sont pas adaptés. Utiliser clisp ou scsh!

--
__Pascal_Bourguignon__
http://www.informatimago.com/


Avatar
Stephane Chazelas
2003/11/19, 19:34(+01), Pascal Bourguignon:
[...]
Mais si c'est un petit fichier, c'est plus efficace de le faire dans
bash que de forker un nouveau processus!


Et si c'est un petit fichier, on s'en fout un peu que ça se
fasse en 0.001s ou 0.01, Alors que sur un gros on fait la
différence entre 10s et 100s.

Même si c'est un gros fichier, ça ne doit pas faire une grosse
différence: c'est que de l'entrée/sortie et awk est aussi interprété!


bash a un work around au fait que read ne peut lire qu'un byte à
la fois quand l'input est un fichier, donc, ça ne sera pas aussi
lamentable qu'avec d'autres shells, mais je parlais surtout de
considérations de design en fait.

Ceci dit, je suis d'accord que pour faire de l'algorithme, les shells
ne sont pas adaptés. Utiliser clisp ou scsh!


no comment...

--
Stéphane ["Stephane.Chazelas" arobase "free.fr"]

Avatar
Pascal Bourguignon
Stephane Chazelas writes:

2003/11/19, 19:34(+01), Pascal Bourguignon:
[...]
Mais si c'est un petit fichier, c'est plus efficace de le faire dans
bash que de forker un nouveau processus!


Et si c'est un petit fichier, on s'en fout un peu que ça se
fasse en 0.001s ou 0.01, Alors que sur un gros on fait la
différence entre 10s et 100s.

Même si c'est un gros fichier, ça ne doit pas faire une grosse
différence: c'est que de l'entrée/sortie et awk est aussi interprété!


bash a un work around au fait que read ne peut lire qu'un byte à
la fois quand l'input est un fichier, donc, ça ne sera pas aussi
lamentable qu'avec d'autres shells, mais je parlais surtout de
considérations de design en fait.


Je ne m'attendais pas à ça: bash est bien plus lent à lire que awk ou que wc:

[ tmp]$ od < asfuwx5ID | time bash -c 'while read line ; do : ; done'
15.91user 15.45system 0:45.43elapsed 69%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (525major+193minor)pagefaults 0swaps


[ tmp]$ od < asfuwx5ID | time awk '{i++;}'
0.38user 0.01system 0:01.78elapsed 21%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (173major+24minor)pagefaults 0swaps


[ tmp]$ od < asfuwx5ID | time wc
258246 2324201 16654827
0.43user 0.01system 0:01.92elapsed 22%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (131major+17minor)pagefaults 0swaps

Ceci dit, je suis d'accord que pour faire de l'algorithme, les shells
ne sont pas adaptés. Utiliser clisp ou scsh!


no comment...

--
Stéphane ["Stephane.Chazelas" arobase "free.fr"]


--
__Pascal_Bourguignon__
http://www.informatimago.com/


Avatar
Stephane Chazelas
2003/11/21, 07:53(+01), Pascal Bourguignon:
[...]
bash a un work around au fait que read ne peut lire qu'un byte à
la fois quand l'input est un fichier, donc, ça ne sera pas aussi
~~~~~~~~~~~~~~~~~~~~~~~~~~~~


lamentable qu'avec d'autres shells, mais je parlais surtout de
considérations de design en fait.


Je ne m'attendais pas à ça: bash est bien plus lent à lire que awk ou que wc:

[ tmp]$ od < asfuwx5ID | time bash -c 'while read line ; do : ; done'
15.91user 15.45system 0:45.43elapsed 69%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (525major+193minor)pagefaults 0swaps
[...]


Quand l'input est un fichier, il lit par bloc et fait des lseeks
en arrière quand il lance des commandes externes (dans mon
exemple, il n'y avait pas de commande externe et l'input était
un fichier).

Quand, l'input est un pipe comme dans ton exemple, il est obligé
de lire caractère par caractère.

Évidemment, ça reste lent. ksh93 est un peu plus rapide et a le
même genre d'optimisation. zsh n'a pas ce genre d'optimisation
mais est plus rapide sur les pipes.

Note que l'awk de GNU est globalement plus lent que les autres
implémentations d'awk (surtout dans les locales utf-8).

--
Stéphane ["Stephane.Chazelas" arobase "free.fr"]


Avatar
Pascal Bourguignon
Stephane Chazelas writes:
la fois quand l'input est un fichier, donc, ça ne sera pas aussi
~~~~~~~~~~~~~~~~~~~~~~~~~~~~


Quand l'input est un fichier, il lit par bloc et fait des lseeks
en arrière quand il lance des commandes externes (dans mon
exemple, il n'y avait pas de commande externe et l'input était
un fichier).

Quand, l'input est un pipe comme dans ton exemple, il est obligé
de lire caractère par caractère.


Ça prouve le succès psychologique des pipes, si je ne fais plus la
différence entre un pipe et un fichier!

--
__Pascal_Bourguignon__
http://www.informatimago.com/