Cプログラム内のテキストファイルをchar []として「#include」


130

コンパイル時に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スクリプトを使用しましたが、お尻が醜く、変数名は1つだけに制限されています。別の方法で教えてもらえますか?


char []にファイルを読み込む方法については、こちらをご覧ください。/programming/410943/reading-a-text-file-into-an-array-in-c以下は、Cプリプロセッサのマクロを使用するためのヒントです。http://gcc.gnu.org/onlinedocs/cpp/Macros.html
Daniel A. White

3
なぜこれをしたいのですか?実行時にファイルを読み取らないのはなぜですか?(回答:実行時にファイルがどこにあるかを知るのが難しいためか、インストールするファイルが1つしかないためかもしれません。)
Jonathan Leffler

または、テキストファイルは、ソースコードなど、コンパイル時にのみ使用できます。
TMS

1
開発時に個別のファイルとしてデータにアクセスしたいが、内容をバイナリにコンパイルしたい場合があります。例は、ローカルストレージにアクセスできないArduinoでWebサーバーを実行している場合です。あなたはそれらを編集するためにhtmlファイルを別々に保ちたいが、コンパイル時にそれらはあなたのソースに文字列として存在する必要があります。
ジョーディー

回答:


134

これには(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;

18
注:xxdによって作成されたchar []はNULLで終了していません!$ xxd -i <file.txt> file.xxd $ echo '、0' >> file.xxdおよびmain.c char file_content [] = {#include "file.xxd"};

2
私はxxdについて知りませんでした。それは素晴らしいです!

1
@eSKay:xxd答えが言うように、それはの出力から直接得られます。配列の名前は入力ファイル名です。入力ファイルを使用する代わりにデータをパイプ処理する場合は、代わりに(配列宣言やlen変数なしで)16進値のリストを取得します。
Hasturkun 2010年

4
これは、GLSLシェーダーを埋め込むときに非常に役立ちます。
リネロ2016年

5
xxdで生成されたCコードに0x00終端を追加する別の方法:xxd -i file.txt | sed 's/\([0-9a-f]\)$/\0, 0x00/' > file.h
vleo

104

問題は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)====="

5
おかげで、SQLの長いフラグメントをC ++ 11コードに埋め込むためにここで提案された方法を選択しました。これは私がcleantly独自のファイルに分割SQLを維持することができ、および編集、それらを適切な構文チェックと、ハイライトなど
YitzikC

1
これは本当に私が欲しいものに近いです。特にユーザー定義の区切り文字。非常に便利。私はさらに一歩踏み出したいと思います:含めたいファイルから接頭辞R "(および接尾辞)"を完全に削除する方法はありますか?プレフィックスとサフィックスを含むbra.inとket.inという2つのファイルを定義し、bra.in、file.txt、およびket.inを1つずつ含めてみました。しかし、コンパイラは、前に次のファイルを含める() "だけRでbra.in(の内容を評価し、それは文句を言われますので、誰でもfile.txtをから接頭辞と接尾辞の乗り心地を取得する方法を知っている場合は私に知らせてください感謝。。。。
TMS

C ++はR "(<newline> #include ...)"を許可しないと思いますか?すなわちストレートJSONまたはXMLまたはCSVまたはものではありません... ....ファイルは任意の一切エンコードを必要としないように、コンパイル時摂取されて持っていいだろう
ブライアンChrisman

1+R"...代わりに開始区切り文字として使用する場合、生のリテラルのテキストをもう少し読みやすくしてからR"...、前に改行を追加できますLine 1。これにより、式が配列からポインターに変換されますが、配列ではなくポインターを初期化しているため、ここでは実際には問題になりません。
ルスラン

14

次の2つの可能性があります。

  1. コンパイラー/リンカー拡張を使用して、適切なシンボルがバイナリー・データの開始と終了を指すようにして、ファイルをバイナリー・ファイルに変換します。この回答を参照してください:GNU ldリンカースクリプトでバイナリファイルを含めます
  2. ファイルを、配列を初期化できる文字定数のシーケンスに変換します。「」を実行して複数行にまたがることはできません。これを機能させるには、行継続文字(\)、エスケープ"文字などが必要です。バイトをシーケンスに変換する小さなプログラムを書くだけの方が簡単です'\xFF', '\xAB', ...., '\0'(または、xxd利用できる場合は、別の回答で説明されているunixツールを使用してください)。

コード:

#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

1
最終行はおそらく「cat file.bin | ./bin2c> data.h」または「./bin2c <file.bin> data.h」
Hasturkun

codeproject.com/Tips/845393/…を使用して、バイナリから(Windowsで)hexファイルを作成し、提案のchar my_file[] = { #include my_large_file.h };おかげでありがとう!
誰かどこか

bin2cdebianのものと同じbin2cではありませんhxtools、注意してください
ThorSummoner

または、そうであれば、呼び出しは今ではかなり奇妙です:bin2c -H myoutput.h myinput1.txt myinputN.txt
ThorSummoner

9

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

したがって、機能しますが、引用符で囲まれたデータが必要です。


それが私の答えの最後の部分で私がほのめかしていたことです。
Daemin

引用、またはそれが何と呼ばれていようとも、私の英語を許してください
Ilya

これには、データをCエスケープする必要があります。私はそれがポストが探しているものではないと思います。これに、ファイルのコンテンツをCエスケープする何らかのインクルードマクロがあった場合は、問題ありません。
ブライアンクリスマン

8

カヤーの答えが好きです。ただし、入力ファイルに触れたくない場合、および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"
;

5

あなたはこれを使用してこれを行うことができますobjcopy

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

これで、実行可能ファイルにリンクできるオブジェクトファイルができました。これには、のコンテンツの先頭、末尾、およびサイズのシンボルが含まれていますmyfile.txt


1
シンボル名を教えていただけますか?
Mark Ch

@MarkCh:ドキュメントに従って、シンボル名は入力ファイル名から生成されます。
John Zwinck

これはx86-64以外のマシンでは機能しないと思いますか?
ThorSummoner


2

私のxtrユーティリティが必要ですが、でそれを行うことができますbash script。これは私が呼ぶスクリプトですbin2inc。最初のパラメータは、結果のの名前ですchar[] variable。2番目のパラメータはの名前です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です


2

あなたがいくつかの汚いトリックに頼るつもりなら、あなたは生の文字列リテラルと#include特定のタイプのファイルのために創造的になることができます。

たとえば、プロジェクトにSQLiteのSQLスクリプトをいくつか含め、構文を強調表示したいが、特別なビルドインフラストラクチャを必要としないとします。コメントを開始するtest.sqlSQLiteの有効なSQLであるこのファイルを使用できます--

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

またはtest.py、有効なPythonスクリプトであるファイルからいくつかの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

文字列として含めることができる他のさまざまな種類のコードについても、同様のトリックを実行できるはずです。それが良い考えかどうかはわかりません。それは一種のきちんとしたハックですが、おそらく実際の本番用コードに必要なものではないでしょう。週末のハックプロジェクトでも大丈夫かもしれません。


このアプローチを使用して、OpenGLシェーダーをテキストファイルにも挿入しました!
yano

1

私はxxdをpython3に再実装し、xxdの煩わしさをすべて修正しました。

  • 定数の正確さ
  • 文字列長のデータ型:int→size_t
  • ヌル終了(必要に応じて)
  • C文字列互換:unsigned配列にドロップします。
  • あなたが書いたのと同じように、小さくて読みやすい出力:印刷可能なasciiはそのまま出力されます。その他のバイトは16進数でエンコードされます。

これはスクリプト自体であり、それ自体でフィルタリングされているため、スクリプトの機能を確認できます。

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

1

かもしれない仕事あなたのような何かを行う場合は次のとおりです。

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 ++スタイルの文字列を含めます。ファイルの先頭にこのような大量のテキストがないため、コードがすっきりします。


3
いいアイデアですが、リテラルに改行が含まれているためにエラーが発生するか、#include部分が文字列として読み取られて実行されず、実行した場合は非表示になり、そうでない場合は非表示になります。 。
Motti

1
@Motti:合意-記述どおり、構文的に無効なC.アイデアは興味深い-Cプリプロセッサは論理的に別のフェーズですが、インクルードされたファイルの各行にはバックスラッシュなどで終わらせる
ジョナサン・レフラー

2
うーん。ほとんどのコンパイラは隣接する文字列を連結するので、バックスラッシュは必要ないようです
EvilTeach

この答えのことは...それが簡単だったら、OPがこれまでに質問したことはないと思います!-1この答えの存在は、うまくいかないものを試すために時間を無駄にすることを人々にわずかに奨励しているためです。「参考までに、これは機能しない」に変更した場合、反対票を削除できると思います
Mark Ch

@JonathanLefflerプリプロセッサの実行後、file.txtのフォーマット方法に応じて、CまたはC ++が有効になります。
Daemin

0

コンパイル時に実行できる場合でも(一般的には実行できないと思います)、テキストはファイルの内容をそのままではなく、前処理されたヘッダーになる可能性があります。実行時にファイルからテキストをロードするか、厄介なカットアンドペーストジョブを実行する必要があると思います。


0

xxd -iオプションを使用したHasturkunの回答は優れています。変換プロセス(テキスト-> hexインクルードファイル)をビルドに直接組み込みたい場合は、hexdump.cツール/ライブラリにxxdの-iオプションと同様の機能が最近追加されました(完全なヘッダーが表示されません-必要です) char配列の定義を提供する-しかし、char配列の名前を選択できるという利点があります):

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

このライセンスはxxdよりもはるかに「標準的」であり、非常に自由です。これを使用してinitファイルをプログラムに埋め込む例は、CMakeLists.txtおよびscheme.cファイルで確認できます。

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

ソースツリーとバンドルユーティリティに生成されたファイルを含めることには長所と短所があります-それを処理する方法はプロジェクトの特定の目標とニーズに依存します。hexdump.cは、このアプリケーションのバンドルオプションを開きます。


0

コンパイラとプリプロセッサだけでは不可能だと思います。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"

あなたが私に見えるように、私は見ました。/etc/hostnameビルドマシンの名前を文字列に埋め込む方法としての参照を超えて、あなたの回答に新しい情報(他の回答にはない情報)はありません。 Mac OS Xにはファイルがないため、移植可能です/etc/hostname。アンダースコアで始まり、その後に大文字が続くマクロ名を使用すると、実装に予約された名前、A Bad Thing™を使用することに注意してください。
ジョナサンレフラー

0

テキストをプログラムにリンクして、グローバル変数として使用してみませんか?例を示します。実行時にGLシェーダーをGPU用にコンパイルする必要があるため、これを使用してOpen GLシェーダーファイルを実行可能ファイルに含めることを検討しています。


0

私にも同様の問題があり、小さなファイルの場合、前述のヨハネスシャウブの解決策は私にとって魅力的なものとして機能しました。

ただし、少し大きいファイルの場合、コンパイラの文字配列の制限に関する問題が発生しました。したがって、ファイルのコンテンツを同じサイズのチャンク(および場合によってはゼロを埋め込む)の2D文字配列に変換する小さなエンコーダーアプリケーションを作成しました。次のような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です。次に、「main_js_file_data.h」などと呼ばれる結果のCコードを含むファイルを、たとえば次のように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;
}

