So I want to execute a command over ssh using ssh2::channel but that crate takes command as &str. I was up until now constructing a std::process::Command and then I would format!("{:?}", cmd) but it doesn't output the env variables I have set.
To be more precise, I have this:
pub fn execute_command(
address: &String,
username: &str,
command: &Command,
buf: Option<&Vec<u8>>,
) -> anyhow::Result<Vec<u8>> {
let tcp = TcpStream::connect(address)?;
let mut sess = Session::new()?;
sess.set_tcp_stream(tcp);
sess.handshake()?;
sess.userauth_agent(&username)?;
let mut channel = sess.channel_session()?;
let command = format!("{:?}", command);
trace!("channel acquired for {}", address);
channel.exec(&command)?;
}
The problem is that Command doesn't include set env variables when being printed out via Debug. Is there another way to extract it as string for invocation where env variables would be included?
There's no built-in method on Command to do that. You could write a function to make such a string though:
fn pretty_cmd(cmd: &Command) -> String {
format!(
"{} {:?}",
cmd.get_envs()
.map(|(key, val)| format!("{:?}={:?}", key, val))
.fold(String::new(), |a, b| a + &b),
cmd
)
}
println!("{}", pretty_cmd(&cmd)); // e.g. "PATH"=Some("/usr") "sh" "-c" "echo hello"
Alternatively you could use a wrapper struct and implement Debug on that:
use std::fmt;
struct PrettyCmd(pub Command);
impl fmt::Debug for PrettyCmd {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} {:?}",
self.0.get_envs()
.map(|(key, val)| format!("{:?}={:?}", key, val))
.fold(String::new(), |a, b| a + &b),
self.0
)
}
}
println!("{:?}", PrettyCmd(cmd)); // "PATH"=Some("/usr") "sh" "-c" "echo hello"
(try on playground)
The advantage of using a wrapper struct like this is that you can store a PrettyCmd wherever you currently store a Command, and the different Debug formatting will happen without needing an extra function call.
Related
I need to put some futures in a Vec for later joining. However if I try to collect it using an iterator, the compiler doesn't seem to be able to determine the type for the vector.
I'm trying to create a command line utility that accepts an arbitrary number of IP addresses, communicates with those remotes and collects the results for printing. The communication function works well, I've cut down the program to show the failure I need to understand.
use futures::future::join_all;
use itertools::Itertools;
use std::net::SocketAddr;
use std::str::from_utf8;
use std::fmt;
#[tokio::main(flavor = "current_thread")]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let socket: Vec<SocketAddr> = vec![
"192.168.20.33:502".parse().unwrap(),
"192.168.20.34:502".parse().unwrap(),];
let async_vec = vec![
MyStruct::get(socket[0]),
MyStruct::get(socket[1]),];
// The above 3 lines happen to work to build a Vec because there are
// 2 sockets. But I need to build a Vec to join_all from an arbitary
// number of addresses. Why doesn't the line below work instead?
//let async_vec = socket.iter().map(|x| MyStruct::get(*x)).collect();
let rt = join_all(async_vec).await;
let results = rt.iter().map(|x| x.as_ref().unwrap().to_string()).join("\n");
let mut rvec: Vec<String> = results.split("\n").map(|x| x.to_string()).collect();
rvec.sort_by(|a, b| a[15..20].cmp(&b[15..20]));
println!("{}", rvec.join("\n"));
Ok(())
}
struct MyStruct {
serial: [u8; 12],
placeholder: String,
}
impl fmt::Display for MyStruct {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let serial = match from_utf8(&self.serial) {
Ok(v) => v,
Err(_) => "(invalid)",
};
let lines = (1..4).map(|x| format!("{}, line{}, {}", serial, x, self.placeholder)).join("\n");
write!(f, "{}", lines)
}
}
impl MyStruct {
pub async fn get(sockaddr: SocketAddr) -> Result<MyStruct, Box<dyn std::error::Error>> {
let char = sockaddr.ip().to_string().chars().last().unwrap();
let rv = MyStruct{serial: [char as u8;12], placeholder: sockaddr.to_string(), };
Ok(rv)
}
}
This line:
let async_vec = socket.iter().map(|x| MyStruct::get(*x)).collect();
doesn't work because the compiler can't know that you want to collect everything into a Vec. You might want to collect into some other container (e.g. a linked list or a set). Therefore you need to tell the compiler the kind of container you want with:
let async_vec = socket.iter().map(|x| MyStruct::get(*x)).collect::<Vec::<_>>();
or:
let async_vec: Vec::<_> = socket.iter().map(|x| MyStruct::get(*x)).collect();
has experience with high level programming languages. I read the Rust book and now trying to survive and understand how the "things" in Rust works. I would love that someone explain what the heck is - Ok(()) and how to deal with it? My goal is to return result from function in to the variable where the output:
Finished dev [unoptimized + debuginfo] target(s) in 0.01s
Running `target/debug/rcp ./file/ aba`
Ok(
"/home/tomand/rcp",
)
Here is the full code:
use std::fs;
use std::env;
use serde_json;
use regex::Regex;
use std::path::Path;
fn determinate_file_size(file: &str) -> u64 {
fs::metadata(file).unwrap().len()
}
fn determinate_is_it_file_or_dirctory(arg: &str) -> &str {
let file = "File";
let dir = "Directory";
let re = Regex::new(r"/").unwrap();
if re.is_match(arg) {
return dir;
}
return file;
}
fn collect_user_arguments() -> Vec<String> {
env::args().collect()
}
fn check_if_arguments_count_valid(args: &Vec<String>) -> bool {
if args.len() == 3 {
return true
}
help();
return false
}
fn get_current_working_dir() -> Result<T> {
env::current_dir()
}
fn help() {
println!("Examples:");
println!("rcp [srcfile] [destfile]");
println!("rcp [srcdir]/[srcfile] [destdir]/[destfile]");
}
fn main() {
let WORKING_DIR = get_current_working_dir();
let args: Vec<String> = collect_user_arguments();
if check_if_arguments_count_valid(&args) {
let arg1 = &args[1];
let arg2 = &args[2];
println!("{:#?}", determinate_is_it_file_or_dirctory(&arg1));
}
}
Seems the compiler tried to give me some inspiration but eventually we miscommunicate in the end:
error[E0107]: this enum takes 2 generic arguments but 1 generic argument was supplied
--> src/main.rs:42:33
|
42 | fn get_current_working_dir() -> Result<T> {
| ^^^^^^ - supplied 1 generic argument
| |
| expected 2 generic arguments
EDIT:
I went with this approach:
fn get_current_working_dir() -> String {
let res = env::current_dir();
match res {
Ok(path) => path.into_os_string().into_string().unwrap(),
Err(_) => "FAILED".to_string()
}
}
It seems more practice is required to understand the Result type and how to manage it.
std::env::current_dir returns a std::io::Result<Pathbuf>, so you need to use that type in your wrapper method:
fn get_current_working_dir() -> std::io::Result<PathBuf> {
env::current_dir()
}
Playground
Other nitpick:
const is not a type so let WORKING_DIR: const = get_current_working_dir(); is wrong, just let WORKING_DIR = get_current_working_dir(); is enough.
I'm trying to figure out build a feature which requires reading the contents of a file into a futures::stream::BoxStream but I'm having a tough time figuring out what I need to do.
I have figured out how to read a file byte by byte via Bytes which implements an iterator.
use std::fs::File;
use std::io::prelude::*;
use std::io::{BufReader, Bytes};
// TODO: Convert this to a async Stream
fn async_read() -> Box<dyn Iterator<Item = Result<u8, std::io::Error>>> {
let f = File::open("/dev/random").expect("Could not open file");
let reader = BufReader::new(f);
let iter = reader.bytes().into_iter();
Box::new(iter)
}
fn main() {
ctrlc::set_handler(move || {
println!("received Ctrl+C!");
std::process::exit(0);
})
.expect("Error setting Ctrl-C handler");
for b in async_read().into_iter() {
println!("{:?}", b);
}
}
However, I've been struggling a bunch trying to figure out how I can turn this Box<dyn Iterator<Item = Result<u8, std::io::Error>>> into an Stream.
I would have thought something like this would work:
use futures::stream;
use std::fs::File;
use std::io::prelude::*;
use std::io::{BufReader, Bytes};
// TODO: Convert this to a async Stream
fn async_read() -> stream::BoxStream<'static, dyn Iterator<Item = Result<u8, std::io::Error>>> {
let f = File::open("/dev/random").expect("Could not open file");
let reader = BufReader::new(f);
let iter = reader.bytes().into_iter();
std::pin::Pin::new(Box::new(stream::iter(iter)))
}
fn main() {
ctrlc::set_handler(move || {
println!("received Ctrl+C!");
std::process::exit(0);
})
.expect("Error setting Ctrl-C handler");
while let Some(b) = async_read().poll() {
println!("{:?}", b);
}
}
But I keep getting a ton of compiler errors, I've tried other permutations but generally getting no where.
One of the compiler errors:
std::pin::Pin::new
``` --> src/main.rs:14:24
|
14 | std::pin::Pin::new(Box::new(stream::iter(iter)))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected trait object `dyn std::iter::Iterator`, found enum `std::result::Result`
Anyone have any advice?
I'm pretty new to Rust, and specifically Streams/lower level stuff so I apologize if I got anything wrong, feel free to correct me.
For some additional background, I'm trying to do this so you can CTRL-C out of a command in nushell
I think you are overcomplicating it a bit, you can just return impl Stream from async_read, there is no need to box or pin (same goes for the original Iterator-based version). Then you need to set up an async runtime in order to poll the stream (in this example I just use the runtime provided by futures::executor::block_on). Then you can call futures::stream::StreamExt::next() on the stream to get a future representing the next item.
Here is one way to do this:
use futures::prelude::*;
use std::{
fs::File,
io::{prelude::*, BufReader},
};
fn async_read() -> impl Stream<Item = Result<u8, std::io::Error>> {
let f = File::open("/dev/random").expect("Could not open file");
let reader = BufReader::new(f);
stream::iter(reader.bytes())
}
async fn async_main() {
while let Some(b) = async_read().next().await {
println!("{:?}", b);
}
}
fn main() {
ctrlc::set_handler(move || {
println!("received Ctrl+C!");
std::process::exit(0);
})
.expect("Error setting Ctrl-C handler");
futures::executor::block_on(async_main());
}
I am trying to learn Rust. I am following a book online which implements the unix program cat. Right now I trying to read the content of files passed as an argument like that cargo run file1.txt file2.txt but the program panics:
D:\rust\cat> cargo run .\src\test.txt
Compiling cat v0.1.0 (D:\rust\cat)
Finished dev [unoptimized + debuginfo] target(s) in 0.62s
Running `target\debug\cat.exe .\src\test.txt`
thread 'main' panicked at 'Box<Any>', src\main.rs:12:28
this is my program:
use std::env;
use std::fs::File;
use std::io;
use std::io::prelude::*;
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() > 1 {
match read_file(&args) {
Ok(content) => println!("{}", content),
Err(reason) => panic!(reason),
}
}
}
fn read_file(filenames: &Vec<String>) -> Result<String, io::Error> {
let mut content = String::new();
for filename in filenames {
let mut file = File::open(filename)?;
file.read_to_string(&mut content)?;
}
Ok(content)
}
Can anyone explain what I am missing here?
The first element of the Args iterator returned by std::env::args is tipically the path of executable (see the docs
for more details).
The error arises because you do not skip the first arg: the program binary is not a sequence of valid UTF-8 bytes.
The apparently non sense error thread 'main' panicked at 'Box<Any>' is because panic! is not used with the same arguments of
the format! syntax.
use std::env;
use std::fs::File;
use std::io;
use std::io::prelude::*;
fn main() {
for filename in env::args().skip(1) {
match read_file(filename) {
Ok(content) => println!("{}", content),
Err(reason) => panic!("{}", reason),
}
}
}
fn read_file(filename: String) -> Result<String, io::Error> {
let mut content = String::new();
let mut file = File::open(filename)?;
file.read_to_string(&mut content)?;
Ok(content)
}
Is there a readily available way to convert ip addresses (both v4 and v6) from binary to text form in Rust (an equivalent to inet_ntop)?
Examples:
"3701A8C0" converts to "55.1.168.192",
"20010db8000000000000000000000001" converts to "2001:db8::1".
inet_ntop takes as input a struct in_addr* or a struct in6_addr*. The direct equivalent of those structs in Rust are Ipv4Addr and Ipv6Addr, both of which implement the Display trait and can therefore be formatted easily to text or printed:
let addr = Ipv4Addr::from (0x3701A8C0);
assert_eq!(format!("{}", addr), String::from ("55.1.168.192"));
println!("{}", addr);
AFAIK, there is not a direct conversion, but you can do that with from_str_radix, and then with the conversion of an ip from a numeric type:
use std::{
error::Error,
io,
net::{IpAddr, Ipv4Addr, Ipv6Addr},
str::FromStr,
};
fn convert(s: &str) -> io::Result<IpAddr> {
if let Ok(u) = u32::from_str_radix(s, 16) {
Ok(Ipv4Addr::from(u).into())
} else if let Ok(u) = u128::from_str_radix(s, 16) {
Ok(Ipv6Addr::from(u).into())
} else {
Err(io::Error::new(io::ErrorKind::InvalidData, "Invalid input"))
}
}
fn main() -> Result<(), Box<dyn Error>> {
let ip = convert("3701A8C0")?;
assert_eq!(ip, IpAddr::from_str("55.1.168.192")?);
let ip = convert("20010db8000000000000000000000001")?;
assert_eq!(ip, IpAddr::from_str("2001:db8::1")?);
Ok(())
}
If you already know that it is, for example, and IPV4, this is a one-liner:
use std::{
error::Error,
net::{IpAddr, Ipv4Addr},
str::FromStr,
};
fn main() -> Result<(), Box<dyn Error>> {
let ip = u32::from_str_radix("3701A8C0", 16).map(Ipv4Addr::from)?;
assert_eq!(ip, IpAddr::from_str("55.1.168.192")?);
Ok(())
}