2017-01-18 53 views
2

我有一個二進制數據文件與各種字符串灑在整個。我正在嘗試編寫一個C代碼來查找文件中第一次出現用戶指定的字符串。 (我知道這是可以使用bash做,但我需要其他原因的C代碼)的代碼,因爲它代表的是:strstr中的微妙?

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

#define CHUNK_SIZE 512 

int main(int argc, char **argv) { 
    char *fname = argv[1]; 
    char *tag = argv[2]; 
    FILE *infile; 
    char *chunk; 
    char *taglcn = NULL; 
    long lcn_in_file = 0; 
    int back_step; 
    fpos_t pos; 

    // allocate chunk 
    chunk = (char*)malloc((CHUNK_SIZE + 1) * sizeof(char)); 

    // find back_step 
    back_step = strlen(tag) - 1; 

    // open file 
    infile = fopen(fname, "r"); 

    // loop 
    while (taglcn == NULL) { 
     // read chunk 
     memset(chunk, 0, (CHUNK_SIZE + 1) * sizeof(char)); 
     fread(chunk, sizeof(char), CHUNK_SIZE, infile); 
     printf("Read %c\n", chunk[0]); 
     // look for tag 
     taglcn = strstr(chunk, tag); 
     if (taglcn != NULL) { 
      // if you find tag, add to location the offset in bytes from beginning of chunk 
      lcn_in_file += (long)(taglcn - chunk); 
      printf("HEY I FOUND IT!\n"); 
     } else { 
      // if you don't find tag, add chunk size minus back_step to location and ... 
      lcn_in_file += ((CHUNK_SIZE - back_step) * sizeof(char)); 
      // back file pointer up by back_step for next read 
      fseek(infile, -back_step, SEEK_CUR); 
      fgetpos(infile, &pos); 
      printf("%ld\n", pos); 
      printf("%s\n\n\n", chunk); 
     } 
    } 
    printf("%ld\n", lcn_in_file); 

    fclose(infile); 
    free(chunk); 
} 

如果你想知道,back_step放在採取不太可能的護理有問題的字符串被分割爲chunk邊界。

我想要檢查的文件大小約爲1Gb。問題是,由於某種原因,我可以在前9000個左右的字節中找到任何字符串,但除此之外,strstr以某種方式未檢測到任何字符串。也就是說,如果我查找位於文件的9000或更多字節的字符串,則strstr不會檢測到它。該代碼讀取整個文件並從未找到搜索字符串。

我嘗試過從012到30000變化CHUNK_SIZE,結果沒有變化。我也嘗試過不同的back_step。我甚至在診斷代碼中輸入chunk字符,當strstr找不到字符串時,並且確定該字符串正是它應該是的位置。診斷輸出pos始終正確。

誰能告訴我我哪裏出錯了嗎?是strstr這裏使用的錯誤工具?

+1

雖然這不一定是問題,爲了使用任意的追求(如從負'SEEK_CUR'偏移),你必須以二進制方式打開的流。您的流在文本模式下打開。 – AnT

+3

另外,是否有機會通過二進制文件進行搜索,即是否有零字節的文件? – AnT

+0

@安泰是的,可能就是這樣。謝謝。 –

回答

4

最可能的原因爲strstr在你的代碼失敗是文件中的空字節的存在。此外,您應該以二進制模式打開文件,以使文件偏移有意義。

要掃描的字節塊中的序列,可以使用memmem()功能。如果不是您的系統上,這裏是一個簡單的實現:

#include <string.h> 

void *memmem(const void *haystack, size_t n1, const void *needle, size_t n2) { 
    const unsigned char *p1 = haystack; 
    const unsigned char *p2 = needle; 

    if (n2 == 0) 
     return (void*)p1; 
    if (n2 > n1) 
     return NULL; 

    const unsigned char *p3 = p1 + n1 - n2 + 1; 
    for (const unsigned char *p = p1; (p = memchr(p, *p2, p3 - p)) != NULL; p++) { 
     if (!memcmp(p, p2, n2)) 
      return (void*)p; 
    } 
    return NULL; 
} 

你會修改你的程序是這樣的:

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

void *memmem(const void *haystack, size_t n1, const void *needle, size_t n2); 

#define CHUNK_SIZE 65536 

