How to get the command behind std::process::Command in Rust? - rust

I'm passing around instances of std::process::Command. Before executing a command I'd like to log the entire command. For instance, if I'm given a command instance that has been constructed like that:
let command = Command::new("sh")
.arg("-c")
.arg("echo hello")
I would like to write a log message like:
Executing command: 'sh' '-c' 'echo hello'
The API looks quite limited though. Is there a way to accomplish that?

Debug is implemented for Command.
use std::process::Command;
fn main() {
let mut command = Command::new("ls");
command.arg("-l");
println!("{:?}", command);
}
Output: "ls" "-l"

You would like to get access to private fields in the command struct. Private fields are not accessible by design.
However, when a Debug trait has been implemented for the struct, the private members are 'printed' using the {:?} format option.
To access those private members programmatically, use the format!() macro. This returns a std::String and accepts the {:?} formatting option. This only works because the Debug trait has been implemented for Command.
fn main() {
let mut command = Command::new("ls");
command.arg("-l");
let command_string: String = std::format!("{:?}", command);
// Print the command_string to standard output
println!("cmd: {}", command_string);
// split the command string accordinig to whitespace
let command_parts = command_string.split(' ');
// print the individual parts of the command_string
for (index, part) in command_parts.enumerate() {
println!("part[{}]: {}", index, part);
}
}
Output:
$> test_prog
cmd: "ls" "-l"
part[0]: "ls"
part[1]: "-l"
$>

Related

How to capture the content of stdout/stderr when I cannot change the code that prints?

I have a function foo that can't be modified and contains println! and eprintln! code in it.
fn foo() {
println!("hello");
}
After I call the function, I have to test what it printed so I want to capture the stdout/stderr into a variable.
I strongly recommend against doing this, but if you are using nightly and don't mind using a feature that seems unlikely to ever be stabilized, you can directly capture stdout and stderr using hidden functionality of the standard library:
#![feature(internal_output_capture)]
use std::sync::Arc;
fn foo() {
println!("hello");
eprintln!("world");
}
fn main() {
std::io::set_output_capture(Some(Default::default()));
foo();
let captured = std::io::set_output_capture(None);
let captured = captured.unwrap();
let captured = Arc::try_unwrap(captured).unwrap();
let captured = captured.into_inner().unwrap();
let captured = String::from_utf8(captured).unwrap();
assert_eq!(captured, "hello\nworld\n");
}
It's very rare that a function "cannot be changed", so I'd encourage you to do so and use dependency injection instead. For example, if you are able to edit foo but do not want to change its signature, move all the code to a new function with generics which you can test directly:
use std::io::{self, Write};
fn foo() {
foo_inner(io::stdout(), io::stderr()).unwrap()
}
fn foo_inner(mut out: impl Write, mut err: impl Write) -> io::Result<()> {
writeln!(out, "hello")?;
writeln!(err, "world")?;
Ok(())
}
See also:
How can I test stdin and stdout?
How to take ownership of T from Arc<Mutex<T>>?
How do I convert a Vector of bytes (u8) to a string?
Not sure if this would work on windows, but should work on unix like systems. You should replace the file descriptor to something you can read later. I don't think it is really easy.
I would suggest to use stdio_override which already does that for you using files. You can redirect it, then execute the function and the read the file content.
From the example:
use stdio_override::StdoutOverride;
use std::fs;
let file_name = "./test.txt";
let guard = StdoutOverride::override_file(file_name)?;
println!("Isan to Stdout!");
let contents = fs::read_to_string(file_name)?;
assert_eq!("Isan to Stdout!\n", contents);
drop(guard);
println!("Outside!");
The library also support anything that implements AsRawFd, through the override_raw call. Confirming that it will probably just work on unix.
Otherwise, you can check on the implementation on how it is done internally, and maybe you could bypass a writer instead of a file somehow.
Shadow println!:
use std::{fs::File, io::Write, mem::MaybeUninit, sync::Mutex};
static mut FILE: MaybeUninit<Mutex<File>> = MaybeUninit::uninit();
macro_rules! println {
($($tt:tt)*) => {{
unsafe { writeln!(&mut FILE.assume_init_mut().lock().unwrap(), $($tt)*).unwrap(); }
}}
}
fn foo() {
println!("hello");
}
fn main() {
unsafe {
FILE.write(Mutex::new(File::create("out").unwrap()));
}
foo();
}

How to build a Rust library depended on a variable passed through command line? [duplicate]

