Remplacer un champ d'une positionnée donnée

Résolu/Fermé
Decon Messages postés 91 Date d'inscription mercredi 29 août 2007 Statut Membre Dernière intervention 8 octobre 2014 - Modifié par Decon le 31/08/2011 à 16:44
Decon Messages postés 91 Date d'inscription mercredi 29 août 2007 Statut Membre Dernière intervention 8 octobre 2014 - 6 sept. 2011 à 16:50
Bonjour,

Je souhaite parcourir un fichier en shell sh ou ksh (peu importe) et pour une positionnée donnée, remplacer le champ à cette position par autre chose.
Plus concrètement:
Soit mon fichier $fic_prov avec des lignes du type:
ACC|168063100|168063100|1|1680631||0|0||19900101000000||7||250  
ABB|168063110|168063112|1|1680631001|1680631|1|0||20110118000000|  

Je souhaite donc que pour une ligne commençant par ABB, il me remplace le deuxième champs 168063110 ( le délimiteur étant | ) par <champ_2>10; où 10 représente les deux derniers caractères du champ 2 (168063110).

Pour finir, je souhaite mettre le résultat final (fichier modifié) dans un autre fichier.

Actuellement j'essaye un truc pas très potable qui me provoque une erreur.
Voici un extrait du code:
a=1  
for fic_prov in $Repertoire/type_fichier*  
do  

nom_fic='basename $fic_prov .txt'_"$a"_"txt"   

awk -F "|" '{ $2 = "<champ_2>substr($2,length("$2")-2,length("$2"))" ; print $0 > $nom_fic}' $fic_prov     

a=a+1  

done  

Erreur:
awk: syntax error near line 1 
awk: bailing out near line 1 

Merci d'avance

A voir également:

18 réponses

mamiemando Messages postés 33357 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 13 novembre 2024 7 805
Modifié par mamiemando le 31/08/2011 à 17:18
Personnellement je travaillerais dans un premier temps avec un fichier toto.awk pour trouver la bonne syntaxe, quitte à la mettre en ligne. Je ne sais pas si c'est exactement ce que tu veux mais :

BEGIN {  
    FS = "|";  
    OFS = "|";  
}  

$1 ~ /^ABB$/ {  
    $2 = "<champ2>" length($2) ";";  
    printed = 1;  
    print $0;  
}  

{  
    if(!printed) print $0;
    printed = 0;
}  


... j'obtiens :

(mando@aldur) (~) $ cat toto.txt   
ACC|168063100|168063100|1|1680631||0|0||19900101000000||7||250    
ABB|168063110|168063112|1|1680631001|1680631|1|0||20110118000000|    

(mando@aldur) (~) $ awk -f toto.awk toto  
ACC|168063100|168063100|1|1680631||0|0||19900101000000||7||250  
ABB|<champ2>9;|168063112|1|1680631001|1680631|1|0||20110118000000|


Bonne chance
1
synopsis8 Messages postés 1364 Date d'inscription dimanche 15 mars 2009 Statut Membre Dernière intervention 1 juin 2013 243
31 août 2011 à 18:13
Bonjour,

Je te propose une autre méthode avec sed.

sed et awk sont LES deux outils à connaitre de préférence.

sed '/ABB/ s/[0-9]*\([0-9][0-9]\)/<fichier>\1/1' toto.txt

0
Decon Messages postés 91 Date d'inscription mercredi 29 août 2007 Statut Membre Dernière intervention 8 octobre 2014 2
31 août 2011 à 19:40
Merci de vous intéresser à mon problème.

Mamiemando dans l'exemple que j'ai donné les deux derniers chiffres étaient "10" alors qu'après remplacement tu as "9;".
Aussi, ce que je n'ai pas dit c'est que le but est de faire plusieurs traitements de ce type dans et sur plusieurs lignes. Ce qui alourdirait énormément l'idée d'un ficher awk à part.

