"#Include" un archivo de texto en un programa C como char []

130

¿Hay alguna manera de incluir un archivo de texto completo como una cadena en un programa C en tiempo de compilación?

algo como:

  • file.txt:

    This is
    a little
    text file
  • C Principal:

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

obteniendo un pequeño programa que imprime en stdout "Este es un pequeño archivo de texto"

Por el momento utilicé un script de Python hackish, pero es feo y está limitado a un solo nombre de variable, ¿puede decirme otra forma de hacerlo?

Brian Tompsett - 汤 莱恩
fuente
Echa un vistazo aquí para leer un archivo en un char []. /programming/410943/reading-a-text-file-into-an-array-in-c Aquí hay algunos consejos para usar las macros del preprocesador C. http://gcc.gnu.org/onlinedocs/cpp/Macros.html
Daniel A. White el
3
¿Por qué quieres hacer esto? ¿Por qué no leer el archivo en tiempo de ejecución? (Respuesta: tal vez porque es difícil saber dónde está el archivo en tiempo de ejecución, o tal vez porque solo debería haber un archivo para instalar.)
Jonathan Leffler el
o tal vez el archivo de texto solo está disponible en tiempo de compilación, como el código fuente.
TMS
1
A veces, desea acceder a los datos como archivos separados en el momento del desarrollo, pero tiene el contenido compilado en su binario. Un ejemplo es ejecutar un servidor web en un Arduino que no tiene acceso al almacenamiento local. Desea mantener sus archivos html separados para editarlos, pero en el momento de la compilación deben existir como cadenas en su fuente.
Geordie

Respuestas:

134

Sugeriría usar (unix util) xxd para esto. puedes usarlo así

$ echo hello world > a
$ xxd -i a

salidas:

unsigned char a[] = {
  0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x0a
};
unsigned int a_len = 12;
Hasturkun
fuente
18
Solo una nota: ¡el char [] creado por xxd no tiene terminación NULL! entonces hago $ xxd -i <file.txt> file.xxd $ echo ', 0' >> file.xxd y en main.c char file_content [] = {#include "file.xxd"};
2
Nunca supe sobre xxd. ¡Es impresionante!
1
@eSKay: eso viene directamente de la salida de xxd, como dice la respuesta. El nombre de la matriz es el nombre de archivo de entrada. si está ingresando datos en lugar de usar un archivo de entrada, obtendrá una lista de valores hexadecimales (sin la declaración de matriz o la variable len).
Hasturkun
44
Eso es extremadamente útil al incrustar sombreadores GLSL.
linello
55
Otra forma de agregar terminación 0x00 al código C producido xxd:xxd -i file.txt | sed 's/\([0-9a-f]\)$/\0, 0x00/' > file.h
vleo
104

La pregunta era sobre C, pero en caso de que alguien intente hacerlo con C ++ 11, se puede hacer con solo pequeños cambios en el archivo de texto incluido gracias a los nuevos literales de cadena sin formato :

En C ++ haz esto:

const char *s =
#include "test.txt"
;

En el archivo de texto haga esto:

R"(Line 1
Line 2
Line 3
Line 4
Line 5
Line 6)"

Por lo tanto, solo debe haber un prefijo en la parte superior del archivo y un sufijo al final del mismo. Entre ellos puedes hacer lo que quieras, no es necesario un escape especial siempre que no necesites la secuencia de caracteres )". Pero incluso esto puede funcionar si especifica su propio delimitador personalizado:

R"=====(Line 1
Line 2
Line 3
Now you can use "( and )" in the text file, too.
Line 5
Line 6)====="
kayahr
fuente
55
Gracias, elegí el método propuesto aquí para incrustar largos fragmentos de sql en mi código C ++ 11. Esto me permite mantener el SQL limpiamente separado en sus propios archivos y editarlos con la verificación de sintaxis adecuada, resaltado, etc.
YitzikC
1
Esto está muy cerca de lo que quiero. Especialmente el delimitador definido por el usuario. Muy útil. Quiero ir un paso más allá: ¿hay alguna forma de eliminar por completo el prefijo R "(y sufijo)" del archivo que desea incluir? Intenté definir dos archivos llamados bra.in y ket.in con el prefijo y el sufijo en ellos, incluidos bra.in, file.txt y ket.in uno por uno. Pero el compilador evalúa el contenido de bra.in (que es solo R "() antes de incluir el siguiente archivo. Por lo tanto, se quejará. Por favor, avíseme si alguien sabe cómo obtener el prefijo y el sufijo del archivo.txt. Gracias.
TMS
Supongo que C ++ no permitiría R "(<nueva línea> #include ...)"? Sería bueno que el archivo se ingiriera en tiempo de compilación para que no requiriera ninguna codificación en absoluto ... es decir, json o xml o csv o lo que no ...
Brian Chrisman
Puede hacer que el texto del literal en bruto sea un poco más legible si lo usa 1+R"...como delimitador inicial en lugar de R"..., y luego antepone una nueva línea antes Line 1. Esto transformará la expresión de una matriz a un puntero, pero eso no es realmente un problema aquí, ya que está inicializando un puntero, no una matriz.
Ruslan
14

Tienes dos posibilidades:

  1. Utilice las extensiones del compilador / enlazador para convertir un archivo en un archivo binario, con los símbolos adecuados que apuntan al principio y al final de los datos binarios. Consulte esta respuesta: incluya un archivo binario con el script de enlace GNU ld .
  2. Convierta su archivo en una secuencia de constantes de caracteres que puedan inicializar una matriz. Tenga en cuenta que no puede hacer "" y abarcar varias líneas. Necesitaría un carácter de continuación de línea ( \), "caracteres de escape y otros para que funcione. Es más fácil escribir un pequeño programa para convertir los bytes en una secuencia como '\xFF', '\xAB', ...., '\0'(o usar la herramienta Unix xxddescrita por otra respuesta, si la tiene disponible):

Código:

#include <stdio.h>

int main() {
    int c;
    while((c = fgetc(stdin)) != EOF) {
        printf("'\\x%X',", (unsigned)c);
    }
    printf("'\\0'"); // put terminating zero
}

(no probado). Entonces hazlo:

char my_file[] = {
#include "data.h"
};

Donde data.h es generado por

cat file.bin | ./bin2c > data.h
Johannes Schaub - litb
fuente
1
la última línea probablemente debería leer "cat file.bin | ./bin2c> data.h" o "./bin2c <file.bin> data.h"
Hasturkun
Solía codeproject.com/Tips/845393/... para crear un archivo hexadecimal (en Windows) desde un binario y luego usó su sugerencia de char my_file[] = { #include my_large_file.h };Gracias!
Alguien en alguna parte
bin2cno es el mismo bin2c que el de Debian hxtools, ten cuidado
ThorSummoner
o si es así, la invocación es mucho más extraña ahora:bin2c -H myoutput.h myinput1.txt myinputN.txt
ThorSummoner
9

ok, inspirado en la publicación de Daemin probé el siguiente ejemplo simple:

a.data:

"this is test\n file\n"

prueba.c:

int main(void)
{
    char *test = 
#include "a.data"
    ;
    return 0;
}

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

Por lo tanto, funciona pero requiere datos rodeados de comillas.

Ilya
fuente
A eso me refería en el último fragmento de mi respuesta.
Daemin
cita, o como se llame, perdone mi inglés
Ilya
Esto requiere que los datos se escapen en C. No creo que eso sea lo que busca la publicación. Si esto tuviera algún tipo de macro de inclusión que escapó en C del contenido del archivo, estaría bien.
Brian Chrisman
8

Me gusta la respuesta de kayahr. Sin embargo, si no desea tocar los archivos de entrada y si está usando CMake , puede agregar las secuencias de caracteres delimitador en el archivo. El siguiente código CMake, por ejemplo, copia los archivos de entrada y ajusta su contenido en consecuencia:

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)

Luego incluya en c ++ de esta manera:

constexpr char *test =
#include "generated/cool.frag"
;
Martin R.
fuente
5

Puedes hacer esto usando objcopy:

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

Ahora tiene un archivo objeto que puede vincular a su ejecutable que contiene símbolos para el comienzo, el final y el tamaño del contenido myfile.txt.

John Zwinck
fuente
1
¿puede decirnos cuáles serán los nombres de los símbolos?
Mark Ch
@ MarkCh: Según los documentos, los nombres de los símbolos se generan a partir del nombre de archivo de entrada.
John Zwinck
Supongo que esto no funcionará en máquinas que no sean x86-64, ¿verdad?
ThorSummoner
2

Necesitas mi xtrutilidad pero puedes hacerlo con un bash script. Este es un guión que llamo bin2inc. El primer parámetro es el nombre del resultado char[] variable. El segundo parámetro es el nombre de file. La salida es C include filecon el contenido del archivo codificado (en minúsculas hex) como el nombre de variable dado. El char arrayes zero terminated, y la longitud de los datos se almacena en$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")';'

PUEDES OBTENER XTR AQUÍ xtr (personaje eXTRapolator) es GPLV3


fuente
2

Si está dispuesto a recurrir a algunos trucos sucios, puede ser creativo con literales de cadena sin formato y #includepara ciertos tipos de archivos.

Por ejemplo, supongamos que quiero incluir algunos scripts SQL para SQLite en mi proyecto y quiero resaltar la sintaxis, pero no quiero ninguna infraestructura de compilación especial. Puedo tener este archivotest.sql que es SQL válido para SQLite donde --comienza un comentario:

--x, R"(--
SELECT * from TestTable
WHERE field = 5
--)"

Y luego en mi código C ++ puedo tener:

int main()
{
    auto x = 0;
    const char* mysql = (
#include "test.sql"
    );

    cout << mysql << endl;
}

El resultado es:

--
SELECT * from TestTable
WHERE field = 5
--

O para incluir algún código de Python de un archivo test.pyque sea un script de Python válido (porque #comienza un comentario en Python ypass es un no-op):

#define pass R"(
pass
def myfunc():
    print("Some Python code")

myfunc()
#undef pass
#define pass )"
pass

Y luego en el código C ++:

int main()
{
    const char* mypython = (
#include "test.py"
    );

    cout << mypython << endl;
}

Lo que dará salida:

pass
def myfunc():
    print("Some Python code")

myfunc()
#undef pass
#define pass

Debería ser posible jugar trucos similares para varios otros tipos de código que podría incluir como una cadena. Si es una buena idea o no, no estoy seguro. Es una especie de hack limpio pero probablemente no es algo que desearías en un código de producción real. Sin embargo, podría estar bien para un proyecto de hack de fin de semana.

Mattnewport
fuente
¡He usado este enfoque para poner OpenGL Shaders en archivos de texto también!
yano
1

Reimplementé xxd en python3, arreglando todas las molestias de xxd:

  • Const corrección
  • tipo de datos de longitud de cadena: int → size_t
  • Terminación nula (en caso de que desee)
  • Compatible con cadena C: soltar unsigneden la matriz.
  • Resultado más pequeño y legible, como lo habría escrito: el ascii imprimible se emite tal cual; otros bytes están codificados en hexadecimal.

Aquí está el script, filtrado por sí mismo, para que pueda ver lo que hace:

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;

Uso (esto extrae el script):

#include <stdio.h>

extern const char pyxxd[];
extern const size_t pyxxd_len;

int main()
{
    fwrite(pyxxd, 1, pyxxd_len, stdout);
}
usuario2394284
fuente
1

Lo que podría funcionar es si haces algo como:

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

Por supuesto, tendrá que tener cuidado con lo que realmente está en el archivo , asegurándose de que no haya comillas dobles, que se escapen todos los caracteres apropiados, etc.

Por lo tanto, podría ser más fácil si solo carga el texto de un archivo en tiempo de ejecución o incrusta el texto directamente en el código.

Si aún quisiera el texto en otro archivo, podría tenerlo allí, pero tendría que estar representado allí como una cadena. Usaría el código como arriba pero sin las comillas dobles. Por ejemplo:

file.txt

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

main.cpp

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

Entonces, básicamente, tener una cadena de estilo C o C ++ en un archivo de texto que incluya. Haría que el código fuera más ordenado porque no hay una gran cantidad de texto al comienzo del archivo.

Daemin
fuente
3
Buena idea, pero no funcionará, o tiene un error porque el literal incluye una nueva línea o la parte #include se leerá como una cadena y no se ejecutará, maldita sea si lo hace y maldita sea si no lo hace. .
Motti el
1
@Motti: de acuerdo, como está escrito, sintácticamente inválido C. La idea es interesante: el preprocesador C es lógicamente una fase separada, pero la práctica es que no despega porque cada línea en el archivo incluido tendría para terminar con una barra invertida, etc.
Jonathan Leffler el
2
Humm Me parece que usted no debe necesitar la barra invertida como la mayoría de los compiladores concatenar cadenas adjecent juntos
EvilTeach
Lo que pasa con esta respuesta es ... si fuera así de simple, ¡no creo que el OP hubiera hecho la pregunta! -1 porque la presencia de esta respuesta es un poco alentador para que las personas pierdan su tiempo intentando algo que no funciona. Creo que podríamos eliminar el voto negativo si cambiaste "Lo que podría funcionar" a "Para referencia, esto no funciona"
Mark Ch
@JonathanLeffler Después de que se ejecute el preprocesador, debe ser C o C ++ válido, dependiendo de cómo se formatee file.txt.
Daemin
0

Incluso si se puede hacer en tiempo de compilación (no creo que pueda hacerlo en general), el texto probablemente sea el encabezado preprocesado en lugar de los contenidos de los archivos al pie de la letra. Espero que tengas que cargar el texto del archivo en tiempo de ejecución o hacer un trabajo desagradable de cortar y pegar.

Daniel Paull
fuente
0

La respuesta de Hasturkun usando la opción xxd -i es excelente. Si desea incorporar el proceso de conversión (texto -> archivo de inclusión hexadecimal) directamente en su compilación, la herramienta / biblioteca hexdump.c agregó recientemente una capacidad similar a la opción -i de xxd (no le da el encabezado completo; necesita para proporcionar la definición de la matriz de caracteres, pero eso tiene la ventaja de permitirle elegir el nombre de la matriz de caracteres):

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

Su licencia es mucho más "estándar" que xxd y es muy liberal: puede ver un ejemplo de su uso para incrustar un archivo init en un programa en los archivos CMakeLists.txt y schema.c aquí:

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

Existen ventajas y desventajas para incluir los archivos generados en los árboles de origen y las utilidades de agrupación: cómo manejarlo dependerá de los objetivos y necesidades específicos de su proyecto. hexdump.c abre la opción de agrupación para esta aplicación.

starseeker
fuente
0

Creo que no es posible solo con el compilador y el preprocesador. gcc permite esto:

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

Pero desafortunadamente no esto:

#define _STRGF(x) # x
#define STRGF(x) _STRGF(x)

    printk ( MODULE_NAME " built " __DATE__ " at " __TIME__ " on host "
            STRGF(
#               include "/etc/hostname"
            )
            "\n" );

El error es:

/etc/hostname: In function init_module’:
/etc/hostname:1:0: error: unterminated argument list invoking macro "STRGF"
no-un-usuario
fuente
He mirado, como me has ordenado mirar. No veo ninguna información nueva en su respuesta (información que no está en otras respuestas), más allá de una referencia /etc/hostnamecomo una forma de incrustar el nombre de la máquina de compilación en la cadena, que (incluso si funcionara) no sería portátil ya que Mac OS X no tiene un archivo /etc/hostname. Tenga en cuenta que usar nombres de macro que comienzan con un guión bajo seguido de una letra mayúscula es usar un nombre reservado para la implementación, que es A Bad Thing ™.
Jonathan Leffler
0

¡Por qué no vincular el texto al programa y usarlo como una variable global! Aquí hay un ejemplo. Estoy considerando usar esto para incluir archivos de sombreador Open GL dentro de un ejecutable ya que los sombreadores GL deben compilarse para la GPU en tiempo de ejecución.

TechDragon
fuente
0

Tuve problemas similares, y para archivos pequeños, la solución antes mencionada de Johannes Schaub funcionó de maravilla para mí.

Sin embargo, para archivos que son un poco más grandes, se encontró con problemas con el límite de matriz de caracteres del compilador. Por lo tanto, escribí una pequeña aplicación de codificador que convierte el contenido del archivo en una matriz de caracteres 2D de fragmentos de igual tamaño (y posiblemente ceros de relleno). Produce archivos de texto de salida con datos de matriz 2D como este:

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

donde 4 es en realidad una variable MAX_CHARS_PER_ARRAY en el codificador. El archivo con el código C resultante, llamado, por ejemplo, "main_js_file_data.h" se puede insertar fácilmente en la aplicación C ++, por ejemplo, así:

#include "main_js_file_data.h"

Aquí está el código fuente del codificador:

#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;
}
volzotan
fuente
0

Este problema me estaba irritando y xxd no funciona para mi caso de uso porque hizo que la variable se llamara algo así como __home_myname_build_prog_cmakelists_src_autogen cuando intenté escribirlo, así que hice una utilidad para resolver este problema exacto:

https://github.com/Exaeta/brcc

Genera un archivo fuente y de encabezado y le permite establecer explícitamente el nombre de cada variable para que pueda usarlas a través de std :: begin (arrayname) y std :: end (arrayname).

Lo incorporé a mi proyecto cmake así:

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)

Con pequeños ajustes, supongo que también podría funcionar para C.

Exaeta
fuente
-1

en xh

"this is a "
"buncha text"

en main.c

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

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

    return 0
}

Debería hacer el trabajo.

EvilTeach
fuente
Para varias líneas, necesita agregar \ n entonces: "línea 1 \ n" "línea 2 \ n"
Superfly Jon
es un poco engañoso, obviamente esto requiere un poco de preparación del archivo de texto para agregar comillas y \ n caracteres, no funciona en el caso general
Mark Ch