Sed, remplacer les n premières occurrences

Sed, remplacer les n premières occurrences - Shell/Batch - Programmation

Marsh Posté le 07-02-2011 à 22:47:22    

Bonjour,
 
Comme dit dans le titre, je cherche à remplacer les n premières occurrences d'un caractère donné d'une ligne par un autre.
Exemple pour les 4 premiers et a => b:
laaaaaal => lbbbbaal
lalalalalalala =>lblblblblalala
 
Google! ou man! me direz vous... Je ne trouve que ceci:
 
replace/substitute the N first occurences of each line
sed 's/<replaceThis>/<withThis>/<N>' <file>
 
Très bien, sauf que chez moi:

Citation :

chep@Ludwig:~$ echo laaaaal |sed 's/a/b/2'
labaaal

Seule la deuxième occurrence est remplacée
 
J'ai aussi une petite question subsidiaire  :ange:  
Comment remplacer par exemple 4 'a' par 3 'b'? J'arrive a détecter les '4' a mais pas à remplacer par 3 'b'.
 

Citation :

chep@Ludwig:~$ echo laaaal |sed 's/a\{4\}/b/'
lbl


mais

Citation :

chep@Ludwig:~$ echo laaaal |sed 's/a\{4\}/b\{3\}/'
lb{3}l


Évidemment, dans mon cas il suffit d'écrire 'bbb' mais le but serait plutôt d'utiliser une variable pour remplacer n 'a' par m 'b'.
 
 
Merci d'avance.


---------------
deluser --remove-home ptitchep
Reply

Marsh Posté le 07-02-2011 à 22:47:22   

Reply

Marsh Posté le 09-02-2011 à 14:26:45    

