"#include" текстовый файл в программе C как char []
Есть ли способ включить весь текстовый файл в виде строки в программе C во время компиляции?
что-то вроде:
-
file.txt:
This is
a little
text file
-
main.c:
#include <stdio.h>
int main(void) {
#blackmagicinclude("file.txt", content)
/*
equiv: char[] content = "This is\na little\ntext file";
*/
printf("%s", content);
}
получить небольшую программу, которая печатает на stdout "Это
маленький
текстовый файл "
В настоящий момент я использовал хакерский python script, но он прикладом - уродливым и ограниченным только одним именем переменной, можете ли вы сказать мне еще один способ сделать это?
Ответы
Ответ 1
Я бы предложил использовать (unix util) xxd для этого.
вы можете использовать его так
$ echo hello world > a
$ xxd -i a
выходы:
unsigned char a[] = {
0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x0a
};
unsigned int a_len = 12;
Ответ 2
Вопрос был о C, но если кто-то попытается сделать это с С++ 11, то это может быть сделано с небольшими изменениями в включенном текстовом файле благодаря новому raw строковые литералы:
В С++ выполните следующее:
const char *s =
#include "test.txt"
;
В текстовом файле сделайте следующее:
R"(Line 1
Line 2
Line 3
Line 4
Line 5
Line 6)"
Таким образом, в верхней части файла должен быть только префикс и суффикс в конце его. Между ними вы можете делать то, что хотите, не требуется специального экранирования, если вам не нужна последовательность символов )"
. Но даже это может работать, если вы укажете свой собственный разделитель:
R"=====(Line 1
Line 2
Line 3
Now you can use "( and )" in the text file, too.
Line 5
Line 6)====="
Ответ 3
У вас есть две возможности:
- Используйте расширения компилятора/компоновщика для преобразования файла в двоичный файл с соответствующими символами, указывающими на начало и конец двоичных данных. См. Этот ответ: Включить двоичный файл с помощью GNU ld linker script.
- Преобразуйте файл в последовательность символьных констант, которые могут инициализировать массив. Обратите внимание, что вы не можете просто делать "" и охватывать несколько строк. Для этого вам понадобится символ продолжения строки (
\
), escape "
и другие. Легче просто написать небольшую программу для преобразования байтов в последовательность, например '\xFF', '\xAB', ...., '\0'
(или использовать инструмент unix xxd
, описанный другим ответом, если он у вас есть!):
код:
#include <stdio.h>
int main() {
int c;
while((c = fgetc(stdin)) != EOF) {
printf("'\\x%X',", (unsigned)c);
}
printf("'\\0'"); // put terminating zero
}
(не проверено). Затем выполните:
char my_file[] = {
#include "data.h"
};
Где data.h генерируется
cat file.bin | ./bin2c > data.h
Ответ 4
ok, вдохновленный сообщением Daemin, я протестировал следующий простой пример:
a.data:
"this is test\n file\n"
test.c:
int main(void)
{
char *test =
#include "a.data"
;
return 0;
}
gcc -E test.c:
# 1 "test.c"
# 1 "<built-in>"
# 1 "<command line>"
# 1 "test.c"
int main(void)
{
char *test =
# 1 "a.data" 1
"this is test\n file\n"
# 6 "test.c" 2
;
return 0;
}
Таким образом, он работает, но требует данных, окруженных кавычками.
Ответ 5
Мне нравится ответ на кайар. Если вы не хотите прикасаться к входным файлам, но если вы используете CMake, вы можете добавить последовательности символов деления в файл. Следующий код CMake, например, копирует входные файлы и соответственно переносит их содержимое:
function(make_includable input_file output_file)
file(READ ${input_file} content)
set(delim "for_c++_include")
set(content "R\"${delim}(\n${content})${delim}\"")
file(WRITE ${output_file} "${content}")
endfunction(make_includable)
# Use like
make_includable(external/shaders/cool.frag generated/cool.frag)
Затем включите в С++, как это:
constexpr char *test =
#include "generated/cool.frag"
;
Ответ 6
What может сработать, если вы сделаете что-то вроде:
int main()
{
const char* text = "
#include "file.txt"
";
printf("%s", text);
return 0;
}
Конечно, вам нужно быть осторожным с тем, что на самом деле находится в файле, следя за тем, чтобы не было двойных кавычек, чтобы все соответствующие символы экранировались и т.д.
Поэтому может быть проще, если вы просто загрузите текст из файла во время выполнения или внедрите текст непосредственно в код.
Если вам все еще нужен текст в другом файле, вы можете поместить его там, но он должен быть представлен в виде строки. Вы должны использовать код, как указано выше, но без двойных кавычек. Например:
file.txt
"Something evil\n"\
"this way comes!"
main.cpp
int main()
{
const char* text =
#include "file.txt"
;
printf("%s", text);
return 0;
}
То есть, в основном, в текстовом файле, который вы включаете, есть строка в стиле C или C++. Это сделает код более аккуратным, потому что в начале файла нет такого большого количества текста.
Ответ 7
Вам нужна моя утилита xtr
, но вы можете сделать ее с помощью bash script
. Это script я вызываю bin2inc
. Первым параметром является имя результирующего char[] variable
. Второй параметр - это имя file
. Выводится C include file
с содержимым содержимого, закодированным (в нижнем регистре hex
) в качестве имени переменной. char array
zero terminated
, а длина данных сохраняется в $variableName_length
#!/bin/bash
fileSize ()
{
[ -e "$1" ] && {
set -- `ls -l "$1"`;
echo $5;
}
}
echo unsigned char $1'[] = {'
./xtr -fhex -p 0x -s ', ' < "$2";
echo '0x00'
echo '};';
echo '';
echo unsigned long int ${1}_length = $(fileSize "$2")';'
ВЫ МОЖЕТЕ ПОЛУЧИТЬ XTR ЗДЕСЬ xtr (символ eXTRapolator) - это GPLV3
Ответ 8
Вы можете сделать это, используя objcopy
:
objcopy --input binary --output elf64-x86-64 myfile.txt myfile.o
Теперь у вас есть объектный файл, который вы можете связать с вашим исполняемым файлом, который содержит символы для начала, конца и размера содержимого из myfile.txt
.
Ответ 9
Если вы готовы прибегнуть к некоторым грязным трюкам, вы можете проявить творческий подход с необработанными строковыми литералами и #include
для определенных типов файлов.
Например, скажем, я хочу включить некоторые сценарии SQL для SQLite в свой проект и хочу получить подсветку синтаксиса, но не хочу никакой специальной инфраструктуры сборки. У меня может быть этот файл test.sql
, который является допустимым SQL для SQLite, где --
начинает комментарий:
--x, R"(--
SELECT * from TestTable
WHERE field = 5
--)"
А потом в моем коде C++ я могу иметь:
int main()
{
auto x = 0;
const char* mysql = (
#include "test.sql"
);
cout << mysql << endl;
}
Вывод:
--
SELECT * from TestTable
WHERE field = 5
--
Или добавить некоторый код Python из файла test.py
, который является допустимым скриптом Python (потому что #
запускает комментарий в Python, а pass
не используется):
#define pass R"(
pass
def myfunc():
print("Some Python code")
myfunc()
#undef pass
#define pass )"
pass
А затем в коде C++:
int main()
{
const char* mypython = (
#include "test.py"
);
cout << mypython << endl;
}
Который будет выводить:
pass
def myfunc():
print("Some Python code")
myfunc()
#undef pass
#define pass
Должна быть возможность воспроизвести похожие трюки для других типов кода, которые вы можете включить в строку. Является ли это хорошей идеей, я не уверен. Это своего рода аккуратный взлом, но, вероятно, не то, что вы хотели бы в реальном производственном коде. Возможно, все будет хорошо для хакерского проекта на выходных.
Ответ 10
Я переопределял xxd в python3, исправляя все раздражения xxd:
- Корректность
- string length тип данных: int → size_t
- Нулевое завершение (в случае, если вы этого захотите)
- C string compatible: Drop
unsigned
в массиве. - Меньший, читаемый вывод, как вы бы его написали: Печатная версия ascii выводится как есть; другие байты кодируются в шестнадцатеричном виде.
Вот сценарий, отфильтрованный сам по себе, чтобы вы могли видеть, что он делает:
pyxxd.c
#include <stddef.h>
extern const char pyxxd[];
extern const size_t pyxxd_len;
const char pyxxd[] =
"#!/usr/bin/env python3\n"
"\n"
"import sys\n"
"import re\n"
"\n"
"def is_printable_ascii(byte):\n"
" return byte >= ord(' ') and byte <= ord('~')\n"
"\n"
"def needs_escaping(byte):\n"
" return byte == ord('\\\"') or byte == ord('\\\\')\n"
"\n"
"def stringify_nibble(nibble):\n"
" if nibble < 10:\n"
" return chr(nibble + ord('0'))\n"
" return chr(nibble - 10 + ord('a'))\n"
"\n"
"def write_byte(of, byte):\n"
" if is_printable_ascii(byte):\n"
" if needs_escaping(byte):\n"
" of.write('\\\\')\n"
" of.write(chr(byte))\n"
" elif byte == ord('\\n'):\n"
" of.write('\\\\n\"\\n\"')\n"
" else:\n"
" of.write('\\\\x')\n"
" of.write(stringify_nibble(byte >> 4))\n"
" of.write(stringify_nibble(byte & 0xf))\n"
"\n"
"def mk_valid_identifier(s):\n"
" s = re.sub('^[^_a-z]', '_', s)\n"
" s = re.sub('[^_a-z0-9]', '_', s)\n"
" return s\n"
"\n"
"def main():\n"
" # 'xxd -i' compatibility\n"
" if len(sys.argv) != 4 or sys.argv[1] != \"-i\":\n"
" print(\"Usage: xxd -i infile outfile\")\n"
" exit(2)\n"
"\n"
" with open(sys.argv[2], \"rb\") as infile:\n"
" with open(sys.argv[3], \"w\") as outfile:\n"
"\n"
" identifier = mk_valid_identifier(sys.argv[2]);\n"
" outfile.write('#include <stddef.h>\\n\\n');\n"
" outfile.write('extern const char {}[];\\n'.format(identifier));\n"
" outfile.write('extern const size_t {}_len;\\n\\n'.format(identifier));\n"
" outfile.write('const char {}[] =\\n\"'.format(identifier));\n"
"\n"
" while True:\n"
" byte = infile.read(1)\n"
" if byte == b\"\":\n"
" break\n"
" write_byte(outfile, ord(byte))\n"
"\n"
" outfile.write('\";\\n\\n');\n"
" outfile.write('const size_t {}_len = sizeof({}) - 1;\\n'.format(identifier, identifier));\n"
"\n"
"if __name__ == '__main__':\n"
" main()\n"
"";
const size_t pyxxd_len = sizeof(pyxxd) - 1;
Использование (это извлекает скрипт):
#include <stdio.h>
extern const char pyxxd[];
extern const size_t pyxxd_len;
int main()
{
fwrite(pyxxd, 1, pyxxd_len, stdout);
}
Ответ 11
Даже если это можно сделать во время компиляции (я не думаю, что это вообще возможно), скорее всего, текст будет предварительно обработанным заголовком, а не содержимым файлов дословно. Я ожидаю, что вам придется загружать текст из файла во время выполнения или делать неприятную работу с вырезанием n-пасты.
Ответ 12
Ответ Hasturkun с использованием опции xxd -i превосходный. Если вы хотите включить процесс преобразования (текст → файл с шестнадцатеричным включением) непосредственно в свою сборку, то инструмент/библиотека hexdump.c недавно добавила возможность, аналогичную опции xxd -i (она не дает вам полный заголовок - вам нужно для обеспечения определения массива char, но это имеет то преимущество, что позволяет выбрать имя массива char):
http://25thandclement.com/~william/projects/hexdump.c.html
Эта лицензия намного более "стандартная", чем xxd, и очень либеральная - пример ее использования для вставки файла init в программу можно увидеть здесь в файлах CMakeLists.txt и schem.c:
https://github.com/starseeker/tinyscheme-cmake
Есть плюсы и минусы как для сгенерированных файлов в исходных деревьях, так и для комплектации утилит - как их обрабатывать будет зависеть от конкретных целей и потребностей вашего проекта. hexdump.c открывает опцию связывания для этого приложения.
Ответ 13
Я думаю, что это невозможно с компилятором и препроцессором. gcc позволяет это:
#define _STRGF(x) # x
#define STRGF(x) _STRGF(x)
printk ( MODULE_NAME " built " __DATE__ " at " __TIME__ " on host "
STRGF(
# define hostname my_dear_hostname
hostname
)
"\n" );
Но, к сожалению, это не так:
#define _STRGF(x) # x
#define STRGF(x) _STRGF(x)
printk ( MODULE_NAME " built " __DATE__ " at " __TIME__ " on host "
STRGF(
# include "/etc/hostname"
)
"\n" );
Ошибка:
/etc/hostname: In function ‘init_module’:
/etc/hostname:1:0: error: unterminated argument list invoking macro "STRGF"
Ответ 14
Почему бы не связать текст с программой и использовать ее как глобальную переменную! Вот пример. Я рассматриваю возможность использования этого для включения файлов шейдера Open GL в исполняемый файл, поскольку GL-шейдеры должны быть скомпилированы для GPU на во время выполнения.
Ответ 15
У меня были похожие проблемы, и для небольших файлов вышеупомянутое решение Йоханнеса Шауба работало как прелесть для меня.
Однако для файлов, которые немного больше, он столкнулся с проблемами с ограничением символьного массива компилятора. Поэтому я написал небольшое приложение кодировщика, которое преобразует содержимое файла в массив двумерных символов с одинаковыми размерами (и, возможно, нулями заполнения). Он создает выходные текстовые файлы с данными 2D-массива следующим образом:
const char main_js_file_data[8][4]= {
{'\x69','\x73','\x20','\0'},
{'\x69','\x73','\x20','\0'},
{'\x61','\x20','\x74','\0'},
{'\x65','\x73','\x74','\0'},
{'\x20','\x66','\x6f','\0'},
{'\x72','\x20','\x79','\0'},
{'\x6f','\x75','\xd','\0'},
{'\xa','\0','\0','\0'}};
где 4 - фактически переменная MAX_CHARS_PER_ARRAY в кодере. Файл с результирующим C-кодом, называемым, например, "main_js_file_data.h", может быть легко интегрирован в приложение С++, например, следующим образом:
#include "main_js_file_data.h"
Вот исходный код кодировщика:
#include <fstream>
#include <iterator>
#include <vector>
#include <algorithm>
#define MAX_CHARS_PER_ARRAY 2048
int main(int argc, char * argv[])
{
// three parameters: input filename, output filename, variable name
if (argc < 4)
{
return 1;
}
// buffer data, packaged into chunks
std::vector<char> bufferedData;
// open input file, in binary mode
{
std::ifstream fStr(argv[1], std::ios::binary);
if (!fStr.is_open())
{
return 1;
}
bufferedData.assign(std::istreambuf_iterator<char>(fStr),
std::istreambuf_iterator<char>() );
}
// write output text file, containing a variable declaration,
// which will be a fixed-size two-dimensional plain array
{
std::ofstream fStr(argv[2]);
if (!fStr.is_open())
{
return 1;
}
const std::size_t numChunks = std::size_t(std::ceil(double(bufferedData.size()) / (MAX_CHARS_PER_ARRAY - 1)));
fStr << "const char " << argv[3] << "[" << numChunks << "]" <<
"[" << MAX_CHARS_PER_ARRAY << "]= {" << std::endl;
std::size_t count = 0;
fStr << std::hex;
while (count < bufferedData.size())
{
std::size_t n = 0;
fStr << "{";
for (; n < MAX_CHARS_PER_ARRAY - 1 && count < bufferedData.size(); ++n)
{
fStr << "'\\x" << int(unsigned char(bufferedData[count++])) << "',";
}
// fill missing part to reach fixed chunk size with zero entries
for (std::size_t j = 0; j < (MAX_CHARS_PER_ARRAY - 1) - n; ++j)
{
fStr << "'\\0',";
}
fStr << "'\\0'}";
if (count < bufferedData.size())
{
fStr << ",\n";
}
}
fStr << "};\n";
}
return 0;
}
Ответ 16
в x.h
"this is a "
"buncha text"
в main.c
#include <stdio.h>
int main(void)
{
char *textFileContents =
#include "x.h"
;
printf("%s\n", textFileContents);
return 0
}
должен выполнить эту работу.