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
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
Rectification : c'est « while read -r; do »
--
François Lafont
Le 06/11/2012 15:41, Francois Lafont a écrit :
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
Le 06/11/2012 16:07, Francois Lafont a écrit :
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
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.
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.
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
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
Voyons voir...
$ motif="ÀÉÇ"
$ l_motif=$(printf $motif | wc -c)
$ echo ${l_motif}
6
$ echo "ÀÉÇé" | cut -b -${l_motif}
ÀÉÇ
Qu'en penses-tu ?
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