Esegui file esterno dall'interno di C++

Aug 21 2020

Sto cercando di creare un'applicazione che sul front-end presenti all'utente un editor di testo in cui inserire il codice e quindi eseguirò quel codice e restituirò il risultato. Ho pensato che sarebbe stato un progetto divertente provare a costruire la mia versione di leetcode come progetto di apprendimento.

In questo momento questo è quello che sto facendo per eseguire il codice fornito. Diciamo che stiamo eseguendo il codice Python, perché è tutto ciò che ho implementato in questo momento.

Per prima cosa prendo il codice che l'utente invia e creo un file che contiene il codice dato:

std::string python(std::string code){
    std::string langCommand = "python3 ";
    std::string outFile;

    //I am hoping to parallelize this operation so I add threadID to output
    outFile = createOutFileName("PythonRunner.py");

    std::ofstream output;
    output.open(outFile);
    output << code;
    output.close();
    return langCommand + outFile;
}

La prossima cosa che faccio è creare un file di output ed eseguire il file creato in precedenza, ma invio il mio stdout/stderr a un altro outputfile:

std::string Program::run(){
    std::string command = createFile(this->lang, this->code);

    this->outputFile = createOutFileName("output.txt");

    std::stringstream newCommand;
    newCommand << command;
    newCommand << ">> ";
    newCommand << outputFile;
    newCommand << " 2>&1";

    system(newCommand.str().c_str());
    std::string output = getOutputFileData(this->outputFile);
    cleanupFiles(command);
    return output;
}

Alla fine restituisco tutto ciò che ho ottenuto dal mio file di output ed è così che sto eseguendo il mio codice.

Devo pensare che ci sia un modo più semplice per farlo. Soprattutto dal momento che sto scrivendo così tanto su un file e poi leggendo da esso, c'è comunque modo di sbarazzarsene?

Voglio anche includere più di una lingua in futuro, quindi non voglio utilizzare librerie specifiche per una determinata lingua.

Infine, questo è il mio primo progetto C++, quindi mi piacerebbe qualsiasi suggerimento C++!

Modifica: alla fine voglio parallelizzare questo codice e trovare un modo per incapsulare il programma in modo che non possa danneggiare il sistema su cui è in esecuzione. Se c'è forse qualche programma esterno che andrebbe bene per questo fammi sapere e mi dà anche il suo stderr/stdout fammi sapere.

Modifica: come qualcuno ha chiesto, ecco l'intero repositoryhttps://github.com/lkelly93/coderunner

Risposte

2 MartinYork Aug 23 2020 at 00:13

Piuttosto di quanto system()dovresti popen().

La differenza è che il sistema esegue il comando in un processo secondario senza accesso a questi processi, mentre popen esegue il comando in un processo secondario ma fornisce l'accesso ai flussi di input e output dei processi secondari.

Ciò ti consentirà di eseguire i processi secondari e trasmettere direttamente l'input ai processi (dal campo di input che hai fornito per l'input standard), quindi leggere l'output dai processi e scriverlo nel campo di output nell'interfaccia utente.

FILE*  proc = popen(command);
std::string inputFromUser = getUserInputFromUI();
// Using fwrite() correctly left to user.
// You need to check for errors and continue etc.
fwrite(inputFromUser.c_str(), 1, inputFromUser.size(), proc);

char  buffer[100];
std::size_t size;
while((size = fread(buffer, 1, 100, proc)) != 0) {
    // Check for read errors here.
    sendToUserInterface(std::string(bufffer, buffer + size));
}

pclose(proc);
 

Ordinato per correlato non è necessario salvare lo script pythong come file. Il comando python accetta -come nome il che significa leggere lo script dall'input standard anziché dal file denominato.

Quindi puoi eseguire il comando python (con popen()) quindi scrivere lo script che vuoi eseguire nel flusso di input del file prodotto.

Ciò eliminerà la necessità di eventuali file intermedi.

1 aki Aug 21 2020 at 19:56

Cose apprezzabili:

  • Documentazione per implementazioni e sezioni realizzate utilizzando i commenti.
  • Classi, funzioni e file secondo il Principio di Responsabilità Unica .
  • Nomi descrittivi delle funzioni. (non variabili come noto di seguito)
  • Quadro di prova! Ma i test dovrebbero controllare anche le funzioni interne, non solo l'intero programma.

La terminologia dei flussi di file e dei nomi dei file è molto confusa e mi fa cercare troppo spesso i tipi restituiti dalle funzioni o la dichiarazione delle variabili.

Program::outputFileè il nome del file che non è chiaro qui. L'ho scambiato per FILE*.

In un altro punto, std::ofstream output;l'output suona come il contenuto di output del programma ma è un flusso!

std::string output = getOutputFileData(this->outputFile);Ed eccolo di nuovo una stringa!


Il codice non si occupa di percorsi assoluti e relativi.

Il test fallisce con questo:

runnerFiles/0x1005c05c0output.txt does not exist.

Con un tale codice, sarei molto riluttante a usare rm. Al massimo, tieni tutti i file usa e getta in una cartella e chiedi all'utente di eliminarli.


std::stringstream newCommand;
newCommand << command;
newCommand << ">> ";
newCommand << outputFile;
newCommand << " 2>&1";

system(newCommand.str().c_str());

std::stringstreampuò essere evitato e puoi usare concatenate std::strings direttamente fintanto che il primo elemento è un std::string.

std::string newCommand = command + ">> " + outputFile + "2>&1";

Il codice in getOutputFileDatache utilizza FILE*e char buffer (che non hai nemmeno allocato!) può essere sostituito con il seguente (aggiungi la gestione degli errori)

  std::ifstream run_output{outFileLocation};
  std::stringstream buffer;
  buffer << run_output.rdbuf();
  return buffer.str();
  • https://stackoverflow.com/questions/2602013/read-whole-ascii-file-into-c-stdstring?noredirect=1&lq=1

Dal momento che non hai bisogno di un controllo preciso sulle linee, non preoccuparti di getline.

Preferisci iostream per I/O. gli iostream sono sicuri, flessibili ed estensibili.

  • https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Rio-streams

std::ofstream output;
output.open(outFile);
output << code;
output.close();

Può essere accorciato come

std::ofstream output(outFile);
output << code;

Non preoccuparti di chiudere se non è necessario. Quando outputesce dall'ambito, il file verrà chiuso da solo. È lo stesso motivo per cui non vai in giro a eliminare ogni banalmente distruttibile std::vectoro array che verrà ripulito automaticamente.


Utilizzare const &o std::string_viewdove le stringhe vengono solo lette. Sono economici da far circolare e indicano l'intenzione che il contenuto non venga modificato.

std::string createFile(std::string lang, std::string code)
std::string getOutputFileData(std::string outFileLocation)
bool isSupportedLanguage(std::string lang)
void Program::cleanupFiles(std::string oldCommand)

auto iter = supportedLanguages.find(lang);

C++ 20 avrà containscosì che ti fa risparmiare alcune righe.

  • https://en.cppreference.com/w/cpp/container/unordered_map

this->code

Invece di this->, considera di aggiungere o anteporre _alle variabili per indicare che sono membri privati.


È più leggibile IMO se l'ordine di implementazione segue l'ordine di dichiarazione per le funzioni.

In Program, il costruttore può andare all'inizio del file, invece che in fondo.

  • https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#nl16-use-a-conventional-class-member-declaration-order