How to give command from rust code to console terminal

I want to run curl command from my rust code to console terminal … I have done this , but not succeeded; Here is my Rust Playground Link

  use std::process::Command;
        fn main() {
            let output = Command::new("curl")
               .arg("-X POST --data '{\"jsonrpc\":\"2.0\",\"method\":\"sayhello\",\"params\":[],\"id\":1}' H \"Content-Type: application/json\" http://127.0.0.1:17853")
   
    .output().unwrap_or_else(|e| {
        panic!("failed to execute process: {}", e)
});

if output.status.success() {
    let s = String::from_utf8_lossy(&output.stdout);

    print!("curl succeeded and stdout was:\n{}", s);
} else {
    let s = String::from_utf8_lossy(&output.stderr);

    print!("curl failed and stderr was:\n{}", s);
}

}

when I run this I received following output ;

curl failed and stderr was:
curl: no URL specified!
curl: try ‘curl --help’ or ‘curl --manual’ for more information

If you do command.arg('-X POST'), the string -X POST is passed as one argument to curl, but curl expects there to be two separate argument -X and POST. .arg is not going to separate argument by spaces like your shell does. Instead, you would want to do something like:

Command::new("curl").args(&["-X", "POST", "--data", "{\"jsonrpc\": ...}"])

See the document for arg for more info.

thanks for your time, but still have following error;

the trait bound `[&str; 4]: std::convert::AsRef<std::ffi::OsStr>` is not satisfied
  --> src/main.rs:10:9
   |
10 |        .arg(&["-X", "POST" , "--data","'{\"jsonrpc\":\"2.0\",\"method\":\"sayhello\",\"params\":[],\"id\":1}' H \"Content-Type: application/json\" http://127.0.0.1:17853"])
   |         ^^^ the trait `std::convert::AsRef<std::ffi::OsStr>` is not implemented for `[&str; 4]`
   |
   = help: the following implementations were found:
             <[T; 0] as std::convert::AsRef<[T]>>
             <[T; 10] as std::convert::AsRef<[T]>>
             <[T; 11] as std::convert::AsRef<[T]>>
             <[T; 12] as std::convert::AsRef<[T]>>
           and 30 others
   = note: required because of the requirements on the impl of `std::convert::AsRef<std::ffi::OsStr>` for `&[&str; 4]`

