call shell run pipeline failed in rust's Command.output - rust

I'm trying to get result from call shell and grep the result. But it failed, in shell, it works.
use std::process::Command;
fn main() {
let result = Command::new("sh")
.arg("-c")
.arg("last") // by this line it works
// .arg("last | grep 'still logged in'") // by this line, it will return 256 code
.output()
.expect("'last' command failed to start");
println!("{:?}", result);
}

https://doc.rust-lang.org/stable/std/process/struct.ExitStatus.html
An ExitStatus represents every possible disposition of a process. On Unix this is the wait status. It is not simply an exit status (a value passed to exit).
See I used wait(&status) and the value of status is 256, why?. grep returns 1 as no match was found, and this becomes an exist status of 256.
If you want the exit code, call output.status.code(). This correctly returns Some(1).

Related

Mapping errors from Command::new("/bin/bash") arguments

I am executing a bash command, for which I would like to catch an error when the move argument fails. Since there is one more command after move, my goal is to capture specifically whether the move operation got successfully executed to a status code and to break out of the entire Rust program.
Is there a way to accomplish this? I have provided below the source code.
let path : String = "/home/directory/".to_string();
let command = Command::new("bin/bash")
.arg("-c")
.arg("mv somefile1.txt /home/")
.arg("cp ~/somefile2.txt .")
.stdout(Stdio::piped())
.output();
bash -c doesn't accept two commands like that. You could try splitting it into two separate Commands:
Command::new("bash")
.arg("-c")
.arg("mv somefile1.txt /home/")
.status()?
.success() // bool
.then(|| ()) // convert bool to Option
.ok_or("mv failed")?; // convert Option to Result
Command::new("bash")
.arg("-c")
.arg("cp ~/somefile2.txt .")
.status()?
.success() // bool
.then(|| ()) // convert bool to Option
.ok_or("cp failed")?; // convert Option to Result
Or joining them into a single command with &&:
Command::new("bash")
.arg("-c")
.arg("mv somefile1.txt /home/ && cp ~/somefile2.txt .")
.status()?
.success() # bool
.then(|| ()) # convert bool to Option
.ok_or("failed")?; # convert Option to Result
Better yet, use native Rust functions:
mv → std::fs::rename
cp → std::fs::copy
~ → home::home_dir
This avoids the overhead of calling out to bash and is portable to non-Unix operating systems.
use std::fs;
use dirs::home_dir;
if let Some(home) = home_dir() {
fs::rename("somefile1.txt", home.join("somefile1.txt"))?;
fs::copy(home.join("somefile2.txt"), ".")?;
}

What are some good ways to implement a read line or a sleep timer in Rust

I've finished my CLI but it exits too quickly for people to use it, Anyone know how would I go towards implementing code in my main.rs without breaking the compiler lol.
I was thinking of maybe a for loop that prints , read and execute and then start again. Or maybe a read line function so it stays up long enough to output the display.
Where would you guys implement that ? Thanks!
use structopt::StructOpt;
mod cli;
mod task;
use cli::{Action::*, CommandLineArgs};
use task::Task;
fn main() {
// Get the command-line arguments.
let CommandLineArgs {
action,
todo_file,
} = CommandLineArgs::from_args();
// Unpack the todo file.
let todo_file = todo_file.expect("Failed to find todo file");
// Perform the action.
match action {
Add { text } => task::add_task(todo_file,
Task::new(text)),
List => task::list_tasks(todo_file),
Done { position } =>
task::complete_task(todo_file, position),
}
.expect("Failed to perform action")
}
From the example, it seems you're getting the arguments from the command line. If you instead wanted the program to wait for a user to enter some text, interpret that text as a command and run it, and then wait for input again, until the user exits the program, then you'll probably want https://doc.rust-lang.org/std/io/struct.Stdin.html or possibly a higher level crate such as https://docs.rs/rustyline/8.0.0/rustyline/
If you were using stdin directly, you can call io::stdin().read_line() which will wait until the user has entered a line of text and pressed enter before the function will return. You can then parse that string to get the action to perform. The input/parsing/action code can be surrounded in a loop {} and one of the actions can be an exit command that will exit the loop.

