web-dev-qa-db-fra.com

Extraire une chaîne d'une ligne entre des positions données par un motif dans une autre ligne

Je cherche à sortir les caractères entre deux positions A et B qui sont spécifiées par la ligne précédente. Par paire, les deux lignes ont la même longueur, mais elles peuvent varier d'une paire à l'autre. Existe-t-il un moyen efficace (tailles de fichiers énormes) de le faire avec grepname__, sedou awkname__?

Exemple de fichier:

xxxxxxAxxxxxxBxxxxxx
1234567890MNOPQRSTUV
xxAxxxxxxxxxxxxxxBxxxxxx
1234567890MNOPQRSTUVWXYZ

...

Je voudrais obtenir le résultat:

7890MNOP
34567890MNOPQRST

...

6
Freewheel

Utilisation de awkname__:

$ awk '!seen{match($0, /A.*B/);seen=1;next} {print substr($0,RSTART,RLENGTH);seen=0}' infile
7890MNOP
34567890MNOPQRST

Explication: lu dans man awkname __ :

RSTART
          The index of the first character matched by match(); 0 if no
          match.  (This implies that character indices start at one.)

RLENGTH
          The length of the string matched by match(); -1 if no match.

match(s, r [, a])  
          Return the position in s where the regular expression r occurs, 
          or 0 if r is not present, and set the values of RSTART and RLENGTH. (...)

substr(s, i [, n])
          Return the at most n-character substring of s starting at I.
          If n is omitted, use the rest of s.
8
αғsнιη

Bien que vous puissiez le faire avec AWK, je suggère Perl. Voici un script:

#!/usr/bin/env Perl

use strict;
use warnings;

while (my $pattern = <>) {
    my $text = <>;
    my $start = index $pattern, 'A';
    my $stop = index $pattern, 'B', $start;
    print substr($text, $start, $stop - $start + 1), "\n";
}

Vous pouvez nommer ce fichier de script comme bon vous semble. Si vous deviez le nommer intervalet le placer dans le répertoire en cours, vous pouvez le marquer comme exécutable avec chmod +x interval. Ensuite, vous pouvez exécuter:

./interval paths...

Remplacez paths... par le ou les chemins d'accès aux fichiers à analyser. Par exemple:

$ ./interval interval-example.txt
7890MNOP
34567890MNOPQRST

La façon dont le script fonctionne est la suivante: jusqu'à la fin de la saisie (c'est-à-dire plus de lignes), il:

  • Lit une ligne, $pattern, qui est votre chaîne avec Aet Bname__, et une autre ligne, $text, qui est la chaîne qui sera découpée en tranches.
  • Trouve l'index du premier Adans $pattern et le premier Bde tous ceux ayant pu précéder le premier Aet les stocke dans les variables $start et $stop, respectivement.
  • Découpe uniquement la partie de $text dont les index vont de $start à $stop. La fonction substrde Perl prend les arguments offset et length, ce qui est la raison de la soustraction, et vous incluez la lettre immédiatement sous Bname__, ce qui explique l'ajout de 1.
  • N'imprime que cette partie, suivie d'un saut de ligne.

Si, pour une raison quelconque, vous préférez une commande courte sur une ligne, vous obtenez le même résultat mais vous le collez facilement - mais il est également plus difficile à comprendre et à gérer - vous pouvez alors utiliser ceci:

Perl -wple '$i=index $_,"A"; $_=substr <>,$i,index($_,"B",$i)-$i+1' paths...

