bash, faire une fonction « grep_debut_verbatim »

Le
Francois Lafont
Bonjour à tous,

Je souhaiterais écrire une sorte fonction de fonction «
grep_debut_verbatim » qui vérifie les conditions suivantes :

* elle lit son entrée standard
* elle attend un unique argument (une chaîne de caractères)
* elle renvoie 0 si la chaîne donnée en argument se trouve en début de
ligne d'au moins une ligne de l'entrée standard et renvoie autre chose
que 0 sinon.
* la chaîne donnée en argument ne doit pas être vue comme une regex mais
comme une « chaîne brute », ie les caractères habituellement spéciaux
pour une regex ne doivent pas l'être et doivent représenter eux-même.

En cherchant un peu, j'ai fait ça :

grep_debut_verbatim ()
{
local motif
# On échappe tous les caractères du motif pour
# éviter qu'un caractère spécial soit interprété
# dans le grep ci-dessous.
motif=$(echo "$1" | sed 's/./\&/g')

while read; do
if echo "$REPLY" | grep -q "^$motif"; then
return 0
fi
done
return 1
}

Ça semble passer avec succès quelques tests :

$ echo '/home/lafont.f' | grep_debut_verbatim '/home/lafont.f'; echo $?
0
$ echo '/home/lafontxf' | grep_debut_verbatim '/home/lafont.f'; echo $?
1

Est-ce que ça vous semble correct ? Je marche un peu sur des œufs avec
cette fonction au niveau de la lecture de l'entrée standard (j'utilise
une variable « made in bash » mais il y a peut-être plus propre) et au
niveau de l'échappement de tous les caractères du motif où ça semble un
peu compliqué mais je n'ai pas trouvé d'autre moyen que celui-là.

--
François Lafont
Vidéos High-Tech et Jeu Vidéo
Téléchargements
Vos réponses
Gagnez chaque mois un abonnement Premium avec GNT : Inscrivez-vous !
Trier par : date / pertinence
Francois Lafont
Le #24939522
Le 06/11/2012 15:41, Francois Lafont a écrit :

grep_debut_verbatim ()
{
local motif
# On échappe tous les caractères du motif pour
# éviter qu'un caractère spécial soit interprété
# dans le grep ci-dessous.
motif=$(echo "$1" | sed 's/./\&/g')

while read; do



Rectification : c'est « while read -r; do »

if echo "$REPLY" | grep -q "^$motif"; then
return 0
fi
done
return 1
}




--
François Lafont
Olivier Miakinen
Le #24939582
Bonjour,

Le 06/11/2012 15:41, Francois Lafont a écrit :

* elle lit son entrée standard
* elle attend un unique argument (une chaîne de caractères)
* elle renvoie 0 si la chaîne donnée en argument se trouve en début de
ligne d'au moins une ligne de l'entrée standard et renvoie autre chose
que 0 sinon.
* la chaîne donnée en argument ne doit pas être vue comme une regex mais
comme une « chaîne brute », ie les caractères habituellement spéciaux
pour une regex ne doivent pas l'être et doivent représenter eux-même.

[...]

motif=$(echo "$1" | sed 's/./\&/g')
[...] grep -q "^$motif"

[...]

Ça semble passer avec succès quelques tests :

$ echo '/home/lafont.f' | grep_debut_verbatim '/home/lafont.f'; echo $?
0
$ echo '/home/lafontxf' | grep_debut_verbatim '/home/lafont.f'; echo $?
1

Est-ce que ça vous semble correct ? [...]



