Extrai uma string de uma linha entre as posições dadas por um padrão em outra linha

6

Eu estou olhando para a saída dos caracteres entre duas posições A e B que são especificadas pela linha anterior. Por par, as duas linhas são iguais em comprimento, mas entre pares os comprimentos podem variar. Existe uma maneira eficiente (tamanhos de arquivo enormes) para fazer isso com grep , sed ou awk ?

Arquivo de exemplo:

xxxxxxAxxxxxxBxxxxxx
1234567890MNOPQRSTUV
xxAxxxxxxxxxxxxxxBxxxxxx
1234567890MNOPQRSTUVWXYZ

...

Gostaria de obter a saída:

7890MNOP
34567890MNOPQRST

...

    
por Freewheel 04.04.2018 / 18:12

6 respostas

8

Usando awk :

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

Explicação: leia em homem awk :

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.
    
por devWeek 04.04.2018 / 19:35
7

Como você mencionou sed , você pode fazer isso com um script sed também:

/^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).(.*)//    # 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.*).$//    # 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
}

Se você colocar tudo isso em uma linha de comando, será assim:

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

-r é necessário para que sed possa entender os parênteses de agrupamento regex sem escapes extras.

FWIW, eu não acho que isso poderia ser feito apenas com grep , embora eu ficaria feliz em provar que está errado.

    
por Digital Trauma 05.04.2018 / 00:51
7

Embora você possa fazer isso com o AWK, sugiro Perl. Aqui está um 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";
}

Você pode nomear esse arquivo de script como quiser. Se você fosse nomear interval e colocar no diretório atual, você pode marcá-lo como executável com chmod +x interval . Então você pode correr:

./interval paths...

Substitua paths... pelo nome de caminho ou nome de caminho reais pelos arquivos que você deseja analisar. Por exemplo:

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

A maneira como o script funciona é que, até o final da entrada ser alcançado (ou seja, sem mais linhas), ele:

  • Lê uma linha, $pattern , que é sua string com A e B , e outra linha, $text , que é a string que será fatiada.
  • Localiza o índice do primeiro A em $pattern e o primeiro B além de qualquer um que tenha precedido esse primeiro A e os armazena nas variáveis $start e $stop , respectivamente .
  • Executa apenas a parte de $text cujos índices variam de $start a $stop . A função substr de Perl usa argumentos de deslocamento e comprimento, que é o motivo da subtração, e você inclui a letra imediatamente abaixo de B , que é a razão para adicionar 1 .
  • Imprime apenas essa parte, seguida por uma quebra de linha.

Se por algum motivo você preferir um comando curto de uma linha que atinja a mesma coisa, mas seja facilmente colado - mas também seja mais difícil de entender e manter - então você pode usar isso:

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

(Como antes, você precisa substituir paths... pelos nomes dos caminhos reais.)

    
por Eliah Kagan 04.04.2018 / 18:42
3

Aqui está uma maneira de fazer isso no GNU awk:

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

Não sabemos ao certo se ...

  • pode haver linhas entre ou antes dos casais, que não fazem parte de um casal; cabeçalho? explicação? comentar?
  • a primeira linha começa com x por definição
  • a segunda linha do casal possivelmente começa com x

Para capturar todas essas situações, usando set() , podemos procurar por linhas que somente existam de (todos) x , A , B . Estes, podemos ser positivos, são as primeiras linhas de nossos casais.

Assim, entramos em 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

Assim, a saída de:

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

torna-se:

7890MNOP
34567890MNOPQRST
x234567890MNOPQR
    
por Jacob Vlijm 05.04.2018 / 12:23
3

Com a sintaxe muito simples do Python 3, podemos criar o seguinte script:

#!/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])

Que funciona assim:

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

O OP forneceu MCVE , mas não forneceu outros requisitos, portanto, com base no que eles mostram, temos um padrão alternado: primeira linha que começa com "x" e, em seguida, linha com dados (neste caso, numérica, mas isso não importa para o nosso propósito).

As vantagens dessa abordagem são:

  • sintaxe simples / legível e fácil de manter
  • não precisa se preocupar com a conformidade com POSIX
  • se precisarmos de algo que alcance vários arquivos e instruções de linha de comando mais curtas - já temos for fname in sys.argv[1:] e poderíamos adicionar flexibilidade adicional na especificação de padrões na linha de comando;
  • podemos adicionar opção recursiva com os.walk module se quisermos / precisarmos
  • se precisarmos imprimir a próxima linha incondicionalmente (e, assim, ignorar as linhas que não seguem o padrão), poderíamos usar apenas 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])
    
por Sergiy Kolodyazhnyy 05.04.2018 / 01:35