Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

bash, faire une fonction « grep_debut_verbatim »

8 réponses
Avatar
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

8 réponses

Avatar
Francois Lafont
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
Avatar
Olivier Miakinen
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 :

<cit.>
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 {.
</cit.>


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
Avatar
Francois Lafont
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
Avatar
Olivier Miakinen
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.
Avatar
Francois Lafont
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
Avatar
Olivier Miakinen
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
Avatar
Olivier Miakinen
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 ?
Avatar
Francois Lafont
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