Forum Programmation.shell Créer deux fichiers avec un seul grep

Posté par  . Licence CC By‑SA.
Étiquettes :
1
20
déc.
2017

J’utilise grep pour filtrer selon un motif et re-diriger le résultat vers un fichier.

Je voudrais qu’un deuxième fichier soit généré, contenant lui les lignes ne correspondant pas au motif, le fichier complémentaire en quelque sorte.

Je peux m’en sortir en appelant deux fois grep, ou en utilisant diff, ou d’autres méthodes j’imagine, mais je voudrais savoir s’il y a une méthode plus élégante/concise/efficiente de faire cela. Je vais être honnête : je cherche LA bonne manière de faire pour briller en société. Ne me décevez pas ! ;)

Je ne suis pas bloqué sur grep, j’ai regardé ces exemples avec awk mais je n’ai pas trouvé de réponse qui me convienne, j’ai peut-être lu trop vite cela dit.

  • # stderr

    Posté par  . Évalué à 3.

    Si le challenge, c'est de le faire en une seule commande, awk sait écrire sur stderr. En répartissant tes lignes sur stderr et stdout, et en mettant les bonnes redirections, ça doit le faire.
    Par contre, c'est franchement de la bidouille, je ne pense pas que ça passe le test de l'élégance :-)

    A part ça, avec juste les commandes classiques du shell, pour l'instant, je ne vois pas comment utiliser moins que deux commandes …

    • [^] # Re: stderr

      Posté par  . Évalué à 5.

      Pas besoin de jouer avec stderr, awk peut écrire dans des fichiers quelconques, par exemple avec la syntaxe

      print "..." > "mon_fichier.txt"
      • [^] # Re: stderr

        Posté par  . Évalué à 3.

        tout à fait, ça marche très bien … quand on n'essaie pas d'abuser de stderr pour avoir deux flux en sortie au lieu d'un seul :-)

        • [^] # Re: stderr

          Posté par  . Évalué à 3.

          ah oui, je vois ce que tu veux dire. Effectivement, c'est pas bien malin d'utiliser stderr comme un gros sale alors qu'on faire propre directement.

    • [^] # Re: stderr

      Posté par  (site web personnel) . Évalué à 5.

      Si le challenge, c'est de le faire en une seule commande, awk sait écrire sur stderr

      En fait awk sait même écrire dans des fichiers, par exemple voici comment obtenir la sortie dans deux fichiers output.a et output.b:

      awk -v expr='^a' '{if(match($0,expr)){print $0 >> "output.a"}else{print $0 >> "output.b"}}'
      

      Au passage l'extrait illustre le passage de paramètres à awk ce qu'on peut utiliser pour choisir les noms de fichiers depuis la procédure appelante.

      (Cela marche avec awk sans tirer parti d'aucune extension, ce que je souligne pour la portabilité!)

      • [^] # Re: stderr

        Posté par  . Évalué à 2.

        oui, c'est en substance ce que dit wismerhill dans son commentaire, mais de façon plus lapidaire. D'ailleurs, je ne l'avais pas compris tout de suite non plus. Tu fais bien d'expliciter. Si tu ne l'as pas interprété comme ça alors que tu le savais, d'autres lecteurs peuvent aussi être passé à côté.

  • # sed

    Posté par  (site web personnel) . Évalué à 5.

    Ce n'est probablement pas la première chose qu'on aperçoit avec cet outil, mais sed propose :

    w filename
    
        Write the pattern space to filename.
    

    Ça peut être une bonne idée de faire un tour de la liste des commandes disponibles dans GNU sed ; par ailleurs, w est spécifié dans POSIX.

    Exemple de cours classique : partir de la sortie de seq et générer un fichier pairs et un fichier impairs.

    Enjoy.

    Debian Consultant @ DEBAMAX

    • [^] # Re: sed

      Posté par  . Évalué à 2.

      sed sait fait trop de trucs :-)

      Tiens, question, qui vaut tant pour awk que pour sed, est-ce que quelqu'un sait si ces outils gardent les fichiers ouverts pendant tout le traitement en gérant une table des fichiers rencontrés dans le script ? ou est-ce que chaque fichier est réouvert et refermé à chaque opération d'écriture ? (Si personne ne sait, je ferais quelques tests, mais là je n'ai pas le temps).

      • [^] # Re: sed

        Posté par  (site web personnel) . Évalué à 1.

        J'aurais imaginé une seule ouverture du fichier au tout début (ou des fichiers si plusieurs expressions avec -e sont spécifiées), et c'est ce que semble confirmer un petit strace.

        Debian Consultant @ DEBAMAX

  • # grep -v

    Posté par  . Évalué à -1.

    A moins que je n'ai pas du tout compris l'objectif, juste :
    grep -v motif fichier_source > fichier_sortie

    • [^] # Re: grep -v

      Posté par  . Évalué à 4.

      j'ai pris un café et maintenant en effet je vois que j'ai mal compris le problème :)

  • # awk

    Posté par  . Évalué à 3.

    awk (en tout cas la version gawk que j'ai sous la main) peut écrire dans des fichiers différents en fonction de tes conditions

    echo "Hello" | awk '{ if($1 ~ /Hello/) print $1 > "/tmp/hello.txt"; else print $1 > "/tmp/world.txt" }'
    • [^] # Re: awk

      Posté par  . Évalué à 4.

      Avec $1 tu va seulement écrire (et tester, dans ton if) le premier champ de la ligne, pour la ligne complète il faut utiliser $0 (ou même print sans paramètres).

    • [^] # Re: awk

      Posté par  . Évalué à 3.

      Je pense que c’est ce que je vais faire (il faut aussi que je regarde sed, comme a suggéré Cyril).

      J’avais pensé à un truc de ce genre mais en utilisant deux motifs distincts, j’avais pas pensé à utiliser "else"…

      Encore un grand merci à tous. Vous assurez.

  • # comm

    Posté par  (Mastodon) . Évalué à 1.

    Alors j'ai pas plus testé que ça, mais je crois bien que comm fait ça.

    • Tu prends ton fichier original
    • Tu grep pour avoir ton fichier qui contient
    • Avec comm (compare) tu auras la possibilité de sortir les lignes du fichier1 qui ne sont pas dans le fichier2

    C'est sympa de voir le nb de solutions différentes au même pb :)

    En théorie, la théorie et la pratique c'est pareil. En pratique c'est pas vrai.

    • [^] # Re: comm

      Posté par  (Mastodon) . Évalué à 2. Dernière modification le 21 décembre 2017 à 08:43.

      Après vérification, c'est exactement ce pour quoi comm est fait. Il te sort directement ce qui est commun, ce qui n'est que dans le premier fichier et ce qui n'est que dans la 2nd. A la fois diff et grep en quelques sortes.

      Par contre il a un pré-requis peut-être rédhibitoire : les lignes doivent être triées.

      En théorie, la théorie et la pratique c'est pareil. En pratique c'est pas vrai.

      • [^] # Re: comm

        Posté par  . Évalué à 3.

        Merci pour ton commentaire je connaissais pas cette commande donc +1 pour ça.

        Ça doit être possible de s’en sortir avec diff aussi. C’est un peu con que tu te fasses moinsser parce que tu n’as pas pris le soin de préciser que ça ne répondait pas à ma demande (j’avais bien spécifié de si possible avoir à appeler un seul binaire)… Mais bon, tu connais le site il me semble :)

        J’ai pas encore eu le temps de me remettre à travailler sur le script en question mais j’essaierai de faire un retour sur la solution choisie.

        • [^] # Re: comm

          Posté par  (Mastodon) . Évalué à 1.

          Surtout que comm te fait 3 colonnes (seulement dans fichier1, seulement dans fichier2, commun). Ensuite pour filtrer et en sortir des fichiers distinct… va falloir sortir awk :)

          En théorie, la théorie et la pratique c'est pareil. En pratique c'est pas vrai.

  • # Pas très élégant mais fonctionnel

    Posté par  . Évalué à 4.

    $ cat test.txt 
    a1
    a2
    b3
    b4
    $ <test.txt tee >(grep a > a.txt) | grep -v a > others.txt
    $ cat a.txt 
    a1
    a2
    $ cat others.txt 
    b3
    b4
    • [^] # Re: Pas très élégant mais fonctionnel

      Posté par  . Évalué à 3.

      je n'avais pas propose tee parce que je pensais que l'objectif etait de ne parser qu'une seule fois… sinon y'a aussi moyen avec socat je pense, mais on rdntre dans l'esoterisme bourne apres :)

    • [^] # Re: Pas très élégant mais fonctionnel

      Posté par  . Évalué à 3.

      Le problème de cette approche, or le fait, comme le dit freem, de devoir parser le fichier deux fois (à la rigueur s’il n’y a pas trop de lignes ça peut aller), c’est surtout au niveau des expressions régulières que je sens venir le piège… Il faut que je détermine ma regex ainsi que sa complémentaire. Là je dois pouvoir encore le faire sans me gourrer, mais si la regex doit être rendue plus complexe, je vais pouvoir me retrouver avec des lignes dans les deux fichiers ou des lignes nulle part…

      Je commence à m’en vouloir un peu de ne pas avoir pensé moi-même au bête mot-clé else… J’ai probablement pas une assez bonne maîtrise de awk (ou sed) pour les considérer à juste titre comme des langages de programmation complets.

      • [^] # Re: Pas très élégant mais fonctionnel

        Posté par  . Évalué à 3.

        #_# Et là c’est moi qui raconte carrément n’importe quoi… si grep -v regexA renvoyait pas le complément de grep regexA ce serait un sacré bug :)

Suivre le flux des commentaires

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