Should be .args(&["-X...

thanks so much, it solve my problem, but still could not print
let s = String::from_utf8_lossy(&output.stdout); means no output . Although I properly have given all args to my program

(.args(&["-X", "POST" , "--data","'{\"jsonrpc\":\"2.0\",\"method\":\"sayhello\",\"params\":[],\"id\":1}'" , "-H", "\"Content-Type: application/json\"", "http://127.0.0.1:17853"]))

of this command curl -X POST --data '{"jsonrpc":"2.0","method":"sayhello","params":[],"id":1}' -H "Content-Type: application/json" http://127.0.0.1:17853, but no output…

However, If I run this command on terminal I can see the output… {"jsonrpc":"2.0","result":"Hello, world","id":1}, but could not got through my program…

Again, the rust process::Command executor is not a shell, so you don’t have to quote strings, etc, like you do in the terminal.

What I mean is that you change it like this and see if it works:

.args(&["-X", "POST", "--data", "{\"jsonrpc\":\"2.0\",\"method\":\"sayhello\",\"params\":[],\"id\":1}" , "-H", "Content-Type: application/json", "http://127.0.0.1:17853"])

Because .args directly pass the argument to curl, your quotation marks will be passed to curl, which it won’t understand. And you don’t need these quotation marks, because you separated the arguments yourself.

Thanks a lot … problem solved… Rust Community is the best community…

Sorry to disturb you , I need one more help… that I want to pass some value to above command through my code like this… .args(format!(&["-X", "POST", "--data", "{\"jsonrpc\":\"2.0\",\"method\":\"sayhello\",\"params\":[**{}**],\"id\":1}" , "-H", "Content-Type: application/json", "http://127.0.0.1:17853"],myNo))

is this possible ? I think I am doing some mistake , because I am facing some errors… Could you give me more time… thanks in advance…

format! only works on a single string, not array of strings.

Try:

.args(
    &["-X", "POST", "--data",
        &format!("{\"jsonrpc\":\"2.0\",\"method\":\"sayhello\",\"params\":[{}],\"id\":1}", myNo)
        , "-H", "Content-Type: application/json", "http://127.0.0.1:17853"]
)

Although I think at this point you might want to look into generating JSON string in your code, rather than format!ing strings, which is 1. tedious and 2. error-prone.

1 Like

Actually, I only need constant json format strings, I don’t need to processing JSON string. If you think, still I must use JSON string, then I will follow your recommendation.
Anyhow, now code is working, although need double ‘{’ and ‘}’ i.e. &format!("{{\"jsonrpc\":\"2.0\",\"method\":\"sayhello\",\"params\":[{}],\"id\":1}}" which is i think required in format! macro.

Yeah, forgot about the need to escape {s by doubling.:stuck_out_tongue_winking_eye: otherwise format! can’t know whether a { is supposed to mean format placeholder or a literal {. Glad it worked.

1 Like

By the way you can use raw string literals (r#"..."#) instead of classic string literals ("...") so that you don’t even need to escape the inner double quotes:

.args(&["-X", "POST", "--data", r#"{"jsonrpc":"2.0","method":"sayhello","params":[],"id":1}"# , "-H", "Content-Type: application/json", "http://127.0.0.1:17853"])

Then again, you could also serialize the json using ::serde_json:

let json = ::serde_json::json!({
    "jsonrpc": "2.0",
    "method": "sayhello",
    "params": [],
    "id": 1,
}).to_string();
2 Likes

thanks I have tried with above json string with raw string option (r#…#), its working and show its expected output. But when I tried with json, it passed successfully to my program but not produced expected output. Is something missing in my program…

fn main() {


  
 let json = ::serde_json::json!({
    "jsonrpc": 2.0,
    "method": "sayhello",
    "params": [],
    "id": 1,
}).to_string();
 

let output = Command::new("curl")
// WORKING 
   // .args(&["-X", "POST", "--data", r#"{"jsonrpc":"2.0","method":"sayhello","params":[],"id":1}"# , "-H", "Content-Type: application/json", "http://127.0.0.1:17853"])

   .args(
    &["-X", "POST", "--data", json.as_str(), 
   "-H", "Content-Type: application/json", "http://127.0.0.1:17853"]
   )
   
    .output().unwrap_or_else(|e| {
        panic!("failed to execute process: {}", e)
});

if output.status.success() {
    let s = String::from_utf8_lossy(&output.stdout);

    print!("curl succeeded and stdout was:\n{}", s);
} else {
    let s = String::from_utf8_lossy(&output.stderr);

    print!("curl failed and stderr was:\n{}", s);
}

output of above program is as below;

curl succeeded and stdout was:
{"jsonrpc":"2.0","error":{"code":-32600,"message":"Invalid request"},"id":1}

Have you tried with the jsonrpc field being the string "2.0" rather than the float 2.0?


To debug, on Linux, you can compare the output of both curl commands by:

  • either sniffing your loopback interface lo,

  • or setting up some kind of “man in the middle”

    1. On one shell, run

      socat TCP-LISTEN:4242,reuseaddr,fork TCP:localhost:17853
      
    2. run your rust program targeting the port 4242 instead of 17853

    3. Observe the output of socat

  • or now that I come to think of it, you can just create in your project folder a dump_args.sh script, that you make executable (chmod +x ./dump_args.sh), with the following contents:

    #!/bin/bash
    for arg in "$@"; do echo $arg; done
    

    And run your rust program spawning the command ./dump_args.sh instead of curl

1 Like

this works. Thanks