Synopsis8, je ne pourrai pas tester ton code ce soir; mais à vu d'oeil, j'ai l'impression qu'il fera ce traitement pour tous les champs. Je ne vois pas à quel moment tu lui indique à quel champ s'applique ce traitement.
0
synopsis8 Messages postés 1364 Date d'inscription dimanche 15 mars 2009 Statut Membre Dernière intervention 1 juin 2013 243
31 août 2011 à 19:50
Je vais essayer de te décomposer la commande

/ABB/ # Seulement les lignes contenant "ABB"

[0-9] # commençant par un chiffre (que je n'enregistre pas)
* # tout les caractères qui suivent
\([0-9][0-9]\) # finissant par 2 chiffres (que j'enregistre cette fois)
/<fichier> # que je remplace par le mot "fichier" (en fait je me suis trompé, c'était plutôt "champ2" que tu voulais mettre
\1 # aurquel je rajoute le premier tampon de mémoire (celui qui contient les 2 derniers chiffres)
/1 # Uniquement la première occurence de chiffres (après ABB). Si je n'avais pas mis ça, il aurait remplacé la dernière, et si j'avais voulu qu'il remplace tous les champs, j'aurais mis /g

Voila.
0
zipe31 Messages postés 36402 Date d'inscription dimanche 7 novembre 2010 Statut Contributeur Dernière intervention 27 janvier 2021 6 417
31 août 2011 à 20:13
Salut,

[0-9] # commençant par un chiffre (que je n'enregistre pas)
* # tout les caractères qui suivent

En fait l'expression doit se lire avec les deux combinés et de ce fait ne correspond plus trop à ta définition ;-(

[0-9]*
Tous caractères numériques y compris aucun.


/1 # Uniquement la première occurrence de chiffres (après ABB). Si je n'avais pas mis ça, il aurait remplacé la dernière, et si j'avais voulu qu'il remplace tous les champs, j'aurais mis /g
Non plus ;-\

Par défaut sed ne traite que la 1ère occurrence rencontrée, d'où en général l'utilisation du "g" pour qu'il traite toutes les occurrences. Ici le "/1" est inutile. Par contre si on avait voulu traiter la seconde occurrence, là oui il aurait été utile de mettre "/2"

;-))
0

Vous n’avez pas trouvé la réponse que vous recherchez ?

Posez votre question
Decon Messages postés 91 Date d'inscription mercredi 29 août 2007 Statut Membre Dernière intervention 8 octobre 2014 2
31 août 2011 à 20:13
Si j'ai bien compris cette méthode ne traiterait au final que soit le champ suivant ABB pour(/1), soit tous les champs(/g).
Par contre vu que mes traitements vont différer suivant le champ, c'est la raison pour laquelle la notion de n° de champ est importante pour moi.
0
synopsis8 Messages postés 1364 Date d'inscription dimanche 15 mars 2009 Statut Membre Dernière intervention 1 juin 2013 243
31 août 2011 à 20:15
Et bien tu changes le numéro en fonction de tes nécéssités /1 ou /2, etc
Tu peux cumuler aussi....
0
zipe31 Messages postés 36402 Date d'inscription dimanche 7 novembre 2010 Statut Contributeur Dernière intervention 27 janvier 2021 6 417
31 août 2011 à 20:35
Attention toutefois, sed ne traite pas des champs comme awk, mais des motifs, et il faut que chaque motif soit bien isolable et semblable pour jouer avec les /1 /2 /3 etc., avec un bémol énorme, on ne peut aller au-delà de /9 ;-\
0
synopsis8 Messages postés 1364 Date d'inscription dimanche 15 mars 2009 Statut Membre Dernière intervention 1 juin 2013 243
31 août 2011 à 23:10
Zipe31, tu as vu ça où au fait qu'on ne pouvait pas aller au delà de 9 ? C'est bizarre, je mets ce que je veux comme valeur, et ça marche.
0
synopsis8 Messages postés 1364 Date d'inscription dimanche 15 mars 2009 Statut Membre Dernière intervention 1 juin 2013 243
31 août 2011 à 20:46
C'est exact, la méthode n'est pas mieux, c'est un autre outil qu'il faut à mon sens connaître comme awk.