Is it possible to define a constant in source code that can be overridden by a compiler flag? That is, something like setting a #define value in the C preprocessor with the -D key=val option to the compiler.
I have read about conditional compilation via the #[cfg(...)] attribute, but that only seems to support booleans. I want to allow the user to set the value of a constant during compilation.
Something like this:
#[from_cfg("max_dimensions")]
const MAX_DIMENSIONS: usize = 100_000;
Building on Lukas Kalbertodt's answer, you can get the environment variable as a constant number with some extra indirection, namely by using a build script.
build.rs
use std::{env, fs::File, io::Write, path::Path};
fn main() {
let out_dir = env::var("OUT_DIR").expect("No out dir");
let dest_path = Path::new(&out_dir).join("constants.rs");
let mut f = File::create(&dest_path).expect("Could not create file");
let max_dimensions = option_env!("MAX_DIMENSIONS");
let max_dimensions = max_dimensions
.map_or(Ok(10_000), str::parse)
.expect("Could not parse MAX_DIMENSIONS");
write!(&mut f, "const MAX_DIMENSIONS: usize = {};", max_dimensions)
.expect("Could not write file");
println!("cargo:rerun-if-env-changed=MAX_DIMENSIONS");
}
main.rs
include!(concat!(env!("OUT_DIR"), "/constants.rs"));
fn main() {
println!("The value is {} ({})", MAX_DIMENSIONS, MAX_DIMENSIONS + 1);
}
$ cargo run
The value is 10000 (10001)
$ MAX_DIMENSIONS=17 cargo run
The value is 17 (18)
$ MAX_DIMENSIONS=1 cargo run
The value is 1 (2)
No, you can't define constants (read: const bindings) with a compiler flag. But you can use the env! macro for something similar. It reads some environment variable at compile time.
const MAX_DIMENSIONS_RAW: &'static str = env!("MAX_DIMENSIONS");
Sadly, this returns a string and not an integer. Furthermore we can't yet call arbitrary functions (like parse) at compile time to calculate a constant. You could use lazy_static to achieve something similar:
lazy_static! {
static ref MAX_DIMENSIONS: usize = MAX_DIMENSIONS_RAW.parse().unwrap();
}
Of course you should add proper error handling. If your user doesn't need to define the environment variable, you can use option_env!.
With this approach, you can pass the setting at build time:
$ MAX_DIMENSIONS=1000 cargo build

How can I access a runtime-defined variable in stuctopt definitions?

I would like to be able to use the value of a variable (or better yet, the return of a function(arg)) as the about string for a CLI program defined using structopt. The end goal is a fully localized CLI that detects the system language or an ENV var and loads up localized strings that get baked into the --help message, etc.
By default it uses the documentation comment:
/// My about string
#[derive(StructOpt)]
struct Cli {}
I've found I can pass a manually entered string instead:
#[derive(StructOpt)]
#[structopt(about = "My about string")]
struct Cli {}
That's one step closer, but what I really want to do is pass a variable:
let about: &str = "My about string";
#[derive(StructOpt)]
#[structopt(about = var!(about))]
struct Cli {}
That last block is pseudo-code because I don't know what syntax to use to achieve this. In the end, I'll need more than just a single string slice, but I figured that was a place to start.
How can I pass through values like this to structopt? Do I need to access the underlying clap interfaces somehow?
StructOpt just adds a derive macro and a corresponding trait over clap. The clap crate has a function to set the about message at runtime, so we just need to add that. If we look at how from_args works we can see that it creates the clap App struct before turning it into the user-defined struct.
Thus, to do what you want:
use structopt::StructOpt;
fn get_localized_about() -> &'static str {
"localized string"
}
#[derive(Debug, StructOpt)]
struct Foo {}
fn main() {
let foo = Foo::from_clap(&Foo::clap().about(get_localized_about()).get_matches());
println!("{:#?}", foo);
}

Executing `find` using `std::process::Command` on cygwin does not work

