Esegui file esterno dall'interno di C++
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
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.
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::stringstream
può essere evitato e puoi usare concatenate std::string
s direttamente fintanto che il primo elemento è un std::string
.
std::string newCommand = command + ">> " + outputFile + "2>&1";
Il codice in getOutputFileData
che 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 output
esce dall'ambito, il file verrà chiuso da solo. È lo stesso motivo per cui non vai in giro a eliminare ogni banalmente distruttibile std::vector
o array che verrà ripulito automaticamente.
Utilizzare const &
o std::string_view
dove 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à contains
così 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