How to get the exit code of a Bash script running from inside a Rust program?

I've been trying to wrap my head around a few simple implementations of systems programming involving the ability to call Bash from C and Rust. I was curious if there was a way to modify the following statement, in Rust specifically, to allow me to get a return value of 4 from a Bash script that runs in the following manner:
let status = Command::new("pathtoscript").status().expect("failed to execute process");
Rust String stuff confuses me initially, but any combination of actions that leads to status granting me access to the value of 4 being returned to the parent process would be great. Thank you so much in advance for helping me. I have checked the Rust documentation but I haven't found anything for getting things BACK to the parent process, only to the child process.
I should say it goes without saying for my application writing to a file and reading from that file is not sufficient or secure enough.
If you need the exit code 4 use status.code() :
use std::process::Command;
fn main() {
let status = Command::new("./script.sh")
.status()
.expect("failed to execute process");
println!("{}", status.code().unwrap()); // 4
}
My script.sh file:
#!/bin/bash
# Will exit with status of last command.
# exit $?
# echo $?
# Will return 4 to shell.
exit 4
And see this too:
use std::process::Command;
let status = Command::new("mkdir")
.arg("projects")
.status()
.expect("failed to execute mkdir");
match status.code() {
Some(code) => println!("Exited with status code: {}", code),
None => println!("Process terminated by signal")
}

FD_ISSET returns 0 after FD_SET

I have the following code:
FD_SET(mc_sock, &readfds);
foo = FD_ISSET(mc_sock, &readfds); // returns 1
// Wait until some socket on the set is ready to be read
while(select (FD_SETSIZE,&readfds,NULL,NULL,ptv)) {
foo = FD_ISSET(mc_sock, &readfds); // returns 0
I add mc_sock to readfds and FD_ISSET returns 1 as expected. However later when inside while loop FD_ISSET returns 0 without calling FD_CLR.
The code jumps into the while when I run a MobileC server but there isn't any FD_CLR in the code runned.
I'm quite a newbie in sets and file descriptors and I haven't found out what's happening. Do you have an idea?
Thanks!
Second, third, and forth arguments of select(2) are in-out parameters, meaning the call modifies them to let you know about what events had happened upon return. This is why you need to re-arm the file descriptor sets before every call to select(2).
Also look into other de-multiplexing facilities like poll(2) and epoll(7).

How do I manually flush a pipe to a child process in node.js?

I am trying to run the a script on node.js which sequentially sends numbers to an executable a.out, which squares it and writes the result on its stdout. When that happens, the next number will be sent. Here's the code:
var spawn = require('child_process').spawn;
var p = spawn("./a.out",[],{"stdio":"pipe"});
p.stdout.setEncoding("utf8");
p.stdout.on("data",function(data){
var x = parseInt(data.trim()), y = Math.sqrt(x);
console.log("received",x);
if(++y===10) p.kill();
else {
console.log("sending",y);
p.stdin.write(y+"\n");
}
});
var start = 2;
console.log("sending",start);
p.stdin.write(start+"\n");
setTimeout(function(){ p.stdin.end(); },1000);
where a.out is the compiled version to the following C program:
#include<stdio.h>
int main(){
int x;
while(scanf("%d",&x)!=EOF)
printf("%d\n",x*x);
return 0;
}
However, I am getting the following output:
sending 2
received 4
sending 3
events.js:72
throw er; // Unhandled 'error' event
^
Error: write after end
Changing the milliseconds value in setTimeout only delays the error. Apparently, the data that I'm trying to send to a.out is being buffered and sent only when I terminate the pipe. How do I flush it manually?
I have been working on this all day.
The problem is that the STDOUT of your spawned process needs to flush it's output buffer, otherwise it will sit there until it fills up, in which case your code won't execute again.
p.stdin.end() only serves to end the process, which by its very nature, allows the OS to clear up all buffers.
You can't do this from the node as this is not the owner of the output buffer.
It is annoying, but as long as you have control of the script, you can modify it there, perhaps allow it to take a command line option to set an autoflush?
Hope this is of some help.

Resources