Comment diviser une sortie en deux fichiers avec grep?


14

J'ai un script mycommand.shque je ne peux pas exécuter deux fois. Je veux diviser la sortie en deux fichiers différents, un fichier contenant les lignes qui correspondent à une expression régulière et un fichier contenant les lignes qui ne correspondent pas à une expression régulière. Ce que je souhaite, c'est essentiellement quelque chose comme ceci:

./mycommand.sh | grep -E 'some|very*|cool[regex].here;)' --match file1.txt --not-match file2.txt

Je sais que je peux simplement rediriger la sortie vers un fichier puis vers deux greps différents avec et sans l'option -v et rediriger leur sortie vers deux fichiers différents. Mais je me demandais s'il était possible de le faire avec un grep.

Alors, est-il possible de réaliser ce que je veux en une seule ligne?

Réponses:


20

Il y a plusieurs façons d'accomplir ceci.

Utiliser awk

Ce qui suit envoie toutes les lignes correspondant coolregexà file1. Toutes les autres lignes vont au fichier2:

./mycommand.sh | awk '/[coolregex]/{print>"file1";next} 1' >file2

Comment ça fonctionne:

  1. /[coolregex]/{print>"file1";next}

    Toutes les lignes correspondant à l'expression régulière coolregexsont imprimées sur file1. Ensuite, nous sautons toutes les commandes restantes et sautons pour recommencer sur la nextligne.

  2. 1

    Toutes les autres lignes sont envoyées à stdout. 1est le raccourci cryptique d'awk pour imprimer la ligne.

La division en plusieurs flux est également possible:

./mycommand.sh | awk '/regex1/{print>"file1"} /regex2/{print>"file2"} /regex3/{print>"file3"}'

Utilisation de la substitution de processus

Ce n'est pas aussi élégant que la solution awk mais, pour être complet, nous pouvons également utiliser plusieurs greps combinés avec une substitution de processus:

./mycommand.sh | tee >(grep 'coolregex' >File1) | grep -v 'coolregex' >File2

Nous pouvons également nous diviser en plusieurs flux:

./mycommand.sh | tee >(grep 'coolregex' >File1) >(grep 'otherregex' >File3) >(grep 'anotherregex' >File4) | grep -v 'coolregex' >File2

Oh cool! Est-il également possible de le diviser en plusieurs fichiers sans simplement faire un autre awk au lieu de file2? Je veux dire d'une manière que les expressions régulières peuvent se chevaucher par exemple.
yukashima huksay

1
@aran Oui, awk est très flexible. Précisément comment on le fera dépendrait de la façon dont les regex se chevauchent.
John1024

J'aimerais voir une solution même si elle ne prend pas en charge les expressions rationnelles qui se chevauchent. par chevauchement, je veux dire que l'intersection du sous-ensemble n'est pas vide sans nervosité.
yukashima huksay

1
@aran J'ai ajouté aux exemples de réponse avec plusieurs flux pour les deux méthodes.
John1024

8
sed -n -e '/pattern_1/w file_1' -e '/pattern_2/w file_2' input.txt

w filename - écrire l'espace de motif actuel dans le nom de fichier.

Si vous souhaitez que toutes les lignes correspondantes soient dirigées vers toutes les lignes file_1non correspondantes file_2, vous pouvez faire:

sed -n -e '/pattern/w file_1' -e '/pattern/!w file_2' input.txt

ou

sed -n '/pattern/!{p;d}; w file_1' input.txt > file_2

Explication

  1. /pattern/!{p;d};
    • /pattern/!- négation - si une ligne ne contient pas pattern.
    • p - imprimer l'espace de motif actuel.
    • d- supprimer l'espace de motif. Commencez le cycle suivant.
    • ainsi, si une ligne ne contient pas de motif, elle imprime cette ligne sur la sortie standard et sélectionne la ligne suivante. La sortie standard est redirigée vers le file_2dans notre cas. La partie suivante du sedscript ( w file_1) n'est pas atteinte alors que la ligne ne correspond pas au modèle.
  2. w file_1- si une ligne contient un motif, la /pattern/!{p;d};partie est sautée (car elle n'est exécutée que lorsque le motif ne correspond pas) et, par conséquent, cette ligne va au file_1.

Pouvez-vous ajouter des explications supplémentaires à la dernière solution?
yukashima huksay

@aran Explication ajoutée. La commande est également corrigée - file_1et a file_2été permutée dans le bon ordre.
MiniMax

0

J'ai aimé la sedsolution car elle ne repose pas sur des bashismes et traite les fichiers de sortie sur le même pied. AFAIK, il n'y a pas d'outil Unix autonome qui fait ce que vous voulez, vous devez donc le programmer vous-même. Si nous abandonnions l'approche du couteau suisse, nous pourrions utiliser n'importe quel langage de script (Perl, Python, NodeJS).

Voici comment cela se ferait dans NodeJS

  #!/usr/bin/env node

  const fs = require('fs');
  const {stderr, stdout, argv} = process;

  const pattern = new RegExp(argv[2] || '');
  const yes = argv[3] ? fs.createWriteStream(argv[3]) : stdout;
  const no = argv[4] ? fs.createWriteStream(argv[4]) : stderr;

  const out = [no, yes];

  const partition = predicate => e => {
    const didMatch = Number(!!predicate(e));
    out[didMatch].write(e + '\n');
  };

  fs.readFileSync(process.stdin.fd)
    .toString()
    .split('\n')
    .forEach(partition(line => line.match(pattern)));

Exemple d'utilisation

# Using designated files
./mycommand.sh | partition.js pattern file1.txt file2.txt

# Using standard output streams
./partition.js pattern > file1.txt 2> file2.txt

0

Si cela ne vous dérange pas l'utilisation de Python et d'une syntaxe d'expression régulière différente:

#!/usr/bin/env python3
import sys, re

regex, os1, os2 = sys.argv[1:]
regex = re.compile(regex)
with open(os1, 'w') as os1, open(os2, 'w') as os2:
    os = (os1, os2)
    for line in sys.stdin:
        end = len(line) - line.endswith('\n')
        os[regex.search(line, 0, end) is not None].write(line)

Usage

./match-split.py PATTERN FILE-MATCH FILE-NOMATCH

Exemple

printf '%s\n' foo bar baz | python3 match-split.py '^b' b.txt not-b.txt
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.