सी

2013-02-12 56 views
12

में पीछे एक पाठ फ़ाइल पढ़ना सी में पीछे की फ़ाइल को पढ़ने का सबसे अच्छा तरीका क्या है? मुझे पहले पता है कि आप सोच रहे होंगे कि इसका कोई उपयोग नहीं है, लेकिन अधिकांश लॉग इत्यादि फ़ाइल के अंत में सबसे हालिया डेटा जोड़ते हैं। मैं पीछे की ओर फ़ाइल से पाठ में पढ़ने के लिए, लाइनों में यह बफरिंग चाहते हैं - किसी

एबीसी
है डीईएफ़
GHI

लाइनों में GHI, डीईएफ़, एबीसी पढ़ना चाहिए।

अब तक मैं कोशिश की है:

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

    void read_file(FILE *fileptr) 
    { 
     char currentchar = '\0'; 
     int size = 0; 

     while(currentchar != '\n') 
     { 
      currentchar = fgetc(fileptr); printf("%c\n", currentchar); 
      fseek(fileptr, -2, SEEK_CUR); 
      if(currentchar == '\n') { fseek(fileptr, -2, SEEK_CUR); break; } 
      else size++; 

     } 
     char buffer[size]; fread(buffer, 1, size, fileptr); 
     printf("Length: %d chars\n", size); 
     printf("Buffer: %s\n", buffer); 


    } 


    int main(int argc, char *argv[]) 
    { 
     if(argc < 2) { printf("Usage: backwards [filename]\n"); return 1; } 

     FILE *fileptr = fopen(argv[1], "rb"); 
     if(fileptr == NULL) { perror("Error:"); return 1; } 

     fseek(fileptr, -1, SEEK_END); /* Seek to END of the file just before EOF */ 
     read_file(fileptr); 


     return 0; 


    } 

बस एक पंक्ति पढ़ सकते हैं और ऐसा करने से बफ़र करने की कोशिश में। क्षमा करें कि मेरा कोड भयानक है, मैं बहुत उलझन में आ रहा हूं। मुझे पता है कि आप सामान्य रूप से पूरी फ़ाइल के लिए स्मृति आवंटित करेंगे और फिर डेटा में पढ़ेंगे, लेकिन बड़ी फ़ाइलों के लिए जो लगातार बदलते हैं, मैंने सोचा कि यह सीधे पढ़ना बेहतर होगा (विशेष रूप से यदि मैं किसी फ़ाइल में टेक्स्ट खोजना चाहता हूं)।

अग्रिम

धन्यवाद * क्षमा करें, यह लिनक्स पर उपयोग किया जाएगा उल्लेख करना भूल गया तो नई-पंक्तियों सीआर बिना सिर्फ NL कर रहे हैं। *

+2

आप संभवतः [स्मृति फ़ाइल को मैप करें] (http://en.wikipedia.org/wiki/Memory-mapped_file), और फ़ाइल को "पढ़ने" के लिए सूचक अंकगणित का उपयोग कर सकते हैं। फाइल पॉइंटर के साथ लगातार कूदने की तुलना में सरल हो सकता है। –

+0

सी मानक से: 'एक बाइनरी स्ट्रीम को SEEK_END के किसी भी मूल्य के साथ fseek कॉल का अर्थपूर्ण रूप से समर्थन करने की आवश्यकता नहीं है। –

+0

शायद आप सादा फ़ाइल के बजाय डीबी में लॉग इन कर सकते हैं? –

उत्तर

7

fseek(binaryStream, offset, SEEK_END) से काम करने की गारंटी नहीं है, इसलिए मैं फ़ाइल आकार निर्धारण के एक और पोर्टेबल (उम्मीदवार) तरीके की अनुशंसा करता हूं। नीचे दिया गया कोड देखें।

मेरा मानना ​​है कि फ़ाइलों को कम से कम न्यूनतम रूप से कर्नेल स्तर पर बफर किया जाना चाहिए (उदाहरण के लिए डिफ़ॉल्ट रूप से प्रति फ़ाइल कम से कम एक ब्लॉक को बफर करना), इसलिए खोज में अतिरिक्त मात्रा में अतिरिक्त I/O नहीं होना चाहिए और केवल फ़ाइल स्थिति को अग्रिम करना चाहिए आंतरिक रूप से। यदि डिफ़ॉल्ट बफरिंग संतोषजनक नहीं है, तो आप I/O को तेज करने के लिए setvbuf() का उपयोग करने का प्रयास कर सकते हैं।

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

/* File must be open with 'b' in the mode parameter to fopen() */ 
long fsize(FILE* binaryStream) 
{ 
    long ofs, ofs2; 
    int result; 

    if (fseek(binaryStream, 0, SEEK_SET) != 0 || 
     fgetc(binaryStream) == EOF) 
    return 0; 

    ofs = 1; 

    while ((result = fseek(binaryStream, ofs, SEEK_SET)) == 0 && 
     (result = (fgetc(binaryStream) == EOF)) == 0 && 
     ofs <= LONG_MAX/4 + 1) 
    ofs *= 2; 

    /* If the last seek failed, back up to the last successfully seekable offset */ 
    if (result != 0) 
    ofs /= 2; 

    for (ofs2 = ofs/2; ofs2 != 0; ofs2 /= 2) 
    if (fseek(binaryStream, ofs + ofs2, SEEK_SET) == 0 && 
     fgetc(binaryStream) != EOF) 
     ofs += ofs2; 

    /* Return -1 for files longer than LONG_MAX */ 
    if (ofs == LONG_MAX) 
    return -1; 

    return ofs + 1; 
} 

/* File must be open with 'b' in the mode parameter to fopen() */ 
/* Set file position to size of file before reading last line of file */ 
char* fgetsr(char* buf, int n, FILE* binaryStream) 
{ 
    long fpos; 
    int cpos; 
    int first = 1; 

    if (n <= 1 || (fpos = ftell(binaryStream)) == -1 || fpos == 0) 
    return NULL; 

    cpos = n - 1; 
    buf[cpos] = '\0'; 

    for (;;) 
    { 
    int c; 

    if (fseek(binaryStream, --fpos, SEEK_SET) != 0 || 
     (c = fgetc(binaryStream)) == EOF) 
     return NULL; 

    if (c == '\n' && first == 0) /* accept at most one '\n' */ 
     break; 
    first = 0; 

    if (c != '\r') /* ignore DOS/Windows '\r' */ 
    { 
     unsigned char ch = c; 
     if (cpos == 0) 
     { 
     memmove(buf + 1, buf, n - 2); 
     ++cpos; 
     } 
     memcpy(buf + --cpos, &ch, 1); 
    } 

    if (fpos == 0) 
    { 
     fseek(binaryStream, 0, SEEK_SET); 
     break; 
    } 
    } 

    memmove(buf, buf + cpos, n - cpos); 

    return buf; 
} 

int main(int argc, char* argv[]) 
{ 
    FILE* f; 
    long sz; 

    if (argc < 2) 
    { 
    printf("filename parameter required\n"); 
    return -1; 
    } 

    if ((f = fopen(argv[1], "rb")) == NULL) 
    { 
    printf("failed to open file \'%s\'\n", argv[1]); 
    return -1; 
    } 

    sz = fsize(f); 
// printf("file size: %ld\n", sz); 

    if (sz > 0) 
    { 
    char buf[256]; 
    fseek(f, sz, SEEK_SET); 
    while (fgetsr(buf, sizeof(buf), f) != NULL) 
     printf("%s", buf); 
    } 

    fclose(f); 
    return 0; 
} 

मैंने केवल 2 अलग-अलग कंपाइलरों वाले विंडोज़ पर इसका परीक्षण किया है।

+0

इसके लिए धन्यवाद, यह वास्तव में अच्छी तरह से काम करता है (मैं कभी भी अपने साथ नहीं आ सकता था, मेरे मस्तिष्क को बहुत करीब मोड़ता है!)। – Joshun

+0

कोई जांच नहीं। मैंने एक छोटी सी बग तय कर दी है, इसलिए अब यह ठीक से मामलों को संभाल सकता है जहां अंतिम पंक्ति '' \ n '' के साथ समाप्त नहीं होती है (मूल रूप से यह पिछली पंक्ति के साथ समेकित होगी, यदि कोई हो)। –

+0

@AlexeyFrunze, क्या इसे आखिरी बार मुफ्त बफ चाहिए? – scorpiozj

9

आप प्रोग्राम tac के माध्यम से इनपुट को पाइप कर सकते हैं, जो cat जैसा है लेकिन पीछे की ओर!

http://linux.die.net/man/1/tac

+1

+1 और मैं अभी 'tac' के बारे में अभी सीखता हूं, यह कमाल है। – Mike

+0

अच्छा लगता है, क्या इसमें सी फ़ंक्शन है? – Joshun

+0

आप निश्चित रूप से t'internet पर 'tac' के लिए स्रोत कोड पा सकते हैं। उदाहरण के लिए यहां: http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/tac.c –

4

काफी कुछ तरीकों से आप ऐसा कर सकता है कर रहे हैं, लेकिन एक समय में एक बाइट पढ़ने निश्चित रूप से गरीब विकल्पों में से एक है।

अंतिम, 4KB कहें, और अंतिम चरित्र से पिछली नई लाइन तक वापस चलना मेरी पसंद होगी।

फ़ाइल का एक और विकल्प mmap फ़ाइल है, और केवल यह दिखाएं कि फ़ाइल मेमोरी का एक ढेर है, और उसमें पीछे की ओर स्कैन करें। [आप mmap बता सकते हैं कि आप इसे पीछे से पढ़ रहे हैं, इसे आपके लिए डेटा प्रीफ़ेच करने के लिए]।

यदि फ़ाइल बहुत बड़ी है (कई गीगाबाइट्स), तो आप केवल mmap में फ़ाइल के एक छोटे हिस्से का उपयोग करना चाह सकते हैं।

+0

धन्यवाद, मैं इसे 'mmaping' करने की कोशिश करूंगा – Joshun

0

प्रत्येक बाइट ध्वनियों के लिए FSEEKING बेहद धीमी गति से लगता है।

यदि आपको स्मृति मिल गई है, तो बस पूरी फ़ाइल को स्मृति में पढ़ें और या तो इसे उलट दें या इसे पीछे स्कैन करें।

एक और विकल्प विंडोज मेमोरी मैप की गई फाइलें होगी।

+2

मेमोरी मैप की गई फ़ाइलें विंडोज़- विशिष्ट विशेषता :) –

1

आप इसे कैसे करना सीखना चाहते हैं, तो यहां Debian/Ubuntu के उदाहरण है (आरपीएम आधारित distros जैसे अन्य के लिए, के रूप में की जरूरत के लिए अनुकूलित):

~$ which tac 
/usr/bin/tac 
~$ dpkg -S /usr/bin/tac 
coreutils: /usr/bin/tac 
~$ mkdir srcs 
~$ cd srcs 
~/srcs$ apt-get source coreutils 

(क्लिप apt-get उत्पादन)

~/srcs$ ls 
coreutils-8.13 coreutils_8.13-3.2ubuntu2.1.diff.gz coreutils_8.13-3.2ubuntu2.1.dsc coreutils_8.13.orig.tar.gz 
~/srcs$ cd coreutils-8.13/ 
~/srcs/coreutils-8.13$ find . -name tac.c 
./src/tac.c 
~/srcs/coreutils-8.13$ less src/tac.c 

यह 600 से अधिक लाइनों में बहुत लंबा नहीं है, और जब यह कुछ उन्नत सुविधाओं को पैक करता है, और अन्य स्रोतों से कार्यों का उपयोग करता है, तो रिवर्स लाइन बफरिंग कार्यान्वयन tac.c स्रोत फ़ाइल में प्रतीत होता है।