0

この問題は私を苛立たせ、xxdは私のユースケースでは機能しません。なぜなら、スクリプトを作成しようとしたときに__home_myname_build_prog_cmakelists_src_autogenのような変数が呼び出されたため、この正確な問題を解決するユーティリティを作成しました。

https://github.com/Exaeta/brcc

ソースとヘッダーファイルを生成し、各変数の名前を明示的に設定して、std :: begin(arrayname)とstd :: end(arrayname)を介してそれらを使用できるようにします。

私はそれを私のようにcmakeプロジェクトに組み込んだ:

add_custom_command(
  OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/binary_resources.hpp ${CMAKE_CURRENT_BINARY_DIR}/binary_resources.cpp
  COMMAND brcc ${CMAKE_CURRENT_BINARY_DIR}/binary_resources RGAME_BINARY_RESOURCES_HH txt_vertex_shader ${CMAKE_CURRENT_BINARY_DIR}/src/vertex_shader1.glsl
  DEPENDS src/vertex_shader1.glsl)

小さな調整で、Cでも動作するようにできると思います。


-1

XHで

"this is a "
"buncha text"

main.c

#include <stdio.h>
int main(void)
{
    char *textFileContents =
#include "x.h"
    ;

    printf("%s\n", textFileContents);

    return 0
}

仕事をすべきです。


複数行の場合は追加する必要があります\ nそう: "行1 \ n" "行2 \ n"
Superfly Jon

少し誤解を招きやすいですが、引用符と文字を追加するためにテキストファイルを準備する必要があり、\ n一般的なケースでは機能しません
Mark Ch
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.