Mon premier test a échoué, dans cygwin sur Windows : j'ai mis un « { »
dans la chaîne à tester, qui est devenu spécial avec « { ». Ça s'est
mis à marcher en remplaçant « grep » par « grep -E », mais le 'man'
n'est pas très rassurant sur ce point :

La version traditionnelle d'egrep ne connaît pas le méta-caractère {, et
certaines implantations d'egrep utilisent { à la place, si bien que des
scripts shell portables devraient éviter { dans les motifs d'egrep et
utiliser [{] pour désigner un caractère {.


En fait, il me semblerait plus simple de ne pas utiliser grep du tout
pour comparer deux chaînes, mais plutôt de tronquer les lignes de
l'entrée standard à la longueur voulue avant des les comparer. La
longueur en question, en bash c'est par exemple ${#1} (ou ${#motif}
si tu as fait motif=$1 ). Je ne sais pas comment le faire de façon
portable.

Exemple :

$ motif«CDE
$ echo "123456789" | cut -b -${#motif}
12345
$ echo "ABCDEFGHIJKLMNOPQRSTU" | cut -b -${#motif}
ABCDE


Cordialement,
--
Olivier Miakinen
Francois Lafont
Le #24939572
Re,

Le 06/11/2012 16:07, Francois Lafont a écrit :
Le 06/11/2012 15:41, Francois Lafont a écrit :

grep_debut_verbatim ()
{
local motif
# On échappe tous les caractères du motif pour
# éviter qu'un caractère spécial soit interprété
# dans le grep ci-dessous.
motif=$(echo "$1" | sed 's/./\&/g')

while read; do



Rectification : c'est « while read -r; do »

if echo "$REPLY" | grep -q "^$motif"; then
return 0
fi
done
return 1
}






Même avec cet ajustement, la fonction ne marche pas tout à fait. Par
exemple, je me fais « bananer » dans ce cas tout bête :

$ echo 'w' | grep_debut_verbatim 'w'; echo $?
1

car w possède une signification au niveau des regex dans grep.

Je précise que j'ai bien vu l'option -F de grep qui semble faire
exactement ce que je veux (ie le motif se représente lui-même et basta)
mais malheureusement je perds l'interprétation du caractère ^ dont j'ai
besoin car je veux que la correspondance se fasse au niveau d'un début
de ligne.

Voilà où j'en suis.

--
François Lafont
Olivier Miakinen
Le #24939632
Le 06/11/2012 16:27, Francois Lafont a écrit :

[...] je me fais « bananer » dans ce cas tout bête :

$ echo 'w' | grep_debut_verbatim 'w'; echo $?
1

car w possède une signification au niveau des regex dans grep.



Eh oui. La solution compliquée consisterait à modifier ton sed
pour qu'il transforme un caractère c en c si ce n'est ni une
lettre ni une accolade, en [c] si c'est une accolade, et en c
si c'est une lettre.

Mais la solution simple est vraiment de ne pas utiliser grep
du tout.
Francois Lafont
Le #24939622
Le 06/11/2012 16:23, Olivier Miakinen a écrit :

En fait, il me semblerait plus simple de ne pas utiliser grep du tout
pour comparer deux chaînes, mais plutôt de tronquer les lignes de
l'entrée standard à la longueur voulue avant des les comparer.



Et oui. Merci pour cette idée simple et pleine de bon sens je pense. En
effet, je sentais bien que je partais dans du n'importe quoi mon grep et
mon sed pour échapper tous les caractères.

La
longueur en question, en bash c'est par exemple ${#1} (ou ${#motif}
si tu as fait motif=$1 ). Je ne sais pas comment le faire de façon
portable.

Exemple :

$ motif«CDE
$ echo "123456789" | cut -b -${#motif}
12345
$ echo "ABCDEFGHIJKLMNOPQRSTU" | cut -b -${#motif}
ABCDE



Ok. Merci pour les explications. Ceci étant, avec cut, il me semble que
je peux avoir des soucis avec des caractères non ASCII. Exemple :

$ echo "ÀÉÇé" | cut -b -3
À

Ceci étant je pense que ton principe de départ est le bon. Je vais
creuser la question.

Merci pour ton aide.

--
François Lafont
Olivier Miakinen
Le #24939712
Le 06/11/2012 16:23, Olivier Miakinen a écrit :

En fait, il me semblerait plus simple de ne pas utiliser grep du tout
pour comparer deux chaînes, mais plutôt de tronquer les lignes de
l'entrée standard à la longueur voulue avant des les comparer. La
longueur en question, en bash c'est par exemple ${#1} (ou ${#motif}
si tu as fait motif=$1 ). Je ne sais pas comment le faire de façon
portable.



Bon, j'ai trouvé un moyen qui est probablement portable, mais au prix
de la lisibilité car c'est vraiment affreux.

$ chaine="ABCDE"
$ motif=$(echo $chaine | sed 's/././g')
$ echo "123456789" | sed "s/($motif).*/1/"
12345
$ echo "ABCDEFGHIJKLMNOPQRSTU" | sed "s/($motif).*/1/"
ABCDE
Olivier Miakinen
Le #24939702
Le 06/11/2012 16:43, Francois Lafont a écrit :

Ok. Merci pour les explications. Ceci étant, avec cut, il me semble que
je peux avoir des soucis avec des caractères non ASCII. Exemple :

$ echo "ÀÉÇé" | cut -b -3
À



Voyons voir...

$ motif="ÀÉÇ"
$ l_motif=$(printf $motif | wc -c)
$ echo ${l_motif}
6
$ echo "ÀÉÇé" | cut -b -${l_motif}
ÀÉÇ

Qu'en penses-tu ?
Francois Lafont
Le #24939692
Le 06/11/2012 16:48, Olivier Miakinen a écrit :

Bon, j'ai trouvé un moyen qui est probablement portable, mais au prix
de la lisibilité car c'est vraiment affreux.

$ chaine="ABCDE"
$ motif=$(echo $chaine | sed 's/././g')
$ echo "123456789" | sed "s/($motif).*/1/"
12345
$ echo "ABCDEFGHIJKLMNOPQRSTU" | sed "s/($motif).*/1/"
ABCDE



Merci bien. Effectivement, ce n'est pas super lisible. :-)

En me basant sur ton principe de base, je suis arrivé à ça :

grep_debut_verbatim ()
{
local motif n debut
motif="$1"
# Le nombre de caractères du motifs.
n=${#motif};

while read -r; do
# On coupe la ligne à n caractères maximum.
debut=${REPLY:0:$n}
if [ "$debut" = "$motif" ]; then
return 0
fi
done
return 1
}

J'espère que ça tient la route... C'est difficile de valider de genre de
choses. :-)

--
François Lafont
Publicité
Poster une réponse
Anonyme