"#include " текстовый файл в программе на C как символ[]


есть ли способ включить весь текстовый файл в виде строки в программу на C во время компиляции?

что-то типа:

  • .txt:
    This is
    a little
    text file
    
  • главная.c:

    #include <stdio.h>
    int main(void) {
       #blackmagicinclude("file.txt", content)
       /*
       equiv: char[] content = "This isna littlentext file";
       */
       printf("%s", content);
    }
    

получение небольшой программы, которая печатает на stdout " это немного текстовый файл"

на данный момент я использовал хакерский скрипт python, но он уродлив и ограничен только одним именем переменной, можете ли вы мне сказать другой способ сделать это?

15 100

15 ответов:

Я бы предложил использовать (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;

вопрос был о C, но если кто-то пытается сделать это с C++11, то это можно сделать только с небольшими изменениями в включенном текстовом файле благодаря новому "сырые" строковые литералы:

В C++ сделать так:

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)====="

у вас есть две возможности:

  1. используйте расширения компилятора/компоновщика для преобразования файла в двоичный файл с соответствующими символами, указывающими на начало и конец двоичных данных. Смотрите этот ответ:включить двоичный файл с GNU ld linker script.
  2. преобразуйте файл в последовательность символьных констант, которые могут инициализировать массив. Обратите внимание, что вы не можете просто сделать "" и охватить несколько строк. Вам понадобится символ продолжения строки (\), побег " символы и другие, чтобы сделать эту работу. Проще просто написать небольшую программу для преобразования байтов в последовательность типа '\xFF', '\xAB', ...., '' (или используйте инструмент 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"
};

где данные.h генерируется с помощью

cat file.bin | ./bin2c > data.h

ОК, вдохновленный Дэмина сообщение Я проверил следующий простой пример:

а.данные:

"this is test\n file\n"

что может сработать, если вы сделаете что-то вроде:

int main()
{
    const char* text = "
#include "file.txt"
";
    printf("%s", text);
    return 0;
}

конечно, вы должны быть осторожны с тем, что на самом деле в файл, убедившись, что нет двойных кавычек, что все соответствующие символы не экранируются, и т. д.

Если вы все еще хотите текст в другом файле, вы можете иметь его там, но это должно быть представлен там в виде строки. Вы бы использовали код, как указано выше, но без двойных кавычек в нем. Например:

"Something evil\n"\
"this way comes!"

int main()
{
    const char* text =
#include "file.txt"
;
    printf("%s", text);
    return 0;
}

тебе нужна моя xtr утилиты, но вы можете сделать это с помощью bash script. Это скрипт, который я называю bin2inc. Первый параметр-это имя результирующего char[] variable. Второй параметр-это имя file. На выходе с include file с закодированным содержимым файла (в нижнем регистре hex) в качестве имени переменной. Элемент char array и zero terminated, и длина данных хранится в $variableName_length

#!/bin/bash

fileSize ()

{

    [ -e "" ]  && {

        set -- `ls -l ""`;

        echo ;

    }

}

echo unsigned char '[] = {'
./xtr -fhex -p 0x -s ', ' < "";
echo '0x00'
echo '};';
echo '';
echo unsigned long int _length = $(fileSize "")';'

ВЫ МОЖЕТЕ ПОЛУЧИТЬ XTR ЗДЕСЬ xtr (символ экстраполятор) - это GPLV3

вы можете сделать это с помощью objcopy:

objcopy --input binary --output elf64-x86-64 myfile.txt myfile.o

Теперь у вас есть объектный файл, который вы можете связать с исполняемым файлом, который содержит символы для начала, конца и размера содержимого из myfile.txt.

мне нравится ответ Кайяра. Если вы не хотите прикасаться к входным файлам однако, и если вы используете 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)

затем включите в c++ вот так:

constexpr char *test =
#include "generated/cool.frag"
;

я переделал ключам в питон3, исправить все неприятности по ключам:

  • Const корректность
  • тип данных длины строки: int → size_t
  • нулевое завершение (в случае, если вы этого хотите)
  • C строка совместима: 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);
}

даже если это можно сделать во время компиляции (я не думаю, что это вообще возможно), текст, скорее всего, будет предварительно обработанным заголовком, а не содержимым файлов дословно. Я ожидаю, что вам придется загрузить текст из файла во время выполнения или сделать неприятную работу cut-n-paste.

в 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
}

должен делать свою работу.

ответ Хастуркуна с использованием опции xxd-i превосходен. Если вы хотите включить процесс преобразования (text -> hex include file) непосредственно в вашу сборку hexdump.c tool / library недавно добавила возможность, аналогичную опции xxd-i (она не дает вам полный заголовок - Вам нужно предоставить определение массива символов - но это имеет то преимущество, что вы можете выбрать имя символа массив):

http://25thandclement.com / ~william/projects/hexdump.c.html

это лицензия намного более "стандартная", чем xxd, и очень либеральная - пример использования ее для встраивания файла init в программу можно увидеть в CMakeLists.txt и схема.c файлы здесь:

https://github.com/starseeker/tinyscheme-cmake

есть плюсы и минусы как для включения сгенерированных файлов в исходные деревья, так и для связывания утилит - как справиться с этим будет зависеть от конкретных целей и потребностей вашего проекта. шестнадцатеричного представления.c открывает опцию связывания для этого приложения.

Я думаю, что это невозможно только с компилятором и препроцессором. НКУ позволяет этого:

#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"

Почему бы не связать текст в программу и использовать его в качестве глобальной переменной! вот пример. Я рассматриваю возможность использования этого для включения открытых файлов шейдеров GL в исполняемый файл, поскольку шейдеры GL должны быть скомпилированы для GPU во время выполнения.

у меня были похожие проблемы, и для небольших файлов вышеупомянутое решение Johannes Schaub работало как шарм для меня.

однако для файлов, которые немного больше, он столкнулся с проблемами с ограничением массива символов компилятора. Поэтому я написал небольшое приложение для кодирования, которое преобразует содержимое файла в массив 2D-символов с одинаковыми размерами кусков (и, возможно, заполнение нулей). Он производит выходные текстовые файлы с данными 2D массива следующим образом:

const char main_js_file_data[8][4]= {
    {'\x69','\x73','\x20',''},
    {'\x69','\x73','\x20',''},
    {'\x61','\x20','\x74',''},
    {'\x65','\x73','\x74',''},
    {'\x20','\x66','\x6f',''},
    {'\x72','\x20','\x79',''},
    {'\x6f','\x75','\xd',''},
    {'\xa','','',''}};

где 4 на самом деле является переменной MAX_CHARS_PER_ARRAY в кодере. Файл с полученным кодом C, называется, например " main_js_file_data.затем h " можно легко встроить в приложение C++, например, так:

#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;
}