Remplacer des infos dans un fichier [Shell] - Shell/Batch - Programmation
Marsh Posté le 25-03-2009 à 16:52:15
Ok mais sed me fait la même chose (en 2 étapes mais en moins gourmand) avec sed -e 's/A/<remplacement>/g' <file>, mais si j'ai plusieurs 'A' dans mon fichier (mais d'index différent), ta commande remplace toutes les occurrences!
Il faudrait que je puisse passer un numéro de ligne ou bien l'index qui figure sur la ligne où la modification doit être effectuée.
Marsh Posté le 25-03-2009 à 16:53:47
Tu ne veux supprimer que la première occurence du fichier ? Je ne comprends pas ce que tu cherches à faire (et l'avantage de perl, en plus d'avoir des regexp PCRE au lieu de POSIX, c'est qu'il modifie le fichier directement, ça te fait une étape de moins par rapport à sed... et côté gourmandise, comment dire... je doute que tu y vois une quelconque différence)
Marsh Posté le 25-03-2009 à 17:04:22
Oui possible pour le côté ressource.
En fait dans mon fichier je peux avoir ça :
1|A
2|B
3|A
4|C
5|E
6|A
Et je veux remplacer la lettre A de l'index 3 avec le moins d'étapes possible et en évitant de multiplier les fichiers temporaires.
Marsh Posté le 25-03-2009 à 17:24:15
nawk -F'|' 'BEGIN{ OFS = "|"; } { if ($1 == "3" && $2 ~ "A" ) { $2 = "NEW"; } print $0; }' <fichier> > <nouveau fichier>
mv <nouveau fichier> <fichier>
ou
perl -pi -e 's/3\|A/3|NEW/;' <fichier>
ou
sed -e 's/3\|A/3|NEW/;' <fichier> > <nouveau fichier>
mv <nouveau fichier> <fichier>
Marsh Posté le 25-03-2009 à 18:00:41
Merci pour ta réponse Elmoricq !
La commande avec nawk me plait bien et fonctionne niquel
Marsh Posté le 26-03-2009 à 22:08:02
Elmoricq a écrit : nawk -F'|' 'BEGIN{ OFS = "|"; } { if ($1 == "3" && $2 ~ "A" ) { $2 = "NEW"; } print $0; }' <fichier> > <nouveau fichier> |
On peut même le faire en laissant le système gérer le fichier temporaire
Code :
|
Marsh Posté le 30-03-2009 à 09:13:54
Tu ne peux pas écrire dans le fichier que tu es en train de lire. Je peux me tromper mais je ne pense pas que le 0<&3 change quoi que ce soit au problème. Au final tu écris toujours dans le fichier que tu es en train de lire.
Edit : en fait tu as raison, ça marche. Le nouveau fichier a le même nom que l'ancien fichier mais un inode différent. Ca revient en quelque sorte à transformer le fichier initial en fichier temporaire, sauf qu'on n'a pas à s'embêter à trouver un nom qui va bien.
Marsh Posté le 01-04-2009 à 11:36:57
matafan a écrit : Tu ne peux pas écrire dans le fichier que tu es en train de lire. Je peux me tromper mais je ne pense pas que le 0<&3 change quoi que ce soit au problème. Au final tu écris toujours dans le fichier que tu es en train de lire. |
Arf... faut tester ses théories avant de les écrire
matafan a écrit : Edit : en fait tu as raison, ça marche. Le nouveau fichier a le même nom que l'ancien fichier mais un inode différent. Ca revient en quelque sorte à transformer le fichier initial en fichier temporaire, sauf qu'on n'a pas à s'embêter à trouver un nom qui va bien. |
Tu as décrit effectivement le fonctionnement auquel je pense. Effectivement la création du canal 3 (ou d'un autre) doit se concrétiser quelque part par un inode qui va bien.
Au départ j'avais pensé que tout se faisait en mémoire mais quand j'ai testé et que j'ai vu que ça fonctionnait aussi avec des fichiers qui dépassaient le giga, j'ai abandonné cette idée...
J'ai un peu modifié mon post précédent car j'avais dit, à tord, que ça se faisait sans fichier temporaire alors qu'en réalité, il y a bien un fichier quelque part mais simplement on ne s'en préoccupe pas.
Marsh Posté le 01-04-2009 à 17:31:14
Sur un korn shell ça me pond une erreur :
Code :
|
Où 3 correspond au canal que je veux utiliser (comme dans l'exemple de Sve@r)
Une idée ?
Marsh Posté le 01-04-2009 à 18:01:53
ReplyMarsh Posté le 02-04-2009 à 10:34:51
Oui c'est déjà ce que je fais
Mais ça m'aurait permis d'économiser une étape
Marsh Posté le 02-04-2009 à 11:17:17
Il est moisi ton ksh, avec le miens ça marche bien (sous AIX).
Marsh Posté le 02-04-2009 à 14:34:10
Tonio94 a écrit : Sur un korn shell ça me pond une erreur :
|
Je l'aurais pas dit ainsi mais effectivement, j'étais sceptique sur le fait que ça ne fonctionne pas en ksh. J'ai tenté de tester ce matin mais nous n'avons plus de machine tournant sous ksh. Cependant ksh se voulant compatible shell, c'était assez bizarre que ça ne fonctionnat pas.
Petit détail: il ne faut pas d'espace entre le chiffre et le "<". Donc
exec 3 < fichier => ne fonctionne pas
exec 3< fichier => marche nickel
Elmoricq a écrit : Le faire vieille mode dans un fichier temporaire avec un mv derrière ?
|
Sans approfondir sur le fait que c'est dommage de s'embêter à faire soi-même ce que le système fait déjà, gérer un fichier temporaire doit s'accompagner de précautions surtout contre le multi processus. Si le fichier temporaire est un bête "toto.txt" MAIS que le script est lancé plusieurs fois en parallèle, il risque d'y avoir un sacré chaos au niveau des infos contenues dans "toto.txt".
Obligation donc de s'occuper du
- nom du fichier temporaire qui doit être associé au processus=> utilisation de la variable "$$"
- nettoyage du fichier temporaire en fin de tâche
- nettoyage du fichier temporaire en cas d'interruption intempestive du script
Et là, ce qui semblait au début un truc tout con devient de suite plus délicat à gérer....
Marsh Posté le 03-04-2009 à 15:56:13
Et bien justement j'ai plusieurs exécutions de mon script en parallèle, et chaque exécution écrit dans le même fichier, pas forcément au même moment, mais ça peut arriver.
J'avais fait un oubli mais ça fonctionne bien sous mon ksh merci Sve@r
Par contre quel est le traitement effectué par exec ? Dans mon script je fais plusieurs nawk pour remplacer des références dans mon fichier, dois-je faire le exec et le rm avant chaque ou simplement au début de mon script (ce que j'imagine) ? Dsl pour la question bateau... :|
Marsh Posté le 03-04-2009 à 22:04:21
Tonio94 a écrit : Par contre quel est le traitement effectué par exec ? |
Ca te crée un canal numéroté en input (exec 3<fichier) ou output (exec 4>result) associé au fichier sus nommé.
Ensuite, lire le canal input (read line 0<&3) ou écrire dans le canal output (echo truc 1>&4) aura une répercussion sur le fichier associé au canal
Tonio94 a écrit : Dans mon script je fais plusieurs nawk pour remplacer des références dans mon fichier, dois-je faire le exec et le rm avant chaque ou simplement au début de mon script (ce que j'imagine) ? Dsl pour la question bateau... :| |
Le rm est là pour nettoyer le fichier avant d'y écrire dedans mais en fait, c'en est même inutile. Le exec tu le fais juste avant d'avoir besoin de lire le fichier !!!
Marsh Posté le 04-04-2009 à 20:29:46
Non non, le rm est crucial, sans ça l'écriture se fait dans le fichier que tu es en train de lire (et tu finit probablement avec un fichier vide).
Ca veut dire aussi que tu ne peux pas utiliser cette méthode si tu as besoin de lire plusieurs fois le fichier initial, parce qu'après la première lecture le fd 3 est fermé, et à ce moment là les données s'envolent.
Marsh Posté le 07-04-2009 à 16:18:38
Effectivement j'ai testé et vérifié ! C'est propre comme méthode mais pas forcément pratique quand on a besoin d'écrire souvent dans le fichier et encore moins quand le même script le fait en parallèle.
Petite question sur la ligne suivante :
Code :
|
J'aimerais savoir comment remplacer le "$2" qui représente le deuxième élement de mon fichier file par une variable qui contiendra le numéro de colonne.
Dans ce genre là :
Code :
|
Car j'écris souvent cette même ligne et j'aimerais la mettre dans une fonction.
Merci.
Marsh Posté le 07-04-2009 à 16:53:02
matafan a écrit : Non non, le rm est crucial, sans ça l'écriture se fait dans le fichier que tu es en train de lire (et tu finit probablement avec un fichier vide). |
T'as raison (je viens de tester). Ceci dit, si le exec crée un fichier temporaire contenant la copie du fichier source, je m'explique mal que ça ne fonctionne que si la source a disparu. Ou alors le fichier est créé lors du rm. Bref il y a un manque dans mon raisonnement. Toutefois ça marche avec le rm et finalement c'est le principal.
Tonio94 a écrit : Effectivement j'ai testé et vérifié ! C'est propre comme méthode mais pas forcément pratique quand on a besoin d'écrire souvent dans le fichier et encore moins quand le même script le fait en parallèle. |
Arf évidemment si tu cherches les limites de la méthode, tu les trouves vite. Ceci dit, je vois mal l'utilité d'un script qui va écrire en parallèle (sous-entendu plusieurs fois) le fichier cible (qui sera donc le même à chaque fois)
Tonio94 a écrit : Petite question sur la ligne suivante :
|
Ben te suffit de mettre "col=2" dans ton BEGIN puis demander "$col" qui correspondra alors au 2° mot. Et si tu veux que la valeur "2" puisse arriver depuis l'extérieur (le shell) faut utiliser "-v" lors de l'appel à awk (fonctionne avec les awk évolués comme gawk mais sans garantie pour nawk)
Code :
|
Marsh Posté le 07-04-2009 à 17:27:19
en fait ce qui ce passe c'est ça :
1) Le "exec 3<fichier" ouvre le fichier et l'associe au fd 3
2) Le "rm fichier" supprime le fichier. Comme le fichier a encore des fd ouverts (notre fd 3), il est juste supprimé du répertoire, mais les données restent sur disque. D'ailleurs si on fait un du sur le répertoire qui contient le fichier, on voit que l'espace disque n'a pas été libéré, alors qu'on ne peut pourtant plus accéder au fichier par son nom.
3) Le "truc 0<&3 >fichier" crée un fichier "fichier". Ce fichier est un fichier comme un autre, qui n'a rien à voir avec le fichier initial, même s'il porte le même nom. D'ailleur sont numéro d'inode est différent du fichier initial. La commande lit les données du fd 3, qui sont les données "fantome" de notre fichier initial, qui n'existe plus mais qui a encore ses donées sur disque.
4) Quand truc a fini de lire le fd 3, le fd est fermé. A ce moment là il n'y a plus de fd associé au fichier initial, et ses données sont supprimées.
Donc il n'y a pas vraiment de fichier temporaire. Il y a juste les données du fichier initial, qui ne sont plus associées à un nom de fichier. C'est le mécanisme classique sous unix : quand un fichier est unlinké, il disparait du répertoire et on ne peut plus l'ouvir, mais ses données restent sur disque jusqu'à ce que le dernier fd associé au fichier ai été fermé.
Marsh Posté le 07-04-2009 à 17:40:51
Merci t'es un chef Sve@r !
On peut déclarer ce qu'on veut comme variable dans le BEGIN ?
Car j'essaie avec une modification de 2 colonnes mais il ne me fait que la 2e :
Code :
|
Et autre problème, quand je passe des valeurs numériques aucun soucis mais quand je passe une chaine de caractère à $value il ne m'écrit rien dans le fichier et écrase le contenu de la colonne (à l'emplacement de $var je précise).
Merci.
PS : Pour l'histoire du script lancé en parallèle, en fait il prend en paramètre des chemins différents donc chaque exécution n'analyse pas les mêmes fichiers, mais ils doivent par contre tous écrire dans un même fichier de log commun.
Marsh Posté le 08-04-2009 à 13:16:20
matafan a écrit : |
Arf, non, pas chez-moi. Ma size a changé.
matafan a écrit : 3) Le "truc 0<&3 >fichier" crée un fichier "fichier". Ce fichier est un fichier comme un autre, qui n'a rien à voir avec le fichier initial, même s'il porte le même nom. D'ailleur sont numéro d'inode est différent du fichier initial. |
Arf exact, le n° d'inode en fin de travail est toujours différent (alors que si on n'utilise pas de canal numéroté et qu'on s'amuse à effacer puis recréer le fichier, le n° d'inode ne change pas toujours)
matafan a écrit : Donc il n'y a pas vraiment de fichier temporaire. Il y a juste les données du fichier initial, qui ne sont plus associées à un nom de fichier. C'est le mécanisme classique sous unix : quand un fichier est unlinké, il disparait du répertoire et on ne peut plus l'ouvrir, mais ses données restent sur disque jusqu'à ce que le dernier fd associé au fichier ai été fermé. |
Joli !!! Et inversement si on ne fait pas le "rm", le nom de fichier n'est pas unlinké et est donc toujours associé à son contenu (ainsi que le canal 3) et donc écraser le fichier revient à perdre le contenu (et le canal 3 ne sert à rien). Excellent
Tonio94 a écrit : Merci t'es un chef Sve@r !
|
Hum. Je ne suis pas un pro de awk mais je suis un peu dubitatif sur ta façon de faire. Il faut bien comprendre que les éléments $1 $2 $3 ne sont pas des variables (comme le $1 du shell) mais des mots de ta ligne (donc des valeurs de travail de awk) et donc j'ai un doute sur le fait que tu aies le droit de dire arbitrairement "$1=truc" alors qu'a l'origine, $1 a été prévu pour contenir autre chose. Ptet que tes problèmes viennent de là...
Tu ne peux pas faire plutôt l'inverse, c.a.d. récupérer $1, $2, $3 dans des variables bien à toi que tu pourras changer à ta guise si l'algo l'impose ???
Marsh Posté le 08-04-2009 à 14:59:55
En fait j'appelle souvent nawk de la même façon mais avec les valeurs et la position de colonne qui changent. Alors plutôt que de le répéter plusieurs fois dans mon code j'aimerais le mettre dans une fonction, dans ce style là :
Code :
|
Écris de cette manière ça fonctionne (grâce à ton précèdent post), quand j'appelle la fonction replace le premier paramètre passé est "2" qui correspond au numéro de colonne à remplacer puis "index1" qui correspond à la réference/index (pour savoir à quelle ligne il faut remplacer) et enfin "2009" qui est la valeur à écrire à cet emplacement.
Ensuite j'ai deux problèmes :
- Lorsque à la place de 2009 je veux passer une chaine de caractère, par exemple "test", dans ce cas nawk m'écrase la valeur à remplacer dans le fichier 'file' mais ne m'écrit pas la chaine à écrire.
- Lorsque je veux remplacer 2 valeurs d'un coup. A ce moment là je passe en paramètre 2 numéros de colonnes (au lieu d'un) et 2 valeurs à remplacer. Comme je l'ai écris dans mon exemple de code précèdent. Mais il ne me fait qu'un remplacement sur les deux :
Code :
|
(seulement col2 est mis à jour dans le file)
Je sais bien que $1, $2, $3, etc correspondent dans le shell aux arguments du script ou d'une fonction et que pour awk ils correspondent aux numéros des "colonnes". Mais dans mon cas la colonne où il y a la valeur à changer est souvent modifiée, dans ce cas je ne peux pas écrire en statique $1, $2, $3 dans ma ligne awk, il faut que je le passe en paramètre de ma fonction.
Je ne sais pas si c'est plus clair comme cela, en espérant que tu puisses me filer un coup de pouce Merci
Marsh Posté le 09-04-2009 à 13:16:14
Tonio94 a écrit : En fait j'appelle souvent nawk de la même façon mais avec les valeurs et la position de colonne qui changent. Alors plutôt que de le répéter plusieurs fois dans mon code j'aimerais le mettre dans une fonction, dans ce style là :
|
Je suis très étonné que cela fonctionne correctement vu que tu ne passes pas tes variables à awk. Tu appelles awk en laissant au shell le soin de remplacer "$value" par la valeur correspondante. Si tu rajoutes 'set -x" juste avant le awk, tu verras un truc ressemblant à ça
Code :
|
Tonio94 a écrit : Ensuite j'ai deux problèmes : |
Hé oui. Il faudrait qu'il y ait écrit $col='test' or le nawk reçoit l'instruction $col=test (sans quotte). Ceci étant issu de la remarque ci-dessus
Voici ce que moi j'ai écrit
Code :
|
Et ça fonctionne totalement sur un fichier de ce style
Code :
|
Je me retrouve en sortie avec un fichier de ce style
Code :
|
Tonio94 a écrit : Je sais bien que $1, $2, $3, etc correspondent dans le shell aux arguments du script ou d'une fonction et que pour awk ils correspondent aux numéros des "colonnes". Mais dans mon cas la colonne où il y a la valeur à changer est souvent modifiée, dans ce cas je ne peux pas écrire en statique $1, $2, $3 dans ma ligne awk, il faut que je le passe en paramètre de ma fonction. |
Oui mais t'as oublié de les passer en tant que variables à awk !!!
Tonio94 a écrit : - Lorsque je veux remplacer 2 valeurs d'un coup. A ce moment là je passe en paramètre 2 numéros de colonnes (au lieu d'un) et 2 valeurs à remplacer. Comme je l'ai écris dans mon exemple de code précèdent. Mais il ne me fait qu'un remplacement sur les deux :
|
Faudrait pouvoir passer un tableau à awk et ça, suis pas certain qu'on puisse faire.
Moi pour faire ça je ruse en écrivant une boucle dans la fonction pour traiter chaque remplacement
Code :
|
Et ça fonctionne totalement sur un fichier de ce style
Code :
|
Je me retrouve en sortie avec un fichier de ce style
Code :
|
Bien entendu, tout ça sous bash+gawk Linux car j'ai pas ksh+nawk pour tester...
Marsh Posté le 22-04-2009 à 11:39:58
Merci pour ta contribution c'est top ! je vais mettre tout ça en application ! (dsl pour le temps de réaction, retour de vacances!)
Tant qu'on est sur awk, un truc tout simple qui ne fonctionne pas et je ne vois pas pourquoi :
Code :
|
Il n'y a rien dans $INFO.... :|
Alors que si je tappe la ligne nawk directement dans le shell en remplaçant les variables par leurs valeurs, ça fonctionne o_o
Contenu du fichier scanné :
toto-1|titi-1|tata-1
toto-2|titi-2|tata-2
etc.
Si tu as une idée je sèche, j'ai fait autrement avec sed en attendant mais c'est plus long :\
Marsh Posté le 27-04-2009 à 14:53:34
Tonio94 a écrit : Merci pour ta contribution c'est top ! je vais mettre tout ça en application ! (dsl pour le temps de réaction, retour de vacances!)
|
T'as toujours pas appris à passer une variable à awk ? J'en ai pourtant parlé 2 fois !!!
Code :
|
C'est pas bien de mettre ses variables en majuscule. Les majuscules sont réservées aux variables système (PATH, HOME, etc)
Marsh Posté le 28-05-2009 à 14:59:55
ReplyMarsh Posté le 30-05-2009 à 19:44:34
Tonio94 a écrit : Autant pour moi :\ |
Hey, 30 jours plus tard !!!???!!!
Enfin vieux motard...
Marsh Posté le 03-06-2009 à 13:58:14
Sve@r a écrit : |
Disons qu'entre temps j'avais fait un truc avec sed - un peu crade - qui fonctionnait et je n'étais pas repassé par là.
Mais je pense que ce thread pourra en aider plus d'un !
Marsh Posté le 25-03-2009 à 15:14:21
Bonjour,
J'ai un fichier qui se présente comme ceci :
1|A
2|B
3|C
etc...
Comment remplacer une lettre (A, B ou C par exemple) dans le fichier par autre chose en faisant le moins d'étapes possibles ?
Actuellement je fais comme ceci :
- Je récupère le numéro de ligne (en fonction de la lettre)
- J'isole la ligne dans un fichier tmp
- Je supprime la ligne du fichier de départ
- Je remplace la lettre par une expression (dans le fichier tmp)
- Je copie la ligne obtenue à l'étape précédente dans le fichier de départ
- Et enfin je renomme le dernier fichier et supprime tous les tmp
J'appelle sed pour chaque étape et pour chaque étape j'ai au moins 1 fichier temporaire de créé. C'est pas forcément lourd mais c'est assez fastidieux...
C'est pourquoi je cherche une méthode plus simple pour remplacer directement dans le fichier ou éventuellement passer par UN SEUL fichier temp.
A vous...
PS : C'est du bash (unix)