Personne? :(


---------------
deluser --remove-home ptitchep
Reply

Marsh Posté le 09-02-2011 à 15:48:46    

Une solution serait de faire une boucle jusqu'à ce que le nombre de remplacement soit atteint ou que la fin de fichier soit atteinte.
A l'intérieur de la boucle, on ferait la recherche et le remplacement pour chaque ligne.
 
La boucle ferait une lecture du fichier de manière classique par :

cat thefichier.txt | while read theligne
do
   ...
done

Ou bien on peut utiliser awk ou gawk ou nawk.
 
On doit aussi pouvoir utiliser Perl ou d'autres langages. Personnellement, j'écrirais un petit code en C, compilé avec gcc, et le problème aurait été résolu en beaucoup moins de temps qu'il n'en aura fallu pour poster la question sur le forum et attendre la première réponse.

Reply

Marsh Posté le 10-02-2011 à 02:08:41    

Merci,
Cela permet de traiter chaque ligne du fichier, mais ça ne permet pas de remplacer les N premières occurrences de chaque ligne. À moins de faire encore une boucle pour chaque ligne, mais quand on m'a vendu sed, on m'a dit qu'il était beaucoup plus puissant :(


---------------
deluser --remove-home ptitchep
Reply

Marsh Posté le 10-02-2011 à 10:58:49    

Je viens de jeter  un oeil au man, et le chiffre qu'on peux mettre à la fin c'est pour remplacer la n-ième occurrence, pas le nombre d'occurrences à remplacer.  
 
Visiblement il n'y a pas moyen non plus de faire comme le cut avec un truc du genre 1-4 :(
 
awk doit pouvoir te permettre de réaliser ca, mais mes connaissances en awk sont assez limitées...

Reply

Marsh Posté le 10-02-2011 à 11:32:20    

C'est très probablement faisable en sed, mais ça demande un niveau d'expertise que j'ai eu il y a bien longtemps, mais que je n'ai plus depuis que j'utilise perl, parce que ça doit être assez complexe à écrire en sed et plus simple en perl.
En perl je procéderais ainsi:
s/(a+)/{my $tmp = substr($1, 1); $tmp =~ tr!a!b!; $tmp}/eg;
On cherche le pattern a+, quand trouvé, on évalue (le e de /eg) {my $tmp = substr($1, 1); $tmp =~ tr!a!b!; $tmp}:
On récupère la sous chaine matchant le pattern (donc avec au moins un a) commençant au 2e caractère, donc ca fait la chaine avec un a de moins, on remplace les a par des b dans cette sous chaine, et enfin on renvoie la chaine (comme on est dans un eval, pas besoin de return...)
C'est donc le résultat de cette évaluation qui est substitué.

 

Testé ainsi:

Code :
  1. #!/usr/local/bin/perl
  2. use strict;
  3. use warnings;
  4.  
  5. my $x = "Biaaall the bad caaaaaat";
  6. $x =~ s/(a+)/{my $tmp = substr($1, 1); $tmp =~ tr!a!b!; $tmp}/eg;
  7. print $x, "\n";

>perl test.pl
>Bibbll the bd cbbbbbt

 

Bon ensuite l'intérêt, c'est qu'on peut en faire une procédure, plus modulaire et réemployable:

Code :
  1. #!/usr/local/bin/perl
  2. use strict;
  3. use warnings;
  4.  
  5. # powersubst(chaine, caractere a remplacer, caractere de remplacement, décalage)
  6. #remplace le caractère a remplacer par le caractère de remplacement
  7. # si décalage est positif n'est pas nul on remplace n occurences initiales
  8. # du caractère a remplacer par n + décalage occurences du caractère de remplacement
  9. # ou rien si n + décalage n'est pas positif
  10. sub powersubst {
  11.    my ($_, $cin, $cout, $decal) = @_;
  12.    if ((not defined($decal)) or (not $decal)) {
  13.     s/$cin/$cout/g;
  14.    }
  15.    else {
  16.     if ($decal < 0) {
  17.         s/($cin+)/{my $tmp = substr($1, 0, $decal);  $tmp =~ s!$cin!$cout!g; $tmp}/eg;
  18.     }
  19.     if ($decal > 0) {
  20.         s/($cin+)/{my $tmp = $1.("$cout" x $decal); $tmp =~ s!$cin!$cout!g; $tmp}/eg;
  21.     }
  22.    }
  23.    return $_;
  24. }
  25.  
  26. my $x = "Biaaall the bad caaaaaat";
  27.  
  28. print powersubst($x, "a", "x", 3), "\n\n";
  29. print powersubst($x, "a", "y", 0), "\n\n";
  30. print powersubst($x, "a", "z", -2), "\n\n";
  31. print powersubst($x, "a", "s" ), "\n";

>perl test.pl
>Bixxxxxxll the bxxxxd cxxxx
>
>Biyyyll the byd cyyyyyyt
>
>Bizll the bd czzzzt
>
>Bisssll the bsd csssssst

 

A+,


Message édité par gilou le 10-02-2011 à 13:25:06

---------------
There's more than what can be linked! --    Iyashikei Anime Forever!    --  AngularJS c'est un framework d'engulé!  --
Reply

Marsh Posté le 10-02-2011 à 11:38:54    

Je ne sais pas si c'est "bien" mais
 

Code :
  1. num=3;
  2. echo laaaaal | sed "`s=$( printf "%${num}s" ); echo " ${s// /s:a:b:;}"`"


 
 

Spoiler :

echo laaaaal | sed 's:a:b:;s:a:b:;s:a:b:;'


---------------
oui oui
Reply

Marsh Posté le 10-02-2011 à 13:50:57    

Essais ça:
 

Code :
  1. sed "s/$i/$j/g"


 
puis tu mets tes regex dans les variables.
 
Et si tu veux travailler dans un intervalle précis de lignes tu rajoutes:
 

Code :
  1. sed "$i,$j s/$k/$l/flags"


 
Espérant t'avoir aidé.
 
Edit : L'incrémentation du flags devrait suffire pour définir le nombre d'occurence


Message édité par efimo le 10-02-2011 à 16:38:13

---------------
FeedBack
Reply

Marsh Posté le 10-02-2011 à 13:59:54    

Oui, mais tout ça répond pas a sa question initiale qui est si j'ai bien compris:
comment remplacer les toutes occurrences de n fois le caractère a par m(n) fois le caractère b, ou m dépend de n (par exemple, m(n) = n-1, ou n+1, ...)
A+,


---------------
There's more than what can be linked! --    Iyashikei Anime Forever!    --  AngularJS c'est un framework d'engulé!  --
Reply

Marsh Posté le 10-02-2011 à 15:33:12    

Merci à tous,
En fait vous avez répondu à mes deux questions.
 
gilou: en effet c'est puissant la fonction en perl et ça répond à ma question subsidiaire donc intéressant.  
Mon but était de le faire avec sed uniquement par curiosité à la base parce que je cherchais à faire les étoiles de ce post:  
http://forum.hardware.fr/hfr/Progr [...] 2303_1.htm
donc je voulais essayer une technique où j'écrirais le nombre max d'étoiles et je remplace les N premières par des espaces :p


---------------
deluser --remove-home ptitchep
Reply

Marsh Posté le 10-02-2011 à 15:33:12   

Reply

Marsh Posté le 12-02-2011 à 11:00:43    

ptitchep a écrit :

Mon but était de le faire avec sed uniquement par curiosité à la base parce que je cherchais à faire les étoiles de ce post.
Donc je voulais essayer une technique où j'écrirais le nombre max d'étoiles et je remplace les N premières par des espaces :p

Pour cela, il suffit de remplacer n occurrences de a par n-1 occurrences de b, en perl, le plus simple serait ceci:
1) Remplacer n occurences de a par n-1 occurences de a: s/(a)(\1*)/$2/g; Dans le second groupe, \1 représente ce qui est matché dans le premier groupe
2) remplacer a par b: tr/a/b/

 

Pour le 1, en sed, cela se transpose a priori ainsi: s/\(a\)\(\1*\)/\2/g et pour le 2, s/a/b/g
donc au final sed 's/\(a\)\(\1*\)/\2/g;s/a/b/g'  (testé sous cygwin)

 

On voit tout de suite aussi que pour remplacer n occurrences de a par n-1 occurrences de b, il va suffire de faire
sed 's/\(a\)\(\1*\)/\1\1\2/g;s/a/b/g'  (testé sous cygwin)

 

echo "**********" | sed ':loop p;s/\(\*\)\(\1*\)/ \2/g;t loop;:pool p;s/\( \)\(\1*\)/*\2/;t pool;d'
 :whistle: sans doute améliorable
A+,


Message édité par gilou le 12-02-2011 à 13:53:19

---------------
There's more than what can be linked! --    Iyashikei Anime Forever!    --  AngularJS c'est un framework d'engulé!  --
Reply

Marsh Posté le 17-02-2011 à 15:02:34    

Merci, j'ai appris pas mal sur sed du coup.
En effet ton expression se simplifie mais je poste ça sur l'autre topic.


---------------
deluser --remove-home ptitchep
Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed