2009-10-26 26 views
5

Poniższy kod odczytuje plik tekstowy o jeden znak w czasie i wydrukować go na standardowe wyjście:Wyszukiwanie ciągu w pliku tekstowym C

#include <stdio.h> 

int main() 
{ 
    char file_to_open[] = "text_file.txt", ch; 
    FILE *file_ptr; 

    if((file_ptr = fopen(file_to_open, "r")) != NULL) 
    { 
     while((ch = fgetc(file_ptr)) != EOF) 
     { 
      putchar(ch); 
     } 
    } 
    else 
    { 
     printf("Could not open %s\n", file_to_open); 
     return 1; 
    } 
    return(0); 
} 

Ale zamiast drukować na stdout [putchar (ch)] Chcę przeszukuj plik pod kątem określonych ciągów podanych w innym pliku tekstowym, tj. strings.txt i wyjście linia z meczu na out.txt

text_file.txt:

 
1993 - 1999 Pentium 
1997 - 1999 Pentium II 
1999 - 2003 Pentium III 
1998 - 2009 Xeon 
2006 - 2009 Intel Core 2 

strings.txt:

 
Nehalem 
AMD Athlon 
Pentium 

w tym przypadku trzy pierwsze linie text_file.txt będzie pasować. Zrobiłem trochę badań operacji na plikach w C i wydaje mi się, że mogę czytać jedną postać w tym czasie z fgetc [jak ja robię w moim kodzie], jedną linię z fgets i jednym blokiem z fread, ale bez słowa jak sądzę byłby idealny w mojej sytuacji?

+3

dlaczego piszesz program ?! Użyj grep/awk/sed, aby to zrobić. –

+0

Nie, Tim. Tagi służą do wyszukiwania. Nikt nie będzie tego szukał. – GManNickG

+1

Tak, wiem ze standardowymi narzędziami Unix mogę to rozwiązać w ciągu kilku sekund, ale to ma na celu głębsze zrozumienie IO pliku C. –

Odpowiedz

7

Zakładam, że jest to ćwiczenie do nauki i po prostu szukasz miejsca do rozpoczęcia. W przeciwnym razie nie powinieneś odkrywać koła na nowo.

Poniższy kod powinien dać ci wyobrażenie o tym, co się dzieje. Jest to program, który pozwala określić nazwę przeszukiwanego pliku i pojedynczy argument do wyszukania w tym pliku. Powinieneś być w stanie zmodyfikować to, aby umieścić wyrazy do wyszukiwania w tablicy napisów i sprawdzić, czy którekolwiek ze słów w tej tablicy pojawiają się w jednym z wierszy przeczytać.

Kluczową funkcją, której szukasz, jest strstr.

#include <stdio.h> 
#include <string.h> 
#include <stdlib.h> 

#ifdef DEBUG 
#define INITIAL_ALLOC 2 
#else 
#define INITIAL_ALLOC 512 
#endif 

char * 
read_line(FILE *fin) { 
    char *buffer; 
    char *tmp; 
    int read_chars = 0; 
    int bufsize = INITIAL_ALLOC; 
    char *line = malloc(bufsize); 

    if (!line) { 
     return NULL; 
    } 

    buffer = line; 

    while (fgets(buffer, bufsize - read_chars, fin)) { 
     read_chars = strlen(line); 

     if (line[read_chars - 1] == '\n') { 
      line[read_chars - 1] = '\0'; 
      return line; 
     } 

     else { 
      bufsize = 2 * bufsize; 
      tmp = realloc(line, bufsize); 
      if (tmp) { 
       line = tmp; 
       buffer = line + read_chars; 
      } 
      else { 
       free(line); 
       return NULL; 
      } 
     } 
    } 
    return NULL; 
} 

int 
main(int argc, char *argv[]) { 
    FILE *fin; 
    char *line; 

    if (argc != 3) { 
     return EXIT_FAILURE; 
    } 

    fin = fopen(argv[1], "r"); 

    if (fin) { 
     while (line = read_line(fin)) { 
      if (strstr(line, argv[2])){ 
       fprintf(stdout, "%s\n", line); 
      } 
      free(line); 
     } 
    } 

    fclose(fin); 
    return 0; 
} 

