Jak grepować długi ciąg w wielu wierszach, nie wiedząc, gdzie jest nowa linia

1

Chcę wyszukać określony ciąg w wielu wierszach w pliku i uzyskać linię, w której znaleziono dopasowanie.

Jednak moim problemem jest to, że plik zawiera jeden bardzo długi ciąg, a nie, tzn. Słowa, i chcę wyszukać podsekwencję tego długiego łańcucha. Dlatego nie mogę używać programu pcregrep i po prostu wyszukać słowo1 \ nword2. Ponieważ tak naprawdę chcę uzyskać numer linii, w której znaleziono dopasowanie, nie mogę po prostu usunąć wszystkich znaków nowej linii ...

Oto przykład, jak wygląda mój plik. Właśnie napisałem wielką literę pasującego łańcucha, abyś mógł go znaleźć:

Ciąg do wyszukiwania:

gcbcdbfceebcfhfchaaccdgfcegffgedffaeaedcbaedhacebeeebcechbcbfeeccbdhcbfg

Plik do przeszukania:

abcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcde
abcdeabcde***GCBCDBFCEEBCFHFCHAACCDGFCEGFFGEDFFAEAEDC
BAEDHACEBEEEBCECHBCBFEECCBDHCBFG***ggfbhbgcedabceedfa
fbaaechaabdbffbebecebaacfcfcdcggfchddcefbcbdegbbba

Czy ktoś z was ma na to łatwe rozwiązanie?

Jeśli nie ma do tego dostępnego narzędzia, napisałbym po prostu krótki skrypt Pythona, aby to zrobić, ale wydaje mi się, że każde narzędzie bash byłoby bardziej wydajne ...

EDYCJA :

Bardzo dziękuję za odpowiedzi, działają one bardzo dobrze, jeśli pozycja znaku nowej linii jest znana.

Przykro mi jednak z powodu nieprecyzyjności mojego pytania. Mój problem polega na tym, że nie wiem, czy w ciągu znaków w pliku znajduje się nowa linia, a nawet więcej niż jedna nowa linia, a ponadto nie wiem, gdzie ona jest. Poprawiłem ciąg wyszukiwania, usuwając nowy wiersz, który nieświadomie wstawiłem.

Czy jest jakiś sposób, aby zezwolić na znak nowej linii w dowolnej pozycji ciągu?

TabeaKischka
źródło

Odpowiedzi:

1

Dodam nową odpowiedź teraz, gdy lepiej rozumiem problem. Podaję to tylko jako działający przykład, ale nie twierdzę, że jest dobry. :)

Rozumiem również, że pytanie to nie chciało używać Pythona ze względu na obawy przed nieefektywnością. Rozumiem więc, że to podejście nie spełnia całej prośby. :(

#!/usr/bin/env python
import sys

def findall_iter(S, pat):
  index = -1
  while True:
    try:
      index = S.index(pat, index+1)
      yield index
    except ValueError:
      raise StopIteration

def findall(S, pat):
  return list(findall_iter(S, pat))

# read in arguments
S = open(sys.argv[2]).read()
pattern = sys.argv[1]

# get indices of all newlines
newline_indices = findall(S, '\n')

# get psudo-indices of all pattern matches
pat_indices = findall(S.replace('\n', ''), pattern)

# iterate through each pattern match psudo-index and
# correlate it back to a real line number from the file
line_numbers = []
for pi in pat_indices:
  for i, ni in enumerate(newline_indices):
    if ni > pi+i:
      line = i + 1
      if line not in line_numbers:
        line_numbers.append(i+1)
      break

print '\n'.join(map(str, line_numbers))

Plusy:

  • Jeśli plik nie jest zbyt duży (<1 GB), wszystkie operacje są wykonywane w pamięci.
  • Używa metody str.index do znalezienia podciągów zamiast (wolniejszego) dopasowywania wyrażeń regularnych
  • Przejrzystsze niż przy użyciu wyrażeń regularnych

Cons:

  • Nie działa dobrze z dużymi plikami.
  • Tworzy dwa tymczasowe ciągi do wykonania zadania.
  • Ostatnia pętla for jest trudna do zrozumienia.
  • Jest Python (co osobiście nie uważam za oszustwo).
Dave
źródło
Cześć Dave, dziękuję za twój wysiłek. Ten skrypt Pythona robi dokładnie to, co chcę i jest naprawdę szybki! Przetestowałem go na pliku tekstowym 400 MB i zajęło to tylko kilka sekund. Skrypt Pythona, który napisałem, był ponad 10 razy wolniejszy. (Użyłem jeszcze kilku pętli, prawdopodobnie to jest powód ...) Jeszcze raz dziękuję wszystkim, którzy pomogli!
TabeaKischka
Cieszę się, że mogłem pomóc. Możesz dodać do niego obsługę większych plików, wykonując kilka sztuczek buforujących. Ale zajmie to więcej czasu i sprawi, że kod będzie mniej czytelny.
Dave
4

Zrobiłbym to za pomocą sedskryptu. Umieść to w pliku, a następnie użyj, sed -nfaby go uruchomić.

:restart
/gcbcdbfceebcfhfchaaccdgfcegffgedffaeaedc$/{
    #   Found the first part, now discard it
    s/^.*$//
    #   Read a new line into the buffer
    N
    #   Discard the new line inserted by the N operation
    s/^\n//
    #   If next line isn't a match, start over
    /^baedhacebeeebcechbcbfeeccbdhcbfg/!b restart
    #   If it is a match, print the line number
    =
    }

Oto jak to wygląda uruchomić bash. Zauważ, że wypisuje numer drugiej dopasowanej linii.

bash-4.1$ cat sample.txt
abcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcde
abcdeabcde***gcbcdbfceebcfhfchaaccdgfcegffgedffaeaedc
baedhacebeeebcechbcbfeeccbdhcbfg***ggfbhbgcedabceedfa
fbaaechaabdbffbebecebaacfcfcdcggfchddcefbcbdegbbba
bash-4.1$
bash-4.1$ cat findmatch.sed
:restart
/gcbcdbfceebcfhfchaaccdgfcegffgedffaeaedc$/{
   #  Found the first part, now discard it
   s/^.*$//
   #  Read a new line into the buffer
   N
   #  Discard the new line inserted by the N operation
   s/^\n//
   #  If next line isn't a match, start over
   /^baedhacebeeebcechbcbfeeccbdhcbfg/!b restart
   #  If it is a match, print the line number
   =
   }
bash-4.1$
bash-4.1$ sed -nf findmatch.sed sample.txt
3
bash-4.1$
Nicole Hamilton
źródło
3

Jestem trochę zdezorientowany, pod jakimi ograniczeniami działasz. Jeśli jednak potrzebujesz numeru linii, zarówno grep, jak i pcregrep mogą dać ci flagę -n.

$ pcregrep -nM "gcbcdbfceebcfhfchaaccdgfcegffgedffaeaedc\nbaedhacebeeebcechbcbfeeccbdhcbfg" | cut -d: -f1
2
baedhacebeeebcechbcbfeeccbdhcbfg***ggfbhbgcedabceedfa

pcregrep pokazuje tylko numer pierwszego dopasowanego wiersza, najwyraźniej więc będziesz musiał pominąć każdy inny wiersz wyjścia za pomocą sed (potokuj powyżej sed -n 'p;N'), jeśli chcesz tylko numery wierszy jako wyjście.

Dave
źródło