int main(int argc, char **argv) { 

    if (argc < 3) { 
     fprintf(sderr, "missing parameters\n"); 
     exit(1); 
    } 

    // open file 
    char *fname = argv[1]; 
    FILE *infile = fopen(fname, "rb"); 
    if (infile == NULL) { 
     fprintf(sderr, "cannot open file %s: %s\n", fname, strerror(errno)); 
     exit(1); 
    } 

    char *tag = argv[2]; 
    size_t tag_len = strlen(tag); 
    size_t overlap_len = 0; 
    long long pos = 0; 

    char *chunk = malloc(CHUNK_SIZE + tag_len - 1); 
    if (chunk == NULL) { 
     fprintf(sderr, "cannot allocate memory\n"); 
     exit(1); 
    } 

    // loop 
    for (;;) { 
     // read chunk 
     size_t chunk_len = overlap_len + fread(chunk + overlap_len, 1, 
               CHUNK_SIZE, infile); 
     if (chunk_len < tag_len) { 
      // end of file or very short file 
      break; 
     } 
     // look for tag 
     char *tag_location = memmem(chunk, chunk_len, tag, tag_len); 
     if (tag_location != NULL) { 
      // if you find tag, add to location the offset in bytes from beginning of chunk 
      printf("string found at %lld\n", pos + (tag_location - chunk)); 
      break; 
     } else { 
      // if you don't find tag, add chunk size minus back_step to location and ... 
      overlap_len = tag_len - 1; 
      memmove(chunk, chunk + chunk_len - overlap_len, overlap_len); 
      pos += chunk_len - overlap_len; 
     } 
    } 

    fclose(infile); 
    free(chunk); 
    return 0; 
} 

注意該文件中的CHUNK_SIZE字節的塊,讀這如果CHUNK_SIZE是文件系統塊大小的倍數,則是最優的。

1

二進制數據文件將包含充當字符串結尾的'\ 0'字節。在那裏越多,strstr將要搜索的區域越短。注意strstr將在其遇到0字節時完成其工作。

可以,只要你還在塊內掃描間隔記憶像塊的空字節後

while (strlen (chunk) < CHUNKSIZE) 
    chunk += strlen (chunk) + 1; 

即重新啓動。

+0

聽起來沒錯。除了逐字節地進行修復之外是否還有其他修復方法,也就是說,有些相當於用於通用字節數組的strstr? –

5

既然你說你的文件是二進制的,strstr()將停止掃描文件中的第一個空字節。

如果您希望查找二進制數據中的模式,那麼memmem()函數適用(如果可用)。它可以在Linux和其他一些平臺(BSD,macOS,...)上使用,但它並沒有被定義爲標準C或POSIX的一部分。它與memcpy()承受的strstr()大致相同。


請注意,您的代碼應檢測由fread()讀取的字節數,並且只能在搜索。

char *tag = …;  // Identify the data to be searched for 
size_t taglen = …; // Identify the data's length (maybe strlen(tag)) 
int  nbytes; 
while ((nbytes = fread(chunk, 1, (CHUNK_SIZE + 1), infile)) > 0) 
{ 
    … 
    tagcln = memmem(chunk, nbytes, tag, taglen); 
    if (tagcln != 0) 
     …found it… 
    … 
} 

它不是很清楚爲什麼你的塊大小爲+1fread()函數不會在數據末尾添加空字節或類似的東西。我保持不變,但可能不會在我自己的代碼中使用它。

確定跨越兩個區塊之間邊界的標籤是很好的。

+0

他有'bash',所以他可能有'memmem()' – Jasen

+0

@Jasen:有一個合理的機會,是的,但是我曾經在沒有'memmem()'的Bash機器上工作 - 他們不是當然運行Linux。 –

+0

@JonathanLeffler感謝您的好評。自從他開始執行memmem()以來,不得不與chrqlie一起工作,但這很有幫助。多謝。 –

1

對於一些非常簡單的代碼,你可以使用mmap()memcmp()

錯誤檢查和正確的頭文件作爲練習留給讀者(至少有一個錯誤 - 另一個練習爲讀者找到):

int main(int argc, char **argv) 
{ 
    // put usable names on command-line args 
    char *fname = argv[ 1 ]; 
    char *tag = argv[ 2 ]; 

    // mmap the entire file 
    int fd = open(fname, O_RDONLY); 
    struct stat sb; 
    fstat(fd, &sb); 
    char *contents = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0); 
    close(fd); 

    size_t tag_len = strlen(tag); 

    size_t bytes_to_check = 1UL + sb.st_size - tag_len; 

    for (size_t ii = 0; ii < bytes_to_check; ii++) 
    { 
     if (!memcmp(contents + ii, tag, tag_len)) 
     { 
      // match found 
      // (probably want to check if contents[ ii + tag_len ] 
      // is a `\0' char to get actual string matches) 
     } 
    } 

    munmap(contents, sb.st_len); 

    return(0); 
} 

這可能不會在任何地方靠近最快的方式(一般,mmap()不會是附近的一個性能贏家的任何地方,特別是在通過簡單地從開始到結束文件流媒體這種使用情況下),但它的簡單。 (請注意,mmap()也有問題,如果文件大小在讀取時發生變化,如果文件增長,則不會看到附加數據;如果文件縮短,則嘗試訪問SIGBUS時會出現SIGBUS(請注意,mmap()也會遇到問題)讀取數據刪除)。