Pour être honnête, cette histoire n’a pas grand chose à voir avec vi, sed ou ed, il s’agit plutôt d’expressions régulières et d’un programme en C. A l’origine, il s’agit de modifier une chaine de caractères dans des fichiers de paramètres .xml
et .properties
. Notez bien que trouver le début de la dite chaine qui commence par "oracle:jdbc:thin:@("
est assez simple… Si vous voyez où je veux en venir ! Petit problème, trouver la fin de la chaine est une autre histoire. Dans cette article vous trouverez un programme en C pour trouver la fin de la chaine et vous découvrirez l’expression régulière mis en œuvre pour remplacer facilement cette chaine à l’aide du programme ed
Trouver la fin d’une chaine entre parenthèses
Je n’ai pas trouvé de méthode pour trouver la parenthèse fermante étant donné une parenthèse ouvrante avec une expression régulière. Vous avez le choix et peut-être qu’un script shell ou un programme Perl vous donnera toute satisfaction. Quoiqu’il en soit, j’ai choisi de développer un programme en C pour arriver au résultat facilement; celui-ci nommé find_cbracket.c
est disponible ci-dessous :
#include <stdio.h>
#include <string.h>
/*
* function usage
* explains how the program is supposed to be used
*/
void usage ()
{
fputs("n" , stdout);
fputs("find_cbracketn" , stdout);
fputs(" displays the column that contains the closingn"
, stdout);
fputs(" bracketn" , stdout);
fputs("n" , stdout);
fputs("Usage:n" , stdout);
fputs(" find_cbracket <col>n" , stdout);
fputs(" - <col> points to the column containing "
, stdout);
fputs(" the opening bracketn" , stdout);
fputs(" - stdin is the input streamsn" , stdout);
fputs(" - stdout is where the column number is printedn"
, stdout);
fputs("n" , stdout);
}
/*
* main
* search for the corresponding closing bracket in the line
* and prints its column number
*
*/
int main (int argc, char **argv)
{
/*
* Step 1.
* - checks the number of parameters and set the
* variables according to it;
* - if not OK, displays the program usage and returns an
* error.
*/
if (argc!=2)
{
usage();
return 1;
}
else
{
long column;
column = atol (argv[1]);
char line [32000];
/*
* Step 2.
* - goes to the opening bracket position
* - checks the character is actually a bracket
*/
if (fgets (line, sizeof line, stdin) != NULL)
{
long j;
long z=0;
for (j=0;j<(column-1);j++) ;
if (line[j] != '(')
{
fputs("nError: character [",stderr);
fputs(argv[2],stderr);
fputs(",",stderr);
fputs(argv[3],stderr);
fputs("] is not a bracketn",stderr);
return 1;
}
else
{
/*
* Step 3.
* - Finds the closing bracket or the end of the
* line
*/
long k=1;
while(k>0)
{
j++;
if (line[j] == ')')
k--;
if (line[j] == '(')
k++;
if (line[j] == 'n' || line[j] == ' ')
j=32000;
if (j>=(sizeof line))
{
fputs("nError: no closing bracketn"
,stderr);
k=0;
return 1;
}
}
fprintf(stdout, "%ldn", j+1);
}
}
}
}
Compilez votre programme sous Linux avec gcc; c’est du C90 et ça devrait le faire sous Windows aussi :
gcc -o find_cbracket find_cbracket.c
cut
Une fois votre programme terminé, vous pouvez facilement utiliser la commande cut
pour reconstituer la chaine cible. Pour cela, prenons exemples :
echo "jhhljhuh(kjhjhlkj;oracle:jdbc:thin:@(kjhlkhl(jh())f) jkj"
>meschaines.txt
echo "jhhljhuhj;oracle:jdbc:thin:@(k(dd)hl(jh())f) jkj"
>>meschaines.txt
Le petit programme qui suit fait le travail assez bien :
SAVEIFS=$IFS
IFS=$(echo -en "nb")
for i in `cat meschaines.txt`; do
j="$i"
cdeb=`echo $i | sed -e 's/oracle:jdbc:thin:@(.*$//' |wc -m`
cdeb=`expr $cdeb + 18`
cfin=`echo "$j" |./find_cbracket $cdeb`
if [ $? -eq 1 ]; then
echo "Error"
fi
cutdeb=`expr $cdeb - 1`
cutfin=`expr $cfin + 1`
echo "`echo $j | cut -c -$cutdeb`(XXXXXXXXXXXX)`echo $j | cut -c $cutfin-`"
done
IFS=$SAVEIFS
unset SAVEIFS
remplacer cut
par ed
Le problème de la méthode ci-dessus est que le(s) fichier(s) n’est pas modifié en place et que ça risque de ne pas fonctionner avec des caractères comme les « . Si vous voulez directement modifier le contenu du fichier en place et, si comme dans mon cas, vous avez plusieurs fichiers, vous pouvez vous inspirer du script qui suit qui utilise plutôt la commande ed
:
SAVEIFS=$IFS
IFS=$(echo -en "nb")
line=0
> command.txt
for i in `cat meschaines.txt`; do
line=`expr $line + 1`
j="$i"
cdeb=`echo $i | sed -e 's/oracle:jdbc:thin:@(.*$//' |wc -m`
cdeb=`expr $cdeb + 18`
cfin=`echo "$j" |./find_cbracket $cdeb`
cdeb=`expr $cdeb - 1`
clong=`expr $cfin - $cdeb`
echo "${line}s/(^.{${cdeb}})(.{${clong}})/1(XXXXXXX)/"
| tee -a command.txt
done
echo w >> command.txt
echo q >> command.txt
IFS=$SAVEIFS
unset SAVEIFS
ed meschaines.txt <command.txt
cat meschaines.txt
jhhljhuh(kjhjhlkj;oracle:jdbc:thin:@(ZZZZZ) jkj
jhhljhuhj;oracle:jdbc:thin:@(ZZZZZ) jkj
Note:
Si vous êtes curieux de comprendre comment l’expression régulière ci-dessus fonctionne, je vous invite à faire une recherche sur Google et à lire cet article très intéressant à propos du grouping et des « backreferences » dans les expressions régulière
6 réflexions sur “Remplacer une chaine de caractères grâce à sa position avec vi, ed ou sed”
Oui [^ »] au lieu de ., ça ne le fait pas mal!
Tu peux alors dans ce cas utiliser plutôt la commande suivante :
cat meschaines.txt |perl -lnpe ‘s/oracle:jdbc:thin:@[^ »]+ »/oracle:jdbc:thin:@(ZZZZZ) »‘> meschaines.txt.new
Concernant la correspondance des parenthèses ça semble effectivement pas si évident que ça 🙂
Encore bravo pour tes articles !
Ni d’ailleurs d’un programme dans un autre langage !
Bonjour,
Ta regexp ne vérifie pas que la parenthèse fermante correspond bien à la parenthèse ouvrante que je recherche. Mon problème n’est peut-être pas très bien exprimé; voilà un cas plus proche de la réalité (et oui on peut mettre une parenthèse dans un mot de passe Oracle 11g) :
cat >meschaines.txt <<EOF
<dsn jdbc_url= »oracle:jdbc:thin:@(description=(address=(a)(b)(c))(connect_data=(d))) » user= »scott » password= »P4sSwo0r) »/>
EOF
Cette regexp renvoie :
<dsn jdbc_url= »oracle:jdbc:thin:@(ZZZZZ) »/>
Moi, je recherche :
<dsn jdbc_url= »oracle:jdbc:thin:@(ZZZZZ) » user= »scott » password= »P4sSw0r) »/<
D’après mes recherches, il n’y a pas moyen de faire correspondre parenthèses ouvrantes et fermantes avec une regexp. Mais je suis le premier à ne pas vouloir d’un programme en C !
Grégory
Tu peux aussi utiliser la commande suivante :
cat meschaines.txt |perl -lnpe « s/oracle:jdbc:thin:@(.*) (w+)?/oracle:jdbc:thin:@(ZZZZZ) 1/ » > meschaines.txt.new
ça évite de coder un programme en C. Il doit certainement être possible de le faire en sed et d’utiliser l’option -i pour que le fichier soit modifié directement.
Et essayez dans vi la séquence de commandes suivante :
[Esc]
:%s/(^.{5})(.{3})/1;-)/
[Esc]
:q!
Les commentaires sont fermés.