Celà étant, je n'ai jamais eu l'occasion d'essayer des /10 ou même des /* voir /$
Ce serait à tenter, En même temps, on peux enchainer les statements en les séparant par des points virgules.

Pour moi, sed et awk sont des outils confortables pour faire du parsing sans avoir à apprendre python (qui n'est jamais installé en natif sous UNIX par exemple).
J'ai essayé python, c'est bien, mais comme tout, il faut se donner le temps.
0
Decon Messages postés 91 Date d'inscription mercredi 29 août 2007 Statut Membre Dernière intervention 8 octobre 2014 2
31 août 2011 à 20:46
D'accord,
C'est un petit peu problématique dans ce cas parce que je peut avoir par ligne une cinquantaine de lignes.
0
synopsis8 Messages postés 1364 Date d'inscription dimanche 15 mars 2009 Statut Membre Dernière intervention 1 juin 2013 243
31 août 2011 à 20:53
Si je m'en réfère à ton intitulé, c'est le 2 champ qui t'intéresse, mais si tu veux essayer avec des /10 ou plus, il faut tester, sinon tu fais avec awk et tu rentres le numéro de colonne si tu en as tant beaucoup
0
Decon Messages postés 91 Date d'inscription mercredi 29 août 2007 Statut Membre Dernière intervention 8 octobre 2014 2
31 août 2011 à 20:56
Oui j'ai mis dans l'intitulé l'exemple pour le deuxième champ en espérant avoir une solution extensible à n'importe quel champ.
Grand merci pour ton aide.
J'essayerai cette solution demain.
0
mamiemando Messages postés 33357 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 13 novembre 2024 7 805
1 sept. 2011 à 00:34
Mamiemando dans l'exemple que j'ai donné les deux derniers chiffres étaient "10" alors qu'après remplacement tu as "9;".

J'y peux rien si la longueur de la chaîne fait 9 :-) Moi je fais ce qui est dit dans l'énoncé, rien ne t'empêche d'ajouter 1 au calcul...

Aussi, ce que je n'ai pas dit c'est que le but est de faire plusieurs traitements de ce type dans et sur plusieurs lignes. Ce qui alourdirait énormément l'idée d'un ficher awk à part.

Moi je veux bien mais en awk tu dois bien préciser les délimiteurs (soit 4 lignes). Tu peux les passer en options (option -F etc...) mais ça ne règlera pas le problème du délimiteur de sortie et surtout, ce n'est pas forcément plus lisible ou plus clair.

Ensuite le code est extensible et me paraît lisible. Il y a peut être moyen de faire mieux mais pour gagner quelques lignes je ne vois pas trop l'intérêt.

Rien ne t'empêche de l'écrire sur une ligne si c'est plus à ton idée :

awk 'BEGIN {  FS = "|";  OFS = "|";  } $1 ~ /^ABB$/ {  $2 = "<champ2>" length($2) ";";  printed = 1;  print $0;  } {  if(!printed) print $0;  printed = 0; }'  toto.txt


Par rapport à la discussion sur sed, je trouve que c'est une bonne idée de le connaître, c'est une syntaxe que tu retrouveras dans vim. Mais à mon avis pour des utilisations avancées ça fait doublon avec awk donc ce n'est pas forcément utile de connaître les deux.

Personnellement je fais les substitutions simples dans vim (comparé à sed, on peut annuler si le résultat ne nous plaît pas, débugger l'expression régulire avec "set hls" etc...), et les plus compliquées avec sed.

Enfin je ne pense pas qu'il soit possible de s'en sortir avec juste une substitution, car injecter le calcul de la longueur va être problématique pour le faire avec juste un sed. On peut faire un truc moche, comme par exemple un gsub sur une expression régulière qui va extraire le second champ et le remplacer mais ce sera beaucoup moins lisible et moins évolutif. Après c'est à toi de voir...
0
zipe31 Messages postés 36402 Date d'inscription dimanche 7 novembre 2010 Statut Contributeur Dernière intervention 27 janvier 2021 6 417
1 sept. 2011 à 14:03
Essaie avec ça :

$ cat plop
ACC|168063100|168063100|1|1680631||0|0||19900101000000||7||250  
ABB|168063110|168063112|1|1680631001|1680631|1|0||20110118000000|  

$ awk 'BEGIN { FS="|";OFS="|" } $1 ~ /^ABB/ {X=length($2)-1; sub(/.*/,"<champ_2>"substr($2, X, length("$2")),$2) }{ print }' plop 
ACC|168063100|168063100|1|1680631||0|0||19900101000000||7||250  
ABB|<champ_2>10|168063112|1|1680631001|1680631|1|0||20110118000000|  

$
0
Decon Messages postés 91 Date d'inscription mercredi 29 août 2007 Statut Membre Dernière intervention 8 octobre 2014 2
1 sept. 2011 à 14:32
Merci
0
Decon Messages postés 91 Date d'inscription mercredi 29 août 2007 Statut Membre Dernière intervention 8 octobre 2014 2
3 sept. 2011 à 01:28
Autre question!
Avez vous une idée de comment passer une variable en paramètre à awk.
Je veux redirriger le résultat en sortie dans un fichier dont le nom et chemin lui aura été passer en paramètre.

Merci d'avance.
0
lami20j Messages postés 21331 Date d'inscription jeudi 4 novembre 2004 Statut Modérateur, Contributeur sécurité Dernière intervention 30 octobre 2019 3 569
3 sept. 2011 à 09:28
Salut,

Utilise les rédirections > ou >>
Le fichier doit être entre guillemets

lami20j@debian-acer:~$ cat plop
1 2 3 4 5
lami20j@debian-acer:~$ awk '{print $1 " " $3 }' plop
1 3
lami20j@debian-acer:~$ awk '{print $1 " " $3  > "plop2" } ' plop
lami20j@debian-acer:~$ cat plop2
1 3
lami20j@debian-acer:~$ awk '{print $1 " " $3  > "/home/lami20j/plop3" } ' plop
lami20j@debian-acer:~$ cat plop3
1 3


0
Decon Messages postés 91 Date d'inscription mercredi 29 août 2007 Statut Membre Dernière intervention 8 octobre 2014 2
Modifié par Decon le 3/09/2011 à 09:55
Oui c'est ce que je fait actuellement. Je veux faire quelque chose de plus évoluée.
En effet, j'ai un répertoire dont le chemin est récupéré depuis une variable $Repertoire et mon nom de fichier de sortie est incrémenté à chaque lancement.
exemple:
 print  > "$Repertoire/$nom_fic"       
où nom_fic =' basename plop .txt'_n°incrémenté".txt"        

Le problème est qu'en mettant des variables entre " " il ne retourne pas la valeur de la variable(c'est pas mieux sans " " non plus).

print  > "$Repertoire/$nom_fic"
mettra ainsi le résultat dans le fichier de nom $Repertoire/$nom_fic et non dans le fichier plop .txt'_n°incrémenter_"txt à l'adresse $repertoire.

En cherchant un peu, j'ai vu qu'il y a possibilité de passer les variables en paramètres au niveau du begin je crois pour que awk puissent les interpréter dans le corps de la fonction.
Mais bon vous vous imaginez bien que mes tests n'ont pas fonctionné ...
0
lami20j Messages postés 21331 Date d'inscription jeudi 4 novembre 2004 Statut Modérateur, Contributeur sécurité Dernière intervention 30 octobre 2019 3 569
Modifié par lami20j le 3/09/2011 à 10:20
Re,


c'est pas mieux sans " " non plus).

Oui, c'est mieux mais il faut faire comme ça ;-)


lami20j@debian-acer:~$ cat plop  
1 2 3 4 5  
lami20j@debian-acer:~$ awk '{fic="plop2";print $1 " " $3 } ' plop  
1 3  
lami20j@debian-acer:~$ cat plop2  
cat: plop2: No such file or directory  
lami20j@debian-acer:~$ awk '{fic="plop2";print $1 " " $3  > fic } ' plop  
lami20j@debian-acer:~$ cat plop2  
1 3  
lami20j@debian-acer:~$ awk '{rep="/home/lami20j/";fic="plop3";print $1 " " $3  > rep fic } ' plop  
lami20j@debian-acer:~$ cat plop3  
1 3
0
zipe31 Messages postés 36402 Date d'inscription dimanche 7 novembre 2010 Statut Contributeur Dernière intervention 27 janvier 2021 6 417
3 sept. 2011 à 10:17
Salut,

En fait tu as le choix.

Ou tu déclare ta variable avec l'option "-v" de awk :

awk  -v var=MYVAR '{ print var }'
ou :
awk  -v var=$MYVAR '{ print var }'


Soit tu inclues directement tes variables du shell dans awk, mais en ayant pris soin de sortir de awk afin que celles-ci soient bien interprétées :

var=MYVAR
awk '{ print ' "$MYVAR" ' }'
0
lami20j Messages postés 21331 Date d'inscription jeudi 4 novembre 2004 Statut Modérateur, Contributeur sécurité Dernière intervention 30 octobre 2019 3 569
3 sept. 2011 à 10:23
Salut,

J'ai pensé aussi à l'option -v ou une variable shell, mais j'ai pensé qu'il vaudrait utiliser une variable à l'intérieur du awk. ;-)
0
lami20j Messages postés 21331 Date d'inscription jeudi 4 novembre 2004 Statut Modérateur, Contributeur sécurité Dernière intervention 30 octobre 2019 3 569
3 sept. 2011 à 10:25
Re,

Aussi avec BEGIN juste pour voir

lami20j@debian-acer:~$ awk 'BEGIN{rep="/home/lami20j/";fic="plop4"}{print $1 " " $3  > rep fic } ' plop
lami20j@debian-acer:~$ cat plop4
1 3
0
Decon Messages postés 91 Date d'inscription mercredi 29 août 2007 Statut Membre Dernière intervention 8 octobre 2014 2
3 sept. 2011 à 10:31
Merci de votre aide. Je vais essayer tout ça.
0
Decon Messages postés 91 Date d'inscription mercredi 29 août 2007 Statut Membre Dernière intervention 8 octobre 2014 2
Modifié par Decon le 6/09/2011 à 16:50
J'ai essayé les différentes méthodes et il y en a aucune qui fonctionne.
Voici un extrait de mon code :
#!/bin/ksh 
Repertoire=$1 

DATE='date '+%y%m%d_%H%M%S'' 
DateV='date '+%y%m%d'' 
a=1 

for fic_prov in $Repertoire/toto* 
do 
nom_fic='basename $fic_prov .txt'_"$a"_"txt" 
nom_fichier=$Repertoire/$nom_fic; 

awk 'BEGIN {  FS = "|";  OFS = "|"; var= ' "$nom_fichier" '; }  
$1 ~ /^ACO$/ {   
 $2 = "<ORG>" substr($2,length($2)-1,length($2)) ;  
 $3= "<ORG>" substr($3,length($3)-1,length($3)) ;  
 if(length($6)> 2) {$5= "<ORG>" substr($5,length($5)-2,length($5)) ; $6= "<ORG>" ; $10= "<START>" ; } 
 if(length($6)< 2) {$5= "<ORG>" ;} 
 }  

$1 ~ /^AC1$/ { 
 $5= substr($5,0,length($5)-10) "<ORG>" substr($5,length($5)-2,length($5)) ;  
 $7= "<START>" ; 
 }  
  
{  print > var   }'  $fic_prov    

a='expr $a + 1' 

done
0