У меня есть большой кусок данных (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) — это разделение между каждым разделом данных (изображения).
Файл myfile
в том числе весь hexdump выглядит примерно так
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, и поместить каждую часть в текстовый файл как 1.txt, 2.txt … n.txt
Я пробовал читать построчно, но это вызывает некоторые проблемы, потому что часть 20 00 .. может быть найдена в 2 строки в некоторых случаях, как в примере выше, поэтому она не будет встречаться каждый раз.
while (getline(myfile,line,'\n')){
if (line == "20 00 20 00 00 10 00 00")
...
}
Определенно сохраните файл в двоичном формате и дамп фактических шестнадцатеричных байтов, в отличие от текстовой формы. Вы сэкономите в 3 раза больше места, а реализация для чтения файлов будет проще писать.
При этом, если ваш файл в двоичном формате, это решение:
#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;
}
Мое предложение состоит в том, чтобы прочитать двоичный файл. Если он достаточно мал, вы можете прочитать все это в памяти за один раз, в противном случае я предлагаю вам использовать операционную систему для отобразить файл в память (или хотя бы «окно» этого).
Тогда довольно легко найти 8-байтовую последовательность, разделяющую записи. Сначала просто ищите 0x20
и всякий раз, когда это найдено, вы видите, является ли это началом всей последовательности разделителей.
Когда вы находите последовательность разделителей, вы берете сохраненную позицию предыдущего разделителя, и позицию вновь найденного разделителя, и данные между ними — это данные, которые вы хотите. Сохраните позицию вновь найденного разделителя как старую позицию и продолжите поиск следующего разделителя.
Вот мое решение. Это немного неэффективно, но я могу переписать это, как только я закончу свои финалы. Я предполагаю, что есть байты данных, разделенные пробелом. Тогда проблема довольно проста -> это просто проблема сопоставления с образцом. Я мог бы использовать некоторые сложные методы, чтобы справиться с этим, но наш шаблон имеет фиксированный размер, который довольно мал. Даже метод грубой силы будет иметь линейное время.
Код не требует пояснений. Я читаю файл побайтово и добавляю его в буфер (не слишком эффективный, он может хранить в файле только окно данных с границами индекса -> это может обеспечить более эффективные операции ввода-вывода при создании новых файлов). Как только завершающая последовательность найдена, мы извлекаем ее и сохраняем в файл (я предположил, что нам не нужны пустые файлы).
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;
}
Учитывая, что фактическая последовательность данных, к которой вы стремитесь, потенциально разбита на строки, вам необходимо прочитать данные в наименьшем «кусочке», который вы можете — двухсимвольные массивы, и игнорировать пробелы (разделители пробела или новой строки).
Как только вы это сделаете, вы можете отслеживать то, что вы прочитали, когда вы пишете это в свой подфайл. Как только вы получите свою «волшебную последовательность», запустите новый подфайл.
Две сложности, которые вы не покрываете:
Одно из преимуществ в этом случае: если вложенный файл, хотя он все еще находится внутри основного файла, начинается ближе к концу строки, он начинается с новой строки и разбивается после 16 двух символов, а не подражает своей позиции в основной файл. Или вы хотели выводить субфайлы в истинных байтах, без разделителей пробелов?
Я собираюсь уйти и написать эту программу: это звучит как весело!
ОК, я написал следующее. Надеюсь, использование описывает, что делать. Я не особо хотел использовать потоки — я нахожу их ужасно неэффективными — но вы начали это …
//
// 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)
Всякий раз, когда кто-то публикует код, всегда остаются комментарии:
«Почему ты не сделал это?»
«Почему ты это сделал?»
Пусть откроются шлюзы!
Я бы пошел с 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-12 МБ и сохраняете его как файл.
Ваша проблема может быть решена путем реализации простого конечный автомат так как у вас нет длительного состояния. Вы будете читать шестнадцатеричные значения, разделенные пробелами, и проверять значения одно за другим, если они соответствуют вашим критериям. Если это соответствует, создайте новый файл, продолжайте поток, если не записали, вы прочитали текущий файл Вот решение, часть чтения может быть оптимизирована путем изменения цикла.
(предполагается, что имя входного файла — 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
Это просто содержимое без разделителя.