Przykładowe wyjście:

 
E:\Temp> searcher.exe searcher.c char 
char * 
    char *buffer; 
    char *tmp; 
    int read_chars = 0; 
    char *line = malloc(bufsize); 
    while (fgets(buffer, bufsize - read_chars, fin)) { 
     read_chars = strlen(line); 
     if (line[read_chars - 1] == '\n') { 
      line[read_chars - 1] = '\0'; 
       buffer = line + read_chars; 
main(int argc, char *argv[]) { 
    char *line; 
+0

Wygląda to bardzo interesująco. Zakładasz, że jest to dla mnie nauka, i widzę, że źródło składa się z elementów, z którymi wcześniej pracowałem, więc powinienem móc w pełni zrozumieć ten kod. –

+0

Jestem całkiem nowy w kodzie C, ale właśnie zastąpiłem całą wywołanie funkcji read_line wywołaniem funkcji fgets i przydzielono linię char * w funkcji głównej do arbitralnie dużej liczby, ponieważ fgets zatrzymuje się na znaku "\ n". Czy możesz wyjaśnić przeznaczenie funkcji read_line? Wygląda na to, że jest tam dużo zbędnego kodu. – anon58192932

+1

@advocate [Jak duże jest wystarczająco duże?] (Http://en.wikipedia.org/wiki/Buffer_overflow) Zaczynam od bufora o rozsądnych rozmiarach i rozwijam go w razie potrzeby. W rzeczywistości należy jeszcze raz sprawdzić, czy bufor nie jest zbyt duży, aby uniemożliwić komputerowi brak pamięci, jeśli ktoś karmi strumień bez końcówek linii, ale było to proste ćwiczenie. –

4

Pamiętaj: fgetc(), getc(), getchar() all zwracają liczbę całkowitą, a nie znak. Liczba całkowita może być EOF lub poprawnym znakiem - ale zwraca jeszcze jedną wartość niż zakres obsługiwany przez typ znaku.

Piszesz substytut polecenia „fgrep”:

fgrep -f strings.txt text_file.txt > out.txt 

Zamiast znaków czytanie, masz zamiar trzeba czytać wiersze - przy użyciu fgets(). (Zapomnij, że funkcja gets() istnieje!)

Podszedłem do twojego kodu i wstawiłem return 0; na końcu dla ciebie (chociaż C99 wykonuje domyślny "return 0;" jeśli spadniesz z końca main()). Jednak C99 wymaga również jawnego typu zwrotu dla każdej funkcji - i dodałem "int" do "int main()" dla ciebie (ale nie możesz użyć wymowy zgodnej z C99, aby nie zwrócić 0 na końcu). Komunikaty o błędach powinny być zapisywane w standardowym błędzie, a nie w standardowym wyjściu.

Prawdopodobnie będziesz musiał użyć alokacji dynamicznej dla listy łańcuchów. Proste wyszukiwanie po prostu zastosuje "strstr()", wyszukując każdy z wymaganych ciągów w każdym wierszu wejścia (upewniając się, że przerwiesz pętlę po znalezieniu dopasowania, aby linia nie była powtarzana, jeśli istnieje wiele dopasowań w jednej linii).

Bardziej wyrafinowane wyszukiwanie pozwoliłoby na wstępne obliczenie, które znaki można zignorować, aby można było wyszukiwać wszystkie ciągi równolegle, przeskakując tekst szybciej niż pętla w pętli. Może to być modyfikacja algorytmu wyszukiwania, takiego jak Boyer-Moore lub Knuth-Morris-Pratt (dodana: lub Rabin-Karp, który jest przeznaczony do równoległego wyszukiwania wielu ciągów).

+0

osobiście preferuję pisanie funkcji do buforowania znaków ... używanie samych fgetów pozwala na dowolne ograniczenie długości linii. – asveikau

+0

@asveikau: Nie widzę różnicy? Używając fgetów zapewniamy bufor, możemy ustawić go w dowolnym rozmiarze. A jeśli linie w pliku strings.txt są dłuższe od bufora, i tak mamy kłopoty ... Czy masz na myśli, że powinniśmy zarządzać przypadkiem przepełnienia bufora, nawet jeśli używamy fgetów? tak, i jest to mniej oczywiste niż w przypadku bufora bez typu. – kriss

+0

fgets() odczytuje do podanej długości bufora; jeśli nie natknie się na znak nowej linii do czasu, gdy zabraknie mu miejsca, zatrzymuje się i wraca. Tak więc, jeśli ostatni znak nie jest nowy, a bufor jest pełny, to możesz znaleźć trochę więcej miejsca (ponownie przydzielić?), Aby umieścić dodatkowe znaki, ponownie wywołać fgets() (ostrożnie - zaczynając od tego, gdzie skończył, mówiąc tylko o dodatkowe miejsce) i uzyskać więcej linii. Więc tak, możesz napisać własnego czytnika, aby uzyskać dane do dynamicznie przydzielanego bufora, który rośnie - lub użyć fgets() do wykonania odczytu podczas obsługi bufora. –

2

Odczytywanie bloków jest zawsze lepsze, ponieważ tak działa bazowy system plików.

Dlatego po prostu czytaj bloki, sprawdź, czy któreś z twoich słów pojawiają się w buforze, , a następnie przeczytaj inny bufor pełny. Musisz tylko zachować ostrożność, aby powtórzyć kilka ostatnich znaków poprzedniego bufora w nowym, aby uniknąć braku wykrycia, jeśli słowa wyszukiwania znajdują się na granicy bufora.

Jeśli ten trywialny algorytm nie jest wystarczający (w twoim przypadku jest to prawdopodobnie), istnieje znacznie bardziej wyrafinowany algorytm do wyszukiwania jednocześnie kilku podciągów w jednym buforze, cf Rabin-Karp.

+0

Kiedy używasz fgetc(), jestem dość pewny, że stdio będzie czytał bloki i znaki bufora ... – asveikau

+0

prawda, ale wywołanie fgetc ma swój koszt sam i jeśli chcesz porównać dane wejściowe z ciągiem znaków (lub kilkoma ciągami znaków) musisz go gdzieś skopiować. To kosztuje znacznie więcej niż czytanie pełnego bufora i praca z nim. Czytanie pełnej linii, jak proponuje Jonathan, jest również dobrą alternatywą dla czytania pełnego bufora, jeśli nie chcesz zarządzać się krwawymi szczegółami bezpośrednio do odczytywania buforów. – kriss

2
cat strings.txt |while read x; do grep "$x" text_file.txt; done 
+1

Miałeś na myśli 'fgrep -f strings.txt text_file.txt> out.txt'? –

+0

Tak, tak, 'fgrep -f strings.txt text_file.txt'. Wydaje mi się, że większa ekspozycja oznacza więcej opcji. –

+0

dziękuję. napisanie programu C w tym celu to kompletna strata czasu. –

Powiązane problemy