When I try to call the find command from a Rust program, either I get a FIND: Invalid switch or a FIND: Parameter format incorrect error.
find works fine from command line.
echo $PATH
/usr/local/bin:/usr/bin:/cygdrive/c/Windows/system32:/cygdrive/c/Windows:.....
The file I am searching for (main.rs) exists.
use std::process::{Stdio,Command};
use std::io::{Write};
fn main() {
let mut cmd_find = Command::new("/cygdrive/c/cygwin64/bin/find.exe")
.arg("/cygdrive/c/cygwin64/home/*")
.stdin(Stdio::piped())
.spawn()
.unwrap_or_else(|e| { panic!("failed to execute process: {}", e)});
if let Some(ref mut stdin) = cmd_find.stdin {
stdin.write_all(b"main.rs").unwrap();
}
let res = cmd_find.wait_with_output().unwrap().stdout;
println!("{}",String::from_utf8_lossy(&res));
}
./find_cmdd.exe
thread '<main>' panicked at 'failed to execute process: The system cannot find the file specified. (os error 2)', find_cmdd.rs:12
I have also tried the following option,
let mut cmd_find = Command::new("find").....
for which I get FIND:Invalid switch error.
I do not have the luxury of renaming/copying the find.exe to another location.
"FIND:Invalid switch error" indicates this is NOT the cygwin find, but you are invoking the Windows one. To double check:
$ find -k
find: unknown predicate `-k'
$ /cygdrive/c/windows/system32/find -k
FIND: Parameter format not correct
Cygwin basically doesn't exist when you are running a program via Command. Executing a process uses the operating system's native functionality; in the case of Windows that's CreateProcessW.
That means that:
The PATH variable set by your cygwin shell may or may not mean anything when starting a process.
The directory structure with /cygdrive/... doesn't actually exist in Windows; that's an artifact.
All that said, you have to use Windows-native paths:
use std::process::{Stdio, Command};
use std::io::Write;
fn main() {
let mut cmd_find = Command::new(r#"\msys32\usr\bin\find.exe"#)
.args(&[r#"\msys32\home"#])
.stdin(Stdio::piped())
.spawn()
.unwrap_or_else(|e| panic!("failed to execute process: {}", e));
if let Some(ref mut stdin) = cmd_find.stdin {
stdin.write_all(b"main.rs").unwrap();
}
let res = cmd_find.wait_with_output().unwrap().stdout;
println!("{}", String::from_utf8_lossy(&res));
}
As a side note, I have no idea what piping standard input to find does; it doesn't seem to have any effect for me on Msys2 or on OS X...

How to send output to stderr?

One uses this to send output to stdout:
println!("some output")
I think there is no corresponding macro to do the same for stderr.
After Rust 1.19
As of Rust 1.19, you can use the eprint and eprintln macros:
fn main() {
eprintln!("This is going to standard error!, {}", "awesome");
}
This was originally proposed in RFC 1896.
Before Rust 1.19
You can see the implementation of println! to dive into exactly how it works, but it was a bit overwhelming when I first read it.
You can format stuff to stderr using similar macros though:
use std::io::Write;
let name = "world";
writeln!(&mut std::io::stderr(), "Hello {}!", name);
This will give you a unused result which must be used warning though, as printing to IO can fail (this is not something we usually think about when printing!). We can see that the existing methods simply panic in this case, so we can update our code to do the same:
use std::io::Write;
let name = "world";
let r = writeln!(&mut std::io::stderr(), "Hello {}!", name);
r.expect("failed printing to stderr");
This is a bit much, so let's wrap it back in a macro:
use std::io::Write;
macro_rules! println_stderr(
($($arg:tt)*) => { {
let r = writeln!(&mut ::std::io::stderr(), $($arg)*);
r.expect("failed printing to stderr");
} }
);
fn main() {
let name = "world";
println_stderr!("Hello {}!", name)
}
print! and println! are convenience methods for writing to standard output. There are other macros with the same formatting features available for different tasks:
write! and writeln! to write a formatted string to a &mut Writer
format! to just generate a formatted String
To write to the standard error stream, you can use e.g. writeln! like this:
use std::io::Write;
fn main() {
let mut stderr = std::io::stderr();
writeln!(&mut stderr, "Error!").unwrap();
}
It's done so:
use std::io::Write;
fn main() {
std::io::stderr().write(b"some output\n");
}
You can test it by sending the program output to /dev/null to ensure it works (I ignore the warning):
$ rustc foo.rs && ./foo > /dev/null
foo.rs:4:5: 4:42 warning: unused result which must be used, #[warn(unused_must_use)] on by default
foo.rs:4 io::stderr().write(b"some output\n");
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
some output
Similarly, one can do the following for stdout:
use std::io::Write;
fn main() {
std::io::stdout().write(b"some output\n");
}
I think this means println! is just a convenience: it's shorter and it also allows some formatting. As an example of the latter, the following displays 0x400:
println!("0x{:x}", 1024u)
While not answering the precise question, maybe it’s of interest that there’s a log crate which specifies an interface for leveled logging that other crates (e.g. env_logger) can fulfill.
The output of such logging will be sent to stderr, and there are additional benefits for users, such as specifying the log level.
This is how using such a logger could look like:
#[macro_use]
extern crate log;
extern crate env_logger;
fn main() {
env_logger::init().unwrap();
error!("this is printed by default");
}
(Example adapted from http://burntsushi.net/rustdoc/env_logger/index.html#example)
Goal
stderr!("Code {}: Danger, Will Robinson! Danger!", 42);
Notes
The other answers generate an unused import warning with the latest nightly, so here's a modern macro that Just Works TM.
Code
macro_rules! stderr {
($($arg:tt)*) => (
use std::io::Write;
match writeln!(&mut ::std::io::stderr(), $($arg)* ) {
Ok(_) => {},
Err(x) => panic!("Unable to write to stderr (file handle closed?): {}", x),
}
)
}

Resources