Forum Programmation.shell Tubes : sed vs. grep

Posté par .
Tags : aucun
0
26
juin
2008
Bonjour,

Un drôle de comportement, je dois modifier des fichiers, je fais pour celà des petits scripts.

Je veux supprimer une ligne contenant une expression :
grep -v "\#include \"fichier.h\"" $1 > $1
me rend un fichier vide. Je suis obligé de faire :
grep -v "\#include \"fichier.h\"" $1 > $1.1
mv $1.1 $1

Pourtant juste avant j'ai fait un ligne du genre :
sed -e 's~\#include \"fichier.h\"~~g' $1 > $1 ;
Qui fonctionne.

Pourquoi cette différence de comportement pour un résultat somme toute similaire ?
  • # Peut-être... mais à vérifier

    Posté par . Évalué à 2.

    Je pense que grep commence à écrire dans le fichier avant tout traitement. De ce fait il efface le contenu du fichier ! c'est tout
    • [^] # Re: Peut-être... mais à vérifier

      Posté par . Évalué à 1.

      Pour moi, que ce soit grep ou sed, les programme ne se contentent que d'écrire sur la sortie standard que je redirige ensuite vers le fichier. Ce que je ne comprends pas c'est la gestion qui est faite du "> $1" qui diffère entre les deux programme alors que ce n'est ni grep ni sed qui s'en occupe mais bash, enfin c'est ce que je croyais, mais manifestement ce n'est pas tout à fait le cas.
  • # sed -i

    Posté par . Évalué à 2.

    En fait tu as de la chance que ça fonctionne. Pour faire des modifications dans un fichier, utilise l'option -i de sed (qui au passage peut te permettre de faire un backup, cf `man sed `):

    sed -i -e 's~\#include \"fichier.h\"~~g' $1
    • [^] # Re: sed -i

      Posté par . Évalué à 1.

      Merci dans le doute j'utilise -i (quoique le bakup ne m'est pas utile en l'occurence). J'ai cherché s'il y avait une option équivalente pour grep mais non.
      • [^] # Re: sed -i

        Posté par (page perso) . Évalué à 3.

        Grep est surtout un outil de lecture sélective ; les seules options permettant de modifier un peu la sortie sont -o et --color.
        Sed, lui, est un éditeur ;) --- pour GNU Sed, info sed est plus complet que la page de manuel. Voir aussi http://sed.sf.net/ ...

        Les deux commandes ci-dessus ne sont pas strictement équivalentes, grep -v 'regexp' supprimant les lignes quand sed -e 's/regexp//' se contente de supprimer le motif reconnu, et donc donne au moins une ligne vide. L'équivalent serait plutôt sed -e '/regexp/d' (aux nuances d'écriture de la regexp près).

        L'ouverture en lecture et écriture du même fichier est à proscrire totalement.
        • [^] # Re: sed -i

          Posté par . Évalué à 4.

          Je reste perplexe devant le fait que dans l'exemple donné initialement par Nicolas, le fichier $1 ne soit pas écrasé avec sed.
          Peu importe que sed soit un éditeur ou pas, normalement c'est au niveau du shell que la redirection est évaluée.
          En lisant le "> $1", le shell doit (re)créer (donc vider dans le cas où il existe déjà) le fichier $1, puis il exécute la commande demandée tout en redirigeant la sortie vers le fichier qu'il a ouvert.

          Bref, ca ne devrait jamais marcher, peu importe la commande, puisque c'est au niveau du shell que le problème se produit.

          D'ailleurs en faisant le test de mon côté, j'obtiens bien un fichier vide tant avec grep qu'avec sed.

          Mes tests ont été réalisés avec bash, peut-être que Nicolas utilise un autre shell ?
          Sinon je ne comprends pas...
          • [^] # Re: sed -i

            Posté par . Évalué à 6.

            Bref, ca ne devrait jamais marcher, peu importe la commande, puisque c'est au niveau du shell que le problème se produit.
            Tu as raison, le shell traite les redirections AVANT de lancer la commande.
            Une commande shell de la forme suivante:
            cmd fichier > fichier
            Se traduit donc par la séquence suivante:
            1) Le shell crée un nouveau process (appel système fork)
            2) Il ouvre fichier et tronque son contenu (le fichier est donc maintenant VIDE)
            3) Il affecte le descripteur de fichier obtenu en 2) à la sortie standard
            4) il lance cmd fichierqui se contente d'écrire dans sa sortie standard (appel système exec)

            J'ai donc beaucoup de doutes surt le fait que sed -e 's~\#include \"fichier.h\"~~g' $1 > $1 fonctionne !

            On peut par contre jouer avec l'ordre d'évaluation des redirections par le shell pour ne pas avoir à utiliser un nom de fichier intermédiaire:
            (rm $1; grep -v "\#include \"fichier.h\"" > $1) < $1
            • [^] # Re: sed -i

              Posté par (page perso) . Évalué à 1.

              Il me semble bien l'avoir vu marcher de temps en temps...
              Dans ta description, tu ne précises pas que dans cmd fichier , c'est cmd qui s'occupe d'ouvrir son fichier argument --- mais ne sait pas où va finalement sa sortie; une variante est cmd <fichier , où c'est le shell qui met aussi en place l'entrée.

              Il se pourrait que la lecture du ou des premiers blocs se fasse parfois avant l'ouverture effective en écriture, suivant comment le noyau ordonnance ses opérations.

              Avec le rm $1, un seul nom est utilisé, mais la sortie doit se faire vers un autre inode, celui de la lecture continuant à pointer sur l'ancien, qui n'est pas réalloué tant que l'opération de lecture n'est pas terminée, même si le nom est déjà effacé... Cette méthode ne fonctionnera pas « bien » quand il y a plusieurs liens durs vers le même inode.
              • [^] # Re: sed -i

                Posté par . Évalué à 2.

                Dans ta description, tu ne précises pas que...
                Je voulais juste décrire les étapes principales pour expliquer que la redirection vide le fichier avant que cmd commence à s'en servir.

                Il se pourrait que la lecture du ou des premiers blocs se fasse parfois avant l'ouverture effective en écriture, suivant comment le noyau ordonnance ses opérations.
                La séquence que j'ai décrite est déterministe, le fichier DOIT être tronqué avant d'être lu.
                Si quelqu'un constate que ça ne se passe pas comme ça, il a trouvé un BUG noyau...

                Avec le rm $1, un seul nom est utilisé, mais la sortie doit se faire vers un autre inode
                Exactement, c'est pour ça que j'ai parlé de nom de fichier intermédiaire. Je n'ai pas dit que l'écriture se faisait en place.
                • [^] # Re: sed -i

                  Posté par . Évalué à 3.

                  La séquence que j'ai décrite est déterministe, le fichier DOIT être tronqué avant d'être lu.
                  Si quelqu'un constate que ça ne se passe pas comme ça, il a trouvé un BUG noyau...


                  Hmm, pas forcément, les shells sont de plus en plus perfectionnés et peut-être que certains reconnaissent que la commande utilise le même fichier en entrée et sortie ?
                  Typiquement, un truc qui m'a énervé récemment c'est quand j'ai voulu faire un gunzip sur un fichier sans extension .gz, eh bien la complétion automatique ne fonctionnait tout simplement pas ! (à bas le progrès !)
                  • [^] # Re: sed -i

                    Posté par . Évalué à 2.

                    Petite erreur de ma part 0:)

                    En fait dans mon soucis de donner un exemple minimaliste j'ai omis de le tester... et effectivement même le sed écrase de fichier.

                    Donc voici l'exemple réel qui me donne le résultat sans écraser le fichier :

                    for i in $1/Makefile* ; do
                    sed -e 's~INCDIR = $(OEDIR)/include~~g' $i \
                    | sed -e 's~LIBDIR = $(OEDIR)/lib~~g' \
                    > $i ;
                    done

                    Ceci OBLIGATOIREMENT dans un fichier script (j'ai essayé de façon intéractive, avec ou sans les '\' ça ne fonctionne pas).

                    PS : c'est bash que j'utilise.
                  • [^] # Re: sed -i

                    Posté par (page perso) . Évalué à 1.

                    Ce qui est nécessaire, c'est de commencer l'écriture au début du fichier de sortie --- les détails du sort des anciennes données dépendant du support et du système de fichiers. Dans le cas d'une ligne de commande cmd option fichier1 >fichier2 ,
                    • le shell ne sait pas que fichier1 est un nom de fichier ;
                    • la commande cmd ne sait pas que sa sortie standard sera détournée vers fichier2...
                    Seul le noyau est en mesure de voir que les deux noms (distincts ou pas) pointent sur le même inode. Par ailleurs, sur une machine chargée, les opérations d'entrée-sortie peuvent être plus ou moins différées... On pourrait rajouter des contrôles idiot-proof, mais comme dit le proverbe, c'est alors une course avec la nature qui produit de meilleurs idiots ;)
                      Pour répondre aussi à Kerro, Sed = stream editor : c'est un éditeur de flux, au code très comapct, qui s'occupe surtout de traiter son flux de données. L'option -i est juste un cadeau aux fainéants pour ne pas avoir à gérer le fichier temporaire (et, accessoirement, leur évite de se tirer une balle dans le pied avec <fic >fic ). On trouvera des discussions plus approfondies sur les redirections du shell sur news:fr.comp.os.unix, ou dans l'Advanced Bash-scripting Guide (traduit en français).
    • # Les sources sont reines dans ton cas

      Posté par (page perso) . Évalué à 2.

      Tu veux lire et écrire en même temps dans un fichier, et au même endroit. SI le logiciel n'est pas conçu pour, ça peut coincer.

      Sed est un éditeur de texte (de flux pour être exact). Il est peut-être conçu pour résister au mauvais traitement que tu lui appliques. Grep n'a rien à voir avec l'édition de texte. La différence est peut-être là.

      Ca ne fonctionnera peut-être plus avec sed si au lieu de supprimer tu fais un remplacement par une chaîne plus longue. Le fait que tes fichiers soient petits entrent peut-être également en ligne de compte si sed utilise un tampon plus grand que tes fichiers.

      Si tu veux la vraie réponse à ta question, regardes dans les sources de grep, mais ça ne t'avanceras pas beaucoup, surtout en comparaison du temps nécessaire :-)

    Suivre le flux des commentaires

    Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.