Извлеките строку из строки между положениями, данными шаблоном в другой строке

Я надеюсь производить символы между двумя положениями A и B, которые указаны предыдущей строкой. На пару эти две строки равны по длине, но среди пар могут варьироваться длины. Есть ли эффективный путь (огромные размеры файла), чтобы сделать это с grep, sed, или awk?

Файл в качестве примера:

xxxxxxAxxxxxxBxxxxxx
1234567890MNOPQRSTUV
xxAxxxxxxxxxxxxxxBxxxxxx
1234567890MNOPQRSTUVWXYZ

...

Я хотел бы получить вывод:

7890MNOP
34567890MNOPQRST

...

6
задан 4 April 2018 в 09:31

6 ответов

Используя awk:

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

Объяснение: читайте в человеке 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.
8
ответ дан 23 November 2019 в 07:13

Так как Вы упомянули , можно сделать это с 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
}

При помещении этого всего на одну командную строку она похожа на это:

$ 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 необходим так, чтобы sed может понять regex группирующиеся круглые скобки без дополнительных Escape.


FWIW, я не думаю, что это могло быть сделано просто с grep, хотя я был бы рад быть доказанным неправым.

7
ответ дан 23 November 2019 в 07:13

Хотя можно сделать это с AWK, я предлагаю Perl. Вот сценарий:

#!/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";
}

Можно назвать тот файл сценария вообще, Вам нравится. Если необходимо было назвать его interval и вставленный в текущий каталог, затем можно отметить его исполняемый файл с chmod +x interval. Затем можно работать:

./interval paths...

Замена paths... с фактическим путем или путями в файлы Вы хотите проанализировать. Например:

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

Способ, которым работает сценарий, состоит в том, что, пока конец входа не достигнут (т.е. больше никаких строк), он:

  • Читает строку, $pattern, который является Вашей строкой с A и B, и другая строка, $text, который является строкой, которая будет нарезана.
  • Находит индекс первого A в $pattern и первое B кроме любого, который, возможно, предшествовал этому сначала A, и хранит их в $start и $stop переменные, соответственно.
  • Части просто часть $text чьи индексы располагаются от $start кому: $stop. Perl substr функция берет смещение и аргументы длины, который является причиной вычитания, и Вы включаете букву сразу под B, который является причиной добавления 1.
  • Печать просто, что часть, сопровождаемая разрывом строки.

Если по некоторым причинам Вы предпочли бы короткую короткую команду, которая достигает того же самого, но легко вставляется в - но также и более трудна понять и поддержать - затем Вы могли использовать это:

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

(Как прежде, необходимо заменить paths... с фактическими путями.)

7
ответ дан 23 November 2019 в 07:13

Вот способ сделать это в GNU awk:

$ gawk 'NR%2 {split($0,a,/[AB]/); FIELDWIDTHS = length(a[1])" "length(a[2])+2; next} {print $2}' file
7890MNOP
34567890MNOPQRST
3
ответ дан 23 November 2019 в 07:13

Мы не знаем наверняка если..

  • могли бы быть строки между или перед парами, которые не являются частью пары; заголовок? объяснение? комментарий?
  • первая строка запускается с x по определению
  • вторая строка пары возможно запускается с x

Поймать все эти ситуации, с помощью set(), мы можем искать строки, которые только существуют (весь из) x, A, B. Они, мы можем быть положительными, являются первыми строками наших пар.

Таким образом мы входим в 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

Таким образом, вывод:

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

становится:

7890MNOP
34567890MNOPQRST
x234567890MNOPQR
3
ответ дан 23 November 2019 в 07:13

С очень простым синтаксисом Python 3 мы можем сделать следующий сценарий:

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

Который работает так:

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

OP обеспечил MCVE, но не обеспечил другие требования, таким образом, на основе того, что они показывают, у нас есть переменный шаблон: первая строка, которая запускается с "x", затем строка с данными (в этом случае числовой, но это не имеет значения для нашей цели).

Преимущества этого подхода:

  • простой/читаемый синтаксис и легкий поддержать
  • никакая потребность волноваться о соответствии POSIX
  • если нам нужно что-то, что увеличивается в несколько файлов и более коротких операторов командной строки - мы уже имеем for fname in sys.argv[1:], и мы могли даже добавить дополнительную гибкость определения шаблонов на командной строке;
  • мы можем добавить рекурсивную опцию с os.walk модуль, если мы хотим/нуждаемся
  • если мы должны распечатать следующую строку безусловно (и таким образом проигнорировать строки, которые не следуют за шаблоном), мы могли использовать просто 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
ответ дан 23 November 2019 в 07:13

Другие вопросы по тегам:

Похожие вопросы: