分隔文本文件中的数据

Separate data in a text file

我有一大块数据 (hexdump),其中包括数千张小图像,数据的结构是这样的。

20 00 20 00 00 10 00 00 <data> 20 00 20 00 00 10 00 00 <data> ...

其中 (20 00 20 00 00 10 00 00) 是每个数据(图像)部分之间的分隔。

包含整个 hexdump 的文件 myfile 看起来像这样

3C 63 9E FF 38 5F 9E FF
31 59 91 FF 20 00 20 00
00 10 00 00 55 73 A2 FF
38 5D 9C FF 3A 5E 95 FF

我想做的基本上就是把它分开。我想把由 20 00 20 00 00 10 00 00 分隔的部分放在一个 txt 文件中作为 1.txt, 2.txt ... n.txt

我尝试按行阅读,但它会导致一些问题,因为 20 00 .. 部分在某些情况下可以在 2 行中找到,例如上面的示例,因此它不会找到所有出现的地方。

while (getline(myfile,line,'\n')){
    if (line == "20 00 20 00 00 10 00 00")
        ...
}

我的建议是读取二进制文件。如果它足够小你可以一次性全部读入内存,否则我建议你使用操作系统来map the file into memory(或者至少是"window")。

然后很容易找到分隔记录的8字节序列。首先简单地搜索 0x20,只要找到它,您就会看到它是否是整个分隔符序列的开始。

找到分隔符序列时,取上一个分隔符保存的位置,和新找到的分隔符的位置,中间的数据就是你要的数据。将新找到的分隔符位置保存为旧位置,继续寻找下一个分隔符。

鉴于您之后的实际数据序列可能会跨行拆分,您需要以最小的 "bite" 读取数据 - 双字符数组 - 并忽略白色space(space 或换行符)。

执行此操作后,您可以在将阅读内容写入子文件时跟踪阅读内容。获得 "magic sequence" 后,开始一个新的子文件。

您没有涵盖的两个复杂性:

  1. "magic sequence" 是否有可能作为普通数据的一部分存在于文件中?如果是这样,您将拆分一个单独的文件。
  2. 我假设您想要在每个子文件的末尾添加 "magic sequence"。这会给你的比较增加一点复杂性:
    • 如果开始匹配,需要暂停写入子文件
    • 如果你进行到一半突然停止匹配,你将不得不在写出新的不匹配条目之前写出部分匹配项。

这样做的一个好处是:如果子文件虽然仍在主文件中,但在行尾附近开始,它将在新行开始并在 16 个两个字符后中断,而不是模仿它在主文件中的位置。或者您是否希望子文件以真实字节输出,没有 space 分隔符?

我要去写这个程序:听起来很有趣!


好的,我写了下面的。希望用法描述了要做什么。我不是特别想使用流 - 我发现它们非常低效 - 但你开始了它......

//
// SubFile.cpp
//

#include <string>
#include <fstream>
#include <iostream>
#include <iomanip>

using namespace std;

const unsigned MaxBytesPerLine = 16;

const unsigned char magic[] = { '\x20','\x00','\x20','\x00','\x00','\x10','\x00','\x00' };

class OutFile : private ofstream {
public: // Methods
    using ofstream::is_open; // Let others see whether I'm open
    OutFile(const string &fileName, bool bin);
    bool Write(unsigned b);
    ~OutFile();
private: // Variables
    unsigned num; // Number bytes in line
    bool bin; // Whether to output binary
}; // OutFile

OutFile::OutFile(const string &filename, bool bin) :
         ofstream(filename),
         num(0),
         bin(bin) {
    if (!bin) {
        setf(uppercase);
    } // if
} // OutFile::OutFile(name, bin)

bool OutFile::Write(unsigned b) {
    if (bin) {
        char c = (char)b; // Endian fix!
        return write(&c, 1).good();
    } // if
    if (num > 0) {
        *this << " ";
    } // if
    *this << setbase(16) << setw(2) << setfill('0') << b;
    if (++num == MaxBytesPerLine) {
        *this << endl;
        num = 0;
    } // if
    return good();
} // OutFile::Write(b)

OutFile::~OutFile() {
    if (bin) {
        return;
    } // if
    if (num == 0) {
        return;
    } // if
    if (!good()) {
        return;
    } // if
    *this << endl;
} // OutFile::~OutFile

void Usage(char *argv0) {
    cout << "Usage:" << endl;
    cout << "     " << argv0 << " <filename.txt> [bin]" << endl;
    cout << "  Read <filename.txt> in hex char pairs, ignoring whitespace." << endl;
    cout << "  Write pairs out to multiple sub-files, called \"1.txt\", \"2.txt\" etc." << endl;
    cout << "  New files are started when the following sequence is detected: " << endl << " ";
    for (unsigned i = 0; i < sizeof(magic); ++i) {
        cout << ' ' << hex << setw(2) << setfill('0') << (int)magic[i];
    } // for
    cout << endl;
    cout << "  If bin is specified: write out in binary, and files have a '.bin' extension" << endl;
} // Usage(argv0)

int main(int argc, char *argv[]) {
    if (argc < 2) {
        Usage(argv[0]);
        return 1;
    } // if
    ifstream inFile(argv[1]);
    if (!inFile.is_open()) {
        cerr << "Could not open '" << argv[1] << "'!" << endl;
        Usage(argv[0]);
        return 2;
    } // if

    bool bin = (argc >= 3) &&
               (argv[2][0] == 'b'); // Close enough!
    unsigned fileNum = 0; // Current output file number

    inFile >> setbase(16); // All inFile accesses will be like this
    while (inFile.good()) { // Let's get started!
        string outFileName = to_string(++fileNum) + (bin ? ".bin" : ".txt");
        OutFile outFile(outFileName, bin);
        if (!outFile.is_open()) {
            cerr << "Could not create " << outFileName << "!" << endl;
            return (int)(fileNum + 2);
        } // if

        unsigned b; // byte read in
        unsigned pos = 0; // Position in 'magic'
        while (inFile >> b) {
            if (b > 0xFF) {
                cerr << argv[1] << " contains illegal value: "
                     << hex << uppercase << showbase << b << endl;
                return -1;
            } // if
            if (b == magic[pos]) {            // Found some magic!
                if (++pos == sizeof(magic)) { // ALL the magic?
                    break;                    // Leave!
                } // if
                continue;                     // Otherwise go back for more
            } // if
            if (pos > 0) {                   // Uh oh. No more magic!
                for (unsigned i = 0; i < pos; ++i) {
                    outFile.Write(magic[i]); // So write out what we got
                } // for
                pos = 0;
            } // if
            outFile.Write(b);
        } // while
    } // for
    if (inFile.eof()) {
        return 0; // Success!
    } // if

    string s;
    inFile.clear();
    getline(inFile, s);
    cerr << argv[1] << " contains invalid data: " << s << endl;
    return -2;
} // main(argc,argv)

Whenever someone posts code, there are invariably comments posted:
"Why didn't you do this?"
"Why did you do that?"
Let the floodgates open!

这是我的解决方案。这有点低效,但我可能会在完成期末考试后重写它。我假设有由白色-space 分隔的数据字节。问题很简单 -> 这只是一个模式匹配问题。我可以使用一些复杂的技术来处理这个问题,但我们的模式有一个非常小的固定大小。即使是蛮力方法也会有线性时间。

代码不言自明。我逐字节读取文件并将其添加到缓冲区(效率不高,只能在文件中保留 window 具有索引边界的数据 -> 这可以在创建过程中进行更高效的 I/O 操作新文件)。找到终止序列后,我们将其弹出并保存到文件中(我假设我们不想要空文件)。

void save(const std::vector<short>& bytes, std::string filename, int sequenceLength)
{
    if (!bytes.size()) return; // Don't want empty files

    std::ofstream outputFile(filename);
    int i = 0;
    for (short byte : bytes)
    {
        outputFile << std::uppercase << std::hex << byte;

        i = (i + 1) % sequenceLength;
        if (i) outputFile << " ";
        else   outputFile << std::endl;
    }
}

std::string getFilename(int number)
{
    std::stringstream ss;
    ss << number << ".txt";
    return ss.str();
}

short getIntFromHex(const char* buffer)
{
    short result;
    std::stringstream ss;
    ss << std::hex << buffer;
    ss >> result;
    return result;
}

bool findTerminatingSequence(const std::vector<short>& bytes, short terminatingSequence[], int sequenceLength)
{
    int i = 0;
    int startIndex = bytes.size() - sequenceLength;
    for (i; i < sequenceLength; i++)
        if (terminatingSequence[i] != bytes[startIndex + i])
            break;
    return i == sequenceLength;
}

void popSequence(std::vector<short>& bytes, int sequenceLength)
{
    for (int j = 0; j < sequenceLength; j++)
        bytes.pop_back();
}

int main()
{
    std::vector<short> bytes;
    std::ifstream inputFile("input.txt");
    int outputFileIndex = 1;
    int sequenceLength = 8;
    short terminatingSequence[] = { 0x20, 0x00, 0x20, 0x00, 0x00, 0x10, 0x00, 0x00 };
    short nextByte;
    char buffer[3];

    while (inputFile >> buffer)
    {
        nextByte = getIntFromHex(buffer);
        bytes.push_back(nextByte);
        if (bytes.size() < sequenceLength || 
            !findTerminatingSequence(bytes, terminatingSequence, sequenceLength)) 
            continue;

        popSequence(bytes, sequenceLength);
        save(bytes, getFilename(outputFileIndex++), sequenceLength);
        bytes.clear();
    }

    save(bytes, getFilename(outputFileIndex), sequenceLength);

    return 0;
}

我会按照这些思路使用 Perl:

#!/usr/bin/perl
use warnings;
use strict;

# Slurp entire file from stdin into variable $data
my $data = <>;

# Find offsets of all occurrences of marker in file
my @matches;
my $marker='\x20\x00\x20\x00\x00\x10\x00\x00';
while ($data =~ /($marker)/gi){
    # Save offset of this match - you may want to add length($marker) here to avoid including marker in output file
    push @matches, $-[0];
}

# Extract data between pairs of markers and write to file
for(my $i=0;$i<scalar @matches -1;$i++){
   my $image=substr $data, $matches[$i], $matches[$i+1] - $matches[$i];
   my $filename=sprintf("file-%05d",$i);
   printf("Saving match at offset %d to file %s\n",$matches[$i],$filename);
   open(MYFILE,">$filename");
   print MYFILE $image;
   close(MYFILE);
}

输出

Saving match at offset 12 to file file-00000
Saving match at offset 44 to file file-00001

运行 像这样:

./perlscript < binaryData

我或多或少地使用了这种技术来从相机中恢复损坏的闪存卡。您只需在整个闪存卡中搜索一些看起来像 JPEG/raw 文件开头的字节,然后抓取以下 10-12MB 并将其另存为文件。

绝对以二进制形式保存文件并转储实际的十六进制字节,而不是文本形式。您将节省 3 倍以上 space 并且读取文件的实现更容易编写。

话虽这么说,如果您的文件是二进制文件,这就是解决方案:

#include <fstream>  

using std::ifstream;
using std::ofstream;
using std::string;

void incrementFilename(char* filename) {
  int iFile;
  sscanf(filename, "%d.dat", &iFile);
  sprintf(filename, "%d.dat", ++iFile);
}

int main() {
  char outputFilename[16] = "1.dat";
  ifstream input("myfile.dat", ifstream::binary);
  ofstream output(outputFilename, ofstream::binary);

  while (!input.eof() || !input.is_open()) {
    char readbyte;
    input.read(&readbyte, 1);

    if (readbyte == 0x20) {
      char remaining[7];
      char testcase[7] = { 0x00, 0x20, 0x00, 0x00, 0x10, 0x00, 0x00 };
      input.read(remaining, 7);
      if (strncmp(remaining, testcase, 7) == 0) {
        incrementFilename(outputFilename);
        output.close();
        output.open(outputFilename, ofstream::binary);
      } else {
        output.write(&readbyte, 1);
        output.write(remaining, 7);
      }
    } else {
      output.write(&readbyte, 1);
    }
  }

  return 0;
}

你的问题可以通过实施一个简单的 finite state machine 来解决,因为你没有长条件。您将读取由空格分隔的十六进制值,并一一检查值是否符合您的条件。如果它匹配创建一个新文件继续流程,如果不写入你已经读取到当前文件。这是解决方案,可以通过更改循环来优化读取部分。

(假设输入文件名为 input.txt)

#include <fstream>
#include <sstream>

using namespace std;

void writeChunk(ostream& output, int value) {
    if (value == 0)
        output << "00" << " ";
    else
        output << hex << value << " ";
}

bool readNext(fstream& input, int& value, stringstream* keep = NULL) {
    if (input.eof()) {
        return false;
    } else {
        input >> hex >> value;
        if (keep != NULL)
            writeChunk(*keep, value);
        return true;
    }
}

string getFileName(int count) {
    stringstream fileName;
    fileName << count << ".txt";
    return fileName.str();
}

int main() {
    int fileCount = 1;
    stringstream fileName;
    fstream inputFile, outputFile;

    inputFile.open("input.txt");
    outputFile.open(getFileName(fileCount), ios::out);

    int hexValue;
    while (readNext(inputFile, hexValue)) {
        // It won't understand eof until an unsuccessful read, so double checking 
        if (inputFile.eof())
            break;

        if (hexValue == 0x20) {
            stringstream ifFails;
            ifFails << "20 ";
            if (readNext(inputFile, hexValue, &ifFails) && hexValue == 0x00 &&
                    readNext(inputFile, hexValue, &ifFails) && hexValue == 0x20 &&
                    readNext(inputFile, hexValue, &ifFails) && hexValue == 0x00 &&
                    readNext(inputFile, hexValue, &ifFails) && hexValue == 0x00 &&
                    readNext(inputFile, hexValue, &ifFails) && hexValue == 0x10 &&
                    readNext(inputFile, hexValue, &ifFails) && hexValue == 0x00 &&
                    readNext(inputFile, hexValue, &ifFails) && hexValue == 0x00) {
                outputFile.close();
                outputFile.open(getFileName(++fileCount), ios::out);
                continue;
            }
            outputFile << ifFails.str();
        } else {
            writeChunk(outputFile, hexValue);
        }
    }

    return 1;
}

您也可以为此使用分词器: 首先将 "myfile" 读入字符串。这是必需的,因为在一个文件上你只能有前向迭代器,但正则表达式需要一个双向迭代器:

auto const& str(dynamic_cast<ostringstream&> (ostringstream().operator<<(ifstream("myfile").rdbuf())).str());

然后你需要一个分割模式,extended '.'也匹配换行符:

auto const& re(regex(".?20.00.20.00.00.10.00.00.?", regex_constants::extended));

最后迭代标记化字符串并将其写入文件0.txt等等。

auto i(0u);
for_each(sregex_token_iterator(str.cbegin(), str.cend(), re, -1),
         sregex_token_iterator(),
         [&i] (string const& s) {ofstream(to_string(i++) + ".txt") << s; });

请注意输出文件没有完全格式化,它们看起来像 1.txt:

55 73 A2 FF
38 5D 9C FF 3A 5E 95 FF

只是没有分隔符的内容。