(Comme auparavant, vous devez remplacer paths... par les noms de chemin d'accès actuels.)

7
Eliah Kagan

Puisque vous avez mentionné sed , vous pouvez également le faire avec un script sed:

/^x*Ax*Bx*$/{              # If an index line is matched, then
  N                        # append the next (content) line into the pattern buffer
  :a                       # label a
  s/^x(.*\n).(.*)/\1\2/    # remove "x" from the index line start and a char from the content line start
  ta                       # if a subtitution happened in the previous line then jump back to a
  :b                       # label a
  s/(.*)x(\n.*).$/\1\2/    # remove "x" from the index line end and a char from the content line end
  tb                       # if a subtitution happened in the previous line then jump back to b
  s/.*\n//                 # remove the index line
}

Si vous mettez tout cela sur une seule ligne de commande, cela ressemble à ceci:

$ sed -r '/^x*Ax*Bx*$/{N;:a;s/^x(.*\n).(.*)/\1\2/;ta;:b;s/(.*)x(\n.*).$/\1\2/;tb;s/.*\n//;}' example-file.txt
7890MNOP
34567890MNOPQRST
$ 

-r est nécessaire pour que sed puisse comprendre les parenthèses de regroupement des expressions rationnelles sans échappées supplémentaires.


FWIW, je ne pense pas que cela pourrait être fait uniquement avec grep, bien que je serais heureux d’avoir tort.

7
Digital Trauma

Nous ne savons pas avec certitude si ..

  • il peut y avoir des lignes entre ou avant les couples, qui ne font pas partie d'un couple; entête? explication? commentaire?
  • la première ligne commence par xpar définition
  • la deuxième ligne du couple commence éventuellement par un xname__

Pour attraper toutes ces situations, en utilisant set(), nous pouvons rechercher les lignes qui seulement existent (toutes) xname__, Aname__, Bname__. Ceux-ci, nous pouvons être positifs, sont les premières lignes de nos couples.

Ainsi nous obtenons en python:

#!/usr/bin/env python3

f = "/path/to/file"

printresult = False

for l in open(f):
    if printresult == True:
        print(l[i[0]:i[1]])
        printresult = False
    Elif set(l.strip()) == {"A", "x", "B"}:
        i = [l.index("A"), l.index("B") + 1]
        printresult = True

Ainsi, la sortie de:

Some results of whatever test
-----------------------------
xxxxxxAxxxxxxBxxxxxx
1234567890MNOPQRSTUV
blub or blublub
xxAxxxxxxxxxxxxxxBxxxxxx
1234567890MNOPQRSTUVWXYZ
peanutbutter
AxxxxxxxxxxxxxxBxxxxxx
x234567890MNOPQRSTUVWXYZ

devient:

7890MNOP
34567890MNOPQRST
x234567890MNOPQR
3
Jacob Vlijm

Voici un moyen de le faire dans GNU awk:

$ gawk 'NR%2 {split($0,a,/[AB]/); FIELDWIDTHS = length(a[1])" "length(a[2])+2; next} {print $2}' file
7890MNOP
34567890MNOPQRST
3
steeldriver

Avec la très simple syntaxe Python 3, nous pouvons créer le script suivant:

#!/usr/bin/env python3
import sys

for fname in sys.argv[1:]:
    with open(fname) as fd:
        for line in fd:
            if line.startswith('x'):
                start_index = line.find('A')
                end_index = line.rfind('B')
            else:
                print(line[start_index:end_index+1])

Qui fonctionne comme tel:

$ ./croplines.py  input.txt 
7890MNOP
34567890MNOPQRST

OP a fourni MCVE , mais n’a pas fourni d’autres exigences. Nous nous basons donc sur un schéma alternatif: première ligne commençant par "x", puis ligne avec les données (dans ce cas numérique, mais peu importe pour notre propos).

Les avantages de cette approche sont les suivants:

  • syntaxe simple/lisible et facile à maintenir
  • pas besoin de s'inquiéter de la conformité POSIX
  • si nous avons besoin de quelque chose pouvant évoluer en plusieurs fichiers et en instructions de ligne de commande plus courtes - nous avons déjà for fname in sys.argv[1:], et nous pourrions même ajouter une flexibilité supplémentaire en spécifiant des modèles sur la ligne de commande;
  • nous pouvons ajouter une option récursive avec le module os.walk si nous voulons/devons
  • si nous devons imprimer la ligne suivante sans condition (et donc ignorer les lignes qui ne suivent pas le modèle), nous pourrions utiliser simplement fd.readline()
    #!/usr/bin/env python3

    import sys

    for fname in sys.argv[1:]:
        with open(fname) as fd:
            for line in fd:

                start_index = 0
                end_index = len(line)-1

                if line.startswith('x'):
                    start_index = line.find('A')
                    end_index = line.rfind('B')+1
                    line = fd.readline()
                    print(line[start_index:end_index])
3