I am trying to execute quite long external command (curl with long parameters) in my C++ program.
Works fine on Linux, but Windows (under Cygwin) always shows "System cannot execute specified program" if "cmd" is longer than 127 chars.
Linux accepts with same code almost unlimited size.
Tried changing size of buffer, no change.
Used this code:
#include <string>
#include <iostream>
#include <stdio.h>
std::string exec(char* cmd) {
FILE* pipe = _popen(cmd, "r");
if (!pipe)
return "ERROR";
char buffer[128];
std::string result = "";
while(!feof(pipe)) {
if(fgets(buffer, 128, pipe) != NULL)
result += buffer;
}
_pclose(pipe);
return result;
}
Is that Windows specific limitation, or is possible somehow to extend the cmd buffer?
Added how is created char* which I am trying to send:
string message="This is message to send.";
std::string send="";
std::string curl_header="curl -s -F \"token=aE71iieVt8G1RWdJb59qmAa3hbxxxx\" -F \"user=uPKYQyNgGURbH1g56nBnjn9jNsxxxxx\" -F \"message=";
send.append(curl_header);
send.append(message);
send.append("\" https://api.pushover.net/1/messages.json");
cout << "\n";
cout << "FULL STRING WITH PARAMS TO EXECUTE:";
std::cout << send << '\n';
char *cstr = new char[send.length() + 1];
strcpy(cstr, send.c_str());
cout << "FULL CHAR ARRAY WITH PARAMS TO EXECUTE:";
std::cout << cstr << '\n';//this returns correct string, even longer than 127 chars
cout << exec(cstr);// this returns error, same with system() if cstr is longer than 127 chars
delete [] cstr;
Have you tried using system?
#include <iostream>
using namespace std;
int main(int argc,char** args){
if(argc<2){
system("run run run run run run run run run run runr urn runrunrunru unr unrunr urn run runr unr urn run runrun runr unr urnrun runr urnurnrunrunrunrunrunrurn urnurnunrunrunrunrunrunrn u unru ru runr ur urn ru run rururnunrunrunr");
}else{
printf("argc: %d",argc);
}
system("pause");
}
This seems to work fine to me. The program is named run, so it runs itself with that huge argument and then prints out the number of arguments it sees. Is this what you are trying to do?
Related
I am trying to get the output of the curl command to work inside of an if statement
I am new to C++ and don't know how I could do this.
int curlreq;
curlreq = system("curl localhost/file.txt");
string curlreqstring = to_string(curlreq);
if ((krxcrlstr.find("hello") != string::npos) ) {
cout << "hello\n";
}
else if (curlreqstring.find("hello2") != string::npos) {
cout << "hello2\n";
}
I am doing this on Windows. The project is a console app C++ 20
All the above code is doing, is printing what the curl response is, but I need that as a variable to then determine what the program should do.
As you see I am getting the contents of a file from localhost, the file itself has a singular line.
std::system returns an int with an implementation-defined value. On many platforms, 0 means success and anything else means some sort of failure. I'm making this assumption in the below example.
My advice is to use libcurl which is what the curl command is using internally. With a little setup you can make your program perform curl actions and receive what you get back into your program. If you do not have access to libcurl or find it a bit hard to get started with, you could wrap your system command in a function which performs the curl command but directs the output to a temporary file which you read after curl is done.
Example:
#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <iterator>
#include <string>
// a simple class to remove the temporary file after use
struct file_to_remove {
// remove "filename" when the object is destroyed
~file_to_remove() { std::remove(filename.c_str()); }
const std::string& str() const { return filename; }
const std::string filename;
};
// A function to "curl":
std::string Curl(std::string options_plus_url) {
// An instance to remove the temporary file after use.
// Place it where you have permission to create files:
file_to_remove tempfile{"tmpfile"};
// build the command line
// -s to make curl silent
// -o to save to a file
options_plus_url = "curl -so " + tempfile.str() + " " + options_plus_url;
// perfom the system() command:
int rv = std::system(options_plus_url.c_str());
// not 0 is a common return value to indicate problems:
if(rv != 0) throw std::runtime_error("bad curl");
// open the file for reading
std::ifstream ifs(tempfile.str());
// throw if it didn't open ok:
if(!ifs) throw std::runtime_error(std::strerror(errno));
// put the whole file in the returned string:
return {std::istreambuf_iterator<char>(ifs),
std::istreambuf_iterator<char>{}};
} // tmpfile is removed when file_to_remove goes out of scope
With the above Curl function you can perform curl commands and get the response as a std::string which you can then use in your if statements etc.
Example:
int main(int argc, char* argv[]) {
if(argc < 2) return 1; // must get an URL as argument
try {
std::string response = Curl(argv[1]);
std::cout << response << '\n';
} catch(const std::exception& ex) {
std::cout << "Exception: " << ex.what() << '\n';
}
}
Aim: To design a linux shell, which shows a prompt to take input from user, creates a new process to execute that command then terminates/exits the process. Here is my code
#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
using namespace std;
string cmd; //global string so cmd copied to child to execute
void HandleAsParent(){
cout<<"Linux Shell 1.0\n";
string s;
while (!exitflag) {
cout<<"myShell>";
getline(cin,cmd); //Take user input
fork();
wait(NULL);
}
}
void HandleAsChild(){
cout<<"Executing";
system(cmd.c_str());
}
int main() {
pid_t p = fork();
if(p != 0){
HandleAsParent(); //This is parent process
}
else {
HandleAsChild(); //This is child process
}
}
The problem is that, because of the first fork() call in the main,
myShell>Executing
is displayed on the first line when the program runs instead of just
myShell>
.
I am able to understand why this is happening but cannot figure out how do I stop that first child process from being executed.
Please suggest me workarounds/solutions to my problem.
Edit 1: This is one of my Assignment(for learning UNIX Processes)
questions, and It is clearly stated that the program " prompts the
user for a command, parses the command, and then executes it with a
child process "
As I already guessed, system() probably uses a combination of fork(), exec() and wait(). Out of curiosity, I googled for source code and found one on woboq.org: glibc/sysdeps/posix/system.c.
This in mind, using system(), the required child process "comes for free". So, I got this minimal sample:
#include <iostream>
void callCmd(const std::string &cmd)
{
system(cmd.c_str());
}
int main()
{
std::cout << "My Linux Shell 1.0\n"
<< "Type exit[Enter] to exit.\n";
for (;;) {
std::cout << "> ";
std::string input; std::getline(std::cin, input);
if (input == "exit") return 0;
callCmd(input);
}
}
Compiled and tested on cygwin on Windows 10:
$ g++ -std=c++11 -o mycroShell mycroShell.cc
$ ./mycroShell
My Linux Shell 1.0
Type exit[Enter] to exit.
> echo "Hello"
Hello
> exit
$
After getting this running, the system() call in callCmd() can be replaced by fork()/exec()/wait() without the necessity to change anything else.
A simplified version could look like this:
#include <iostream>
#include <string>
#include <vector>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
void callCmd(const std::string &input)
{
// the pre-processing: split the input into command and arguments
std::string cmdArgs = input;
std::vector<char*> args;
char *cmd = &cmdArgs[0];
args.push_back(cmd);
for (char *c = cmd; *c; ++c) {
if (*c == ' ') {
*c = '\0'; args.push_back(c + 1);
}
}
args.push_back(nullptr); // append terminator
// simple replacement of system() (not that sophisticated)
int ret = fork();
if (ret < 0) { // failure
std::cerr << "Failed to execute '" << cmd << "'!\n";
} else if (ret == 0) { // child
execvp(cmd, args.data());
} else { // parent
waitpid(ret, nullptr, 0);
}
}
int main()
{
std::cout << "My Linux Shell 1.1\n"
<< "Type exit[Enter] to exit.\n";
for (;;) {
std::cout << "> ";
std::string input; std::getline(std::cin, input);
if (input == "exit") return 0;
callCmd(input);
}
}
Compiled and tested on cygwin on Windows 10 again:
$ g++ -std=c++11 -o mycroShell mycroShell.cc
$ ./mycroShell
My Linux Shell 1.1
Type exit[Enter] to exit.
> /usr/bin/echo "Hello"
"Hello"
> exit
$
Notes:
IMHO, the most tricky part of this is to prepare a proper argument vector for execvp.
I tried with echo "Hello" as well and it worked. This surprised me a bit as echo is a bash built-in command. I assume that it found /usr/bin/echo and used it as well as in my above output.
The error handling is rather poor – something which should be extended for serious applications.
I am working on a program that includes the ability to print the working directory and change directories. Originally I had the user typing 'cd' which would call the cd function and ask them which directory to change to. This worked fine however I wanted to be able to do something like "cd /Users" all in one line. I've managed to split the string and pass the split part to the character variable I'm using for chdir fine, but for some reason chdir is not actually changing the directory with this method.
`void exec_cd(std::string destination)
{
int BUFFER_SIZE = 1024;
char *directory;
directory = new char [BUFFER_SIZE]; //Allocated memory in order to cin to the pointer char
strcpy(directory, destination.c_str()); //Copy destination string into char directory
//std::cout << "Enter target directory: " << std::endl << ">";
//std::cin >> directory;
std::cout << "TEST: " << directory;
chdir(directory);
delete [] directory;
}`
I commented out the old lines I had in there, when those were there instead of strcpy it worked fine. This function is passed everything entered after cd, I know the strcpy is doing its job because the line with "TEST :" outputs whatever it is supposed to (e.g. I type cd /Users) and it will show that directory indeed holds '/Users' it just is not working with chdir for some reason. I have a pwd function that works fine as far as I know but I will post that here too.
void exec_pwd()
{
long size;
char *buf; //buffer holder
char *ptr; //where the current directory will be saved to
size = pathconf(".", _PC_PATH_MAX); //Gets size of path and saves it to size var.
if ((buf = (char *)malloc((size_t)size)) != NULL) //Set buff = size of char * allocated size, if not null then get wd
ptr = getcwd(buf, (size_t)size);
std::cout << ptr << std::endl;
}
Confirmed there was white space in the target directory, added a trim function to remove spaces before and after and issue has been resolved.
You may find that converting OS errors to error_codes as soon as possible is helpful.
#include <unistd.h>
#include <cerrno>
#include <system_error>
#include <iostream>
std::error_code change_directory(std::string const& str)
{
std::error_code result;
if(chdir(str.c_str()))
{
result = std::error_code(errno, std::system_category());
}
return result;
}
void throw_on_failure(std::error_code ec)
{
if (ec) throw std::system_error(ec);
}
int main(int argc, char** argv)
{
try
{
throw_on_failure(change_directory(argv[1]));
}
catch(std::exception const& e)
{
std::cout << e.what() << '\n';
}
}
I have been using code similar to this example to get the results to Linux shell commands from within C++. It seems to work great with basic commands. However, when I try to run it with the command in the example I get the shell error: syntax error near unexpected token `('
#include <stdio.h>
#include <iostream>
int main(int argc, char *argv[]) {
std::string cmdString = "cat <(grep 'cpu ' /proc/stat) <(sleep 1 && grep 'cpu ' /proc/stat) | "
"awk -v RS=\"\" '{print ($13-$2+$15-$4)*100/($13-$2+$15-$4+$16-$5) }' 2>&1";
std::string result, file;
FILE *stream;
std::cout << "cmdString = " << cmdString << std::endl;
stream = popen(cmdString.c_str(), "r");
char buffer[256];
if(fgets(buffer, sizeof(buffer), stream) != NULL) {
file = buffer;
result += file.substr(0, file.size() - 1);
}
pclose(stream);
std::cout << "result = " << result << std::endl;
return 0;
}
Am I doing something wrong? Do I need to reformat the string somehow?
You are using bash-specific constructs in your command line. POSIX mandates that the argument of popen is passed to bin/sh, not user's current shell.
To fix the problem you can do one of the following:
Rewrite the commands to be /bin/sh-compliant.
Explicitly use bash in your command line:
std::string cmdString = "bash -c \".....\"";
Drop the shell altogether and implement the pipeline yourself.
I need to open a subprocess using popen, the process will continuously ask for user input... The main process need to send that data over the pipe.
This is my first attempt:
FILE *in;
char buff[1024];
if(!(in = popen("cd FIX/fix2/src; java -cp .:./* com.fix.bot", "w"))){
return 1;
}
while(1){
char buffer[] = { 'x' };
fwrite(buffer, sizeof(char), sizeof(buffer), in);
cout << "Wrote!" << endl;
usleep(1000000);
}
However the data is not sent! I need to close the pipe with pclose() so that the data is written to the process. How can I make sure to write the data without having to close the pipe everytime?
You'll want to call fflush(in) to make sure that the buffered data is actually written to the stream.
Also check that java -cp .:./* in the command isn't expanding to an invalid classpath. I think that'll end up expanding to several arguments if there's more than one file in the current directory, and not actual classpath entries.
This works for me:
#include <stdio.h>
#include <unistd.h>
#include <iostream>
int main(int argc, char ** argv) {
FILE *in;
char buff[1024];
if(!(in = popen("./2.sh", "w"))){
return 1;
}
while(1) {
char buffer[] = "xx\n";
fwrite(buffer, sizeof(char), sizeof(buffer), in);
fflush(in);
std::cout << "Wrote!" << std::endl;
usleep(1000000);
}
}
where 2.sh is:
#!/bin/sh
read INP
echo 'read: ' $INP
So I am really suspecting that the problem is missing \n.