Remplacer une chaine de caractères grâce à sa position avec vi, ed ou sed

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”

  1. 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 !

  2. 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

  3. 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.

Les commentaires sont fermés.