Passing variables into command line arguments - rust

I am trying to pass a variable as a part of a bash command in my Rust program.
let path : String = "/home/directory/".to_string();
let _command = Command::new("/bin/bash")
.arg("-c")
.arg("mv somefile.txt $path")
.stdout(Stdio::piped())
.spawn()
.expect("Cannot be executed")
.wait();
This program compiles correctly but the designated variable $path under Command::new() is not identical to the string variable provided above. Would like to know how to pass variables from the Rust environment directly into the argument.

$path as written would be a shell or environment variable. You can pass Rust variables with arg. The easiest and safest way to do this is to get rid of the bash -c invocation and call mv directly. Then you can pass &path as a separate argument:
let path = "/home/directory/".to_string();
let exit_code = Command::new("mv")
.arg("somefile.txt")
.arg(&path)
.spawn()
.expect("Cannot be executed")
.wait()
.expect("Wait failed");
Playground
If you did need the explicit shell invocation for some reason, the naïve approach might be to use manual string building, say with format!, to build a shell command:
// Unsafe! Do not build commands by hand.
let exit_code = Command::new("sh")
.arg("-c")
.arg(&format!("mv somefile.txt {path}"))
...
Don't do that! It is unsafe in the same way that building SQL strings by hand leads to SQL injection vulnerabilities. If path were a file name with spaces it would be split into several file names. Worse, an attacker could inject a path like ; rm -rf / and wipe out your server.
The safe way to pass arguments is still using separate arg invocations. The trick is to reference them as "$1", "$2", etc., in the shell command. This method ensures that spaces, quotes, and other tricky characters won't cause problems.
let exit_code = Command::new("sh")
.arg("-c")
.arg("mv somefile.txt \"$1\"")
.arg("sh") // $0
.arg(&path) // $1
.spawn()
.expect("Cannot be executed")
.wait()
.expect("Wait failed");
Playground
Also, don't use let _command =. That would assign the final Result to a variable without checking whether it was successful or not. It's a good habit to always check Results. You can see I added a second expect call in both of the snippets above.
But then, calling unwrap and expect too often is another bad habit. Whenever possible you should aim to propagate errors. The ? operator makes this easy. It's shorter and nicer for callers:
fn main() -> io::Result<()> {
let path = "/home/directory/".to_string();
let exit_code = Command::new("mv")
.arg("somefile.txt")
.arg(&path)
.spawn()?
.wait()?;
Ok(())
}
Playground

Related

rust clap How to specify the type of command line arguments

I am using rust's clap to investigate command line arguments. is it possible to specify which one will be input as a command line argument in clap?
Is it possible to specify the type like type=int in the following python code?
parser = argparse.ArgumentParser()
parser.add_argument("--some_string", type=str, required=True, )
What I am looking for is to specify the type of the command line arguments at the stage of defining them, as in the above python code.
I would like to use clap to do the following
./target/release/~~ --i32_args 12 --f32_args -32.5
Thus, if the type and variable match, we want it to work without error.
./target/release/~~ --i32_args -32 --f32_args 1
In this case, I want to cause an immediate panic and terminate the operation.
What I am looking for is something that does not use methods such as value_of, but judges the type of the command line argument at the time the command is executed, and executes it if the type is correct, or panic immediately if the type is wrong.
It is my understanding that in value_of_t, the type is not determined until the data is retrieved. Therefore, I think it is different from what I am looking for.
A bit late to the party, but you can use value_parser with Clap v4
use clap::{arg, value_parser, Command}; // Clap v4
use std::net::Ipv4Addr;
fn main() {
let matches = Command::new("clap-test")
.arg(
arg!(--ip <VALUE>)
.default_value("127.0.0.1")
.value_parser(value_parser!(Ipv4Addr)),
)
.arg(
arg!(--port <VALUE>)
.required(true)
.value_parser(value_parser!(u16)),
)
.arg(arg!(--f32 <VALUE>).value_parser(value_parser!(f32)))
.get_matches();
println!(
"ip {:?}, port {:?}, float {:?}",
matches.get_one::<Ipv4Addr>("ip").expect("required"),
matches.get_one::<u16>("port").expect("required"),
matches.get_one::<f32>("f32"),
);
}

Why do I need to declare a variable at each iteration of a loop to use it?

I am learning Rust from The Book and I just finished the first exercise, the guessing game.
I use cargo to build and run my little crate.
$ cargo --version
cargo 1.37.0 (9edd08916 2019-08-02)
$ rustc --version
rustc 1.37.0 (eae3437df 2019-08-13)
Everything run fine, including release mode. Nevertheless, I do not understand a the following behaviour of Rust: I have to redeclare the variable which contains the user input at each iteration of the loop.
Since the exercise is guided step-by-step, my code is the same that the one from the book. Nevertheless, the code from the book is the following:
loop {
// Some code to display instructions.
// Reallocate a new string at each iteration!
let mut guess = String::new();
io::stdin().read_line(&mut guess)
.expect("Failed to read line");
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
// Some code to check if the player found the secret number.
}
Noticing this systematic reallocation, I moved the string declaration outside the loop:
// Allocate the string once.
let mut guess = String::new();
loop {
// Some code to display instructions.
io::stdin().read_line(&mut guess)
.expect("Failed to read line");
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
// Some code to check if the player found the secret number.
}
However, Rust did not appreciate this: at the second iteration of the loop, it panics every time.
Why can't I reuse the same mutable variable more than once? Do I not understand something?
EDIT: read_line does not clear the content of the previous input, but appends to it the following one.
Let's say the player enters 1 then 2, the final value of guess will be "1\n2\n".
However, trim() removes the "blank" characters at the beginning and the end of the string, leaving a \n in the middle: parse() panics!
Your code as-is compiles and runs fine on my setup (same version of rust). The panic must happen in the commented-out part of your code. Some comments, though: the scoping in your loop is tricky: guess at the top-half of the loop is the string declared outside the loop, and is the parsed integer in the second half.
More importantly, multiple calls to read_line being passed the same string appends to the string, which probably isn't your intention given the way you're parsing the string. Sprinkling in println!'s of your guess variables should be illuminating. Your code will probably be fixed if you add a guess.clear() on the string after you've parsed the number, but to do that, you'll probably want to rename the u32 guess.
As an aside, you might consider using a BufReader and the for line in reader.lines()) pattern described here.

Bytes written to a BufWriter aren't actually written

I'm trying to write a small Rust program that echoes lines on /dev/ttyS0 back to the sender:
fn echo_loop(device: &str) {
let f = File::open(device).unwrap();
let read = BufReader::new(&f);
let mut writer = BufWriter::new(&f);
read.lines().for_each(|l: Result<String, Error>| match l {
Ok(line) => {
let _ = writer.write(line.as_bytes()).unwrap();
let _ = writer.flush();
println!("We should have written: {}", line);
},
Err(err) => println!("Err!: {:?}", err)
});
}
To test this code, I call the function with "/dev/tty" as its argument.
I would expect to be able to type lines on the console that get echoed after I press return, but I don't get an echo (beside from what I print with the println!(…) macro.
The part that reads the lines seems to work, else I couldn't println! what I type, but why don't I get the echo by writing to the writer?
This is the output of my code (note: The lines “Hello world!” and “That's what I get :-(” is the text I typed, not an output by the program):
Hello world!
We should have written: Hello world!
That's what I get :-(
We should have written: That's what I get :-(
As the docs state, File::open opens the file in read only mode.
When you try to write, you are writing to the BufWriter, which always succeeds. Once you flush, you are ignoring the Result, which means that even if the writing fails (which it does, since it's read-only), you are ignoring that error.
OpenOptions allows you to open a file both in read and write mode at the same time. Note that reading and writing at the same time can have weird consequences, like reading failing to produce anything after the first write, because the write replaces the entire file instead of inserting/appending your writes

How do I print output without a trailing newline in Rust?

The macro println! in Rust always leaves a newline character at the end of each output. For example
println!("Enter the number : ");
io::stdin().read_line(&mut num);
gives the output
Enter the number :
56
I don't want the user's input 56 to be on a new line. How do I do this?
It's trickier than it would seem at first glance. Other answers mention the print! macro but it's not quite that simple. You'll likely need to flush stdout, as it may not be written to the screen immediately. flush() is a trait method that is part of std::io::Write so that needs to be in scope for it to work (this is a pretty easy early mistake).
use std::io;
use std::io::Write; // <--- bring flush() into scope
fn main() {
println!("I'm picking a number between 1 and 100...");
print!("Enter a number: ");
io::stdout().flush().unwrap();
let mut val = String::new();
io::stdin().read_line(&mut val)
.expect("Error getting guess");
println!("You entered {}", val);
}
You can use the print! macro instead.
print!("Enter the number : ");
io::stdin().read_line(&mut num);
Beware:
Note that stdout is frequently line-buffered by default so it may be necessary to use io::stdout().flush() to ensure the output is emitted immediately.
Don't use the print/ln!-macros. Use write/ln!-macros.
It is more verbose, but print/ln! are problematic for using in command-line apps where their output might get piped or redirected to other apps, which is characteristic for Unix environments.
There is used always the same (only once requested and "buffered") stdout-device, but the stdout-device of the system is changed for piping/redirecting. So for each output to stdout you have to request the current stdout-device (std::io::stdout()). This can be done with write/ln!-macros.
So to say print/ln! is broken and there is an open issue since years.

Rust 0.9 -- Reading a file?

Here's what I'm trying to do: open all the command line arguments as (binary) files and read bytes from them. The constantly changing syntax here is not conductive to googling, but here's what I've figured out so far:
use std::io::{File, result};
use std::path::Path;
use std::os;
fn main() {
let args = os::args();
let mut iter = args.iter().skip(1); // skip the program name
for file_name in iter {
println(*file_name);
let path = &Path::new(*file_name);
let file = File::open(path);
}
}
Here's the issue:
test.rs:44:31: 44:41 error: cannot move out of dereference of & pointer
test.rs:44 let path = &Path::new(*file_name);
I've hit a brick wall here because while I'm fine with pointers in C, my understanding of the different pointer types in rust is practically non-existent. What can I do here?
Try &Path::new(file_name.as_slice())
Unfortunately, due to the trait argument that Path::new() takes, if you pass it a ~str or ~[u8] it will try and consume that type directly. And that's what you're passing with *file_name. Except you can't move out of a pointer dereference in Rust, which is why you're getting the error.
By using file_name.as_slice() instead (which is equivalent, in this case, to (*file_name).as_slice(), but Rust will do the dereference for you) it will convert the ~str to a &str, which can then be passed to Path::new() without a problem.

Resources