Use variables as command arguments - rust

In a program I am trying to display the artwork of my currently playing spotify song using rust.
The code only works if I copy and paste the url into the argument, so I tried making a variable called arturl to use in a .arg(arturl). But that makes the code return nothing, and the arturl variable does return the correct value.
My code:
use std::process::{Command, Stdio};
fn main() {
let arturl = Command::new("playerctl")
.arg("metadata")
.arg("mpris:artUrl")
.stdout(Stdio::piped())
.output()
.expect("url failed");
let arturl = String::from_utf8(arturl.stdout).unwrap();
let picture = Command::new("chafa")
.arg("--size=30")
.arg(arturl)
.stdout(Stdio::piped())
.output()
.expect("picture failed");
let picture = String::from_utf8(picture.stdout).unwrap();
println!("{}", picture);
}

You should probably "clean" the string with str::trim:
use std::process::{Command, Stdio};
fn main() {
let arturl = Command::new("playerctl")
.arg("metadata")
.arg("mpris:artUrl")
.stdout(Stdio::piped())
.output()
.expect("url failed");
let arturl = String::from_utf8(arturl.stdout).unwrap().trim();
println!("{:?}", arturl);
let picture = Command::new("chafa")
.arg("--size=30")
.arg(arturl)
.stdout(Stdio::piped())
.output()
.expect("picture failed");
let picture = String::from_utf8(picture.stdout).unwrap();
println!("{}", picture);
}

Managed to fixed this with adding a simple arturl.pop to get rid of the newline at the end of the string
fixed code:
use std::process::{Command, Stdio};
fn main() {
let arturl = Command::new("playerctl")
.arg("metadata")
.arg("mpris:artUrl")
.stdout(Stdio::piped())
.output()
.expect("url failed");
let mut arturl = String::from_utf8(arturl.stdout).unwrap();
arturl.pop();
println!("{:?}", arturl);
let picture = Command::new("chafa")
.arg("--size=30")
.arg(arturl)
.stdout(Stdio::piped())
.output()
.expect("picture failed");
let picture = String::from_utf8(picture.stdout).unwrap();
println!("{}", picture);
}

Related

Sharing variable in two closures

I'm listening to user input in an gtk-rs input element. input.connect_changed triggers when the input changes and input.connect_activate triggers when Enter is pressed.
use gtk::prelude::*;
use gtk::{Application, ApplicationWindow};
use std::process::{Command, Output};
fn main() {
let app = Application::builder()
.application_id("com.jwestall.ui-demo")
.build();
app.connect_activate(build_ui);
app.run();
}
fn run_command(command: &str) -> Output {
Command::new("sh")
.arg("-c")
.arg(command)
.output()
.unwrap_or_else(|_| panic!("failed to execute {}'", command))
}
fn build_ui(app: &Application) {
let input = gtk::Entry::builder()
.placeholder_text("input")
.margin_top(12)
.margin_bottom(12)
.margin_start(12)
.margin_end(12)
.build();
let window = ApplicationWindow::builder()
.application(app)
.title("gtk-app")
.child(&input)
.build();
window.show_all();
input.connect_changed(|entry| {
let input_text = entry.text();
let command = format!("xdotool search --onlyvisible --name {}", input_text);
let window_id_output = run_command(&command);
if window_id_output.status.success() {
println!(
"stdout: {}",
String::from_utf8_lossy(&window_id_output.stdout)
);
} else {
println!(
"sterr: {}",
String::from_utf8_lossy(&window_id_output.stderr)
);
}
});
input.connect_activate(move |entry| {
let input_text = entry.text();
// // `xdotool windowactivate` doesn't produce any output
let command = format!("xdotool windowactivate {}", window_id_output);
let window_activate_output = run_command(&command);
println!("window_activate: {}", window_activate_output);
window.hide();
window.close();
});
}
I want to set window_id_output in input.connect_changed, then use it in input.connect_activate (in the xdotool windowactivate {} command).
How can I use window_id_output this way in these two closures?
Rust Playground
As Sven Marnach said, you can use Rc<RefCell<..>> to move data between closures.
The simplest example is probably this one, probably how the gtk event loop works anyways:
use std::rc::Rc;
use std::cell::RefCell;
fn main() {
let a = Rc::new(RefCell::new(0));
let a_ref = Rc::clone(&a);
let closure_1 = move || {
let mut a = a_ref.borrow_mut();
*a += 1;
println!("closure_1: {}", &a);
};
let a_ref = Rc::clone(&a);
let closure_2 = move || {
let a = a_ref.borrow();
println!("closure_2: {}", &a);
};
for _ in 1..10 {
closure_1();
closure_2();
}
}
For your specific case, see a reduced example below (based on your code):
use std::cell::RefCell;
use std::rc::Rc;
use gtk::prelude::*;
use gtk::{Application, ApplicationWindow};
fn main() {
let app = Application::builder()
.application_id("com.jwestall.ui-demo")
.build();
app.connect_activate(build_ui);
app.run();
}
fn process(s: &str) -> String {
format!("you entered '{}'", s)
}
fn build_ui(app: &Application) {
let input = gtk::Entry::builder()
.placeholder_text("input")
.margin_top(12)
.margin_bottom(12)
.margin_start(12)
.margin_end(12)
.build();
let window = ApplicationWindow::builder()
.application(app)
.title("gtk-app")
.child(&input)
.build();
window.show_all();
let shared_var = Rc::new(RefCell::new(String::new()));
let shared_var_ref = Rc::clone(&shared_var);
input.connect_changed(move |entry| {
let input_text = entry.text();
let mut shared = shared_var_ref.borrow_mut();
*shared = process(&input_text);
});
let shared_var_ref = Rc::clone(&shared_var);
input.connect_activate(move |_entry| {
let shared = shared_var_ref.borrow();
println!("{}", shared);
window.hide();
window.close();
});
}

Executing external commands in parallel and capturing the output in an array in Rust

I have the following while loop that runs generate_user_key for each of the file in the file_array, and outputs the result. I would like to parallelize this such that an array of the generated keys is returned, and the process is executed in parallel instead of sequential to make it faster.
use std::process::Command;
//file_array definition here
let mut i = 0;
while (i<100) {
let generated_key = Command::new("generate_user_key")
.arg(file_array[i])
.output()
.expect("generate_user_key command failed to start");
println!("stdout: {}", String::from_utf8_lossy(&generated_key.stdout));
i=i+1;
}
What is the best way to implement this in rust?
If you want to loop over the array items using rayon then you can simply create into_par_iter and work on array items
use std::process::Command;
use rayon::iter::{ParallelIterator, IntoParallelIterator};
fn main() {
let arr = [1, 2, 3, 4, 5];
let result: Vec<_> = arr.into_par_iter().flat_map(|value| {
let output = Command::new("sh")
.args(["-c", &format!("echo {}", value)])
.output()
.expect("failed to execute process");
println!("Index: {}, Output: {:?}", value, output.stdout);
output.stdout
});
println!("{:?}", result);
}
You can also use range to loop over and use the counter as array index
use std::process::Command;
use rayon::iter::{ParallelIterator, IntoParallelIterator};
fn main() {
let arr = [1, 2, 3, 4, 5];
let result: Vec<_> = (0..arr.len()).into_par_iter().flat_map(|idx| {
let output = Command::new("sh")
.args(["-c", &format!("echo {}", arr[idx])])
.output()
.expect("failed to execute process");
println!("Index: {}, Output: {:?}", idx, output.stdout);
output.stdout
});
println!("{:?}", result);
}
Example using thread
use std::thread;
use std::time::Duration;
fn main() {
let mut threads = vec![];
for idx in 0..arr.len() {
threads.push(thread::spawn(move || -> Vec<_> {
let output = Command::new("sh")
.args(["-c", &format!("echo -n {}", idx)])
.output()
.expect("failed to execute process");
println!("Index: {}, Output: {:?}", idx, output.stdout);
thread::sleep(Duration::from_millis(1));
output.stdout
}));
}
let result = threads.into_iter().flat_map(|c| c.join().unwrap()).collect::<Vec<_>>();
println!("{:?}", result);
}
This should be easy to do with rayon. E.g. something like this (untested since I don't have your generate_user_key):
use rayon::prelude::*;
let keys = (0..100).into_par_iter().map (|_| {
Command::new("generate_user_key")
.arg(file_array[i])
.output()
.expect("generate_user_key command failed to start")
.stdout
})
.collect::<Vec<_>>();
or better:
use rayon::prelude::*;
let keys = file_array.par_iter().map (|f| {
Command::new("generate_user_key")
.arg(f)
.output()
.expect("generate_user_key command failed to start")
.stdout
})
.collect::<Vec<_>>();
When all else fails, throw threads at the problem. It almost certainly isn't the correct approach, but it works.
let mut join_handles = Vec::new();
for _ in 0..100 {
join_handles.push(thread::spawn(|| {
let generated_key = Command::new("generate_user_key")
.arg(file_array[i])
.output()
.expect("generate_user_key command failed to start");
String::from_utf8_lossy(&generated_key.stdout)
}));
}
let outputs = join_handles.into_iter().map(Result::unwrap).collect::<Vec<_>>();
Edit: The correct solution is probably using Command::spawn to start the processes without blocking. The OS can then handle running them in parallel and you can then collect the outputs.

How to save command stdout to a file?

I'm writing a function that has a particular logging requirement. I want to capture the output from a Command::new() call and save it into a file. I'm just using echo here for simplicity's sake.
fn sys_command(id: &str) -> u64 {
let mut cmd = Command::new("echo")
.args(&[id])
.stdout(Stdio::piped())
.spawn()
.expect("failed to echo");
let stdout = cmd.stdout.as_mut().unwrap();
let stdout_reader = BufReader::new(stdout);
// let log_name = format!("./tmp/log/{}.log", id);
// fs::write(log_name, &stdout_reader);
println!("{:?}", stdout_reader.buffer());
cmd.wait().expect("failed to call");
id.parse::<u64>().unwrap()
}
How can I capture the output and save it to a file? I've made a playground here. My println! call returns [].
Even reading to another buffer prints the wrong value. Below, sys_command("10") prints 3. Here's an updated playground.
fn sys_command(id: &str) -> u64 {
let mut buffer = String::new();
let mut cmd = Command::new("echo")
.args(&[id])
.stdout(Stdio::piped())
.spawn()
.expect("failed to echo");
let stdout = cmd.stdout.as_mut().unwrap();
let mut stdout_reader = BufReader::new(stdout);
let result = stdout_reader.read_to_string(&mut buffer);
println!("{:?}", result.unwrap());
cmd.wait().expect("failed to draw");
id.parse::<u64>().unwrap()
}
What am I missing?
Instead of capturing the output in memory and then writing it to a file, you should just redirect the process's output to a file. This is simpler and more efficient.
let log_name = format!("./tmp/log/{}.log", id);
let log = File::create(log_name).expect("failed to open log");
let mut cmd = Command::new("echo")
.args(&[id])
.stdout(log)
.spawn()
.expect("failed to start echo");
cmd.wait().expect("failed to finish echo");
Even reading to another buffer prints the wrong value. Below, sys_command("10") prints 3.
This is unrelated — read_to_string() returns the number of bytes read, not the contents of them, and there are 3 characters: '1' '0' '\n'.

How can I use threads to run this code simultaneously in rust?

I have a rust program that creates temporary email addresses using the mail.tm API, and I want to use threads to create emails simultaneously, to increase the speed. However, what I have tried, only results in printing "Getting email.." x amount of times, and exiting. I am unsure what to do about this. Any help or suggestions are appreciated.
use json;
use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng};
use reqwest;
use reqwest::header::{HeaderMap, HeaderValue, ACCEPT, CONTENT_TYPE};
use std::{collections::HashMap, io, iter, vec::Vec};
use std::thread;
fn gen_address() -> Vec<String> {
let mut rng = thread_rng();
let address: String = iter::repeat(())
.map(|()| rng.sample(Alphanumeric))
.map(char::from)
.take(10)
.collect();
let password: String = iter::repeat(())
.map(|()| rng.sample(Alphanumeric))
.map(char::from)
.take(5)
.collect();
let body = reqwest::blocking::get("https://api.mail.tm/domains")
.unwrap()
.text()
.unwrap();
let domains = json::parse(&body).expect("Failed to parse domain json.");
let domain = domains["hydra:member"][0]["domain"].to_string();
let email = format!("{}#{}", &address, &domain);
vec![email, password]
}
fn gen_email() -> Vec<String> {
let client = reqwest::blocking::Client::new();
let address_info = gen_address();
let address = &address_info[0];
let password = &address_info[1];
let mut data = HashMap::new();
data.insert("address", &address);
data.insert("password", &password);
let mut headers = HeaderMap::new();
headers.insert(ACCEPT, HeaderValue::from_static("application/ld+json"));
headers.insert(
CONTENT_TYPE,
HeaderValue::from_static("application/ld+json"),
);
let res = client
.post("https://api.mail.tm/accounts")
.headers(headers)
.json(&data)
.send()
.unwrap();
vec![
res.status().to_string(),
address.to_string(),
password.to_string(),
]
}
fn main() {
fn get_amount() -> i32 {
let mut amount = String::new();
loop {
println!("How many emails do you want?");
io::stdin()
.read_line(&mut amount)
.expect("Failed to read line.");
let _amount: i32 = match amount.trim().parse() {
Ok(num) => return num,
Err(_) => {
println!("Please enter a number.");
continue;
}
};
}
}
let amount = get_amount();
let handle = thread::spawn(move || {
for _gen in 0..amount {
let handle = thread::spawn(|| {
println!("Getting email...");
let maildata = gen_email();
println!(
"Status: {}, Address: {}, Password: {}",
maildata[0], maildata[1], maildata[2]);
});
}
});
handle.join().unwrap();
}
Rust Playground example
I see a number of sub-threads being spawned from an outer thread. I think you might want to keep those handles and join them. Unless you join those sub threads the outer thread will exit early. I set up a Rust Playground to demonstrate ^^.
In the playground example, first run the code as-is and note the output of the code - the function it's running is not_joining_subthreads(). Note that it terminates rather abruptly. Then modify the code to call joining_subthreads(). You should then see the subthreads printing out their stdout messages.
let handle = thread::spawn(move || {
let mut handles = vec![];
for _gen in 0..amount {
let handle = thread::spawn(|| {
println!("Getting email...");
let maildata = gen_email();
println!(
"Status: {}, Address: {}, Password: {}",
maildata[0], maildata[1], maildata[2]);
});
handles.push(handle);
}
handles.into_iter().for_each(|h| h.join().unwrap());
});
handle.join().unwrap();

The disappearance of a variable if it is not displayed Rust

Please help me to understand why i need to display variable. I use ssh2 crate to create ssh connect.
My code is here:
use ssh2::Session;
use std::io::prelude::*;
use std::net::{TcpStream};
fn main() {
// Connect to the SSH server
let tcp = TcpStream::connect("192.168.1.251:22").unwrap();
let mut sess = Session::new().unwrap();
sess.set_tcp_stream(tcp);
sess.handshake().unwrap();
sess.userauth_password("root", "password").unwrap();
let mut s = String::new();
let last_stat = String::from("unknown");
loop {
let mut channel = sess.channel_session().unwrap();
channel.exec("systemctl is-active firewalld").unwrap();
channel.read_to_string(&mut s).unwrap();
if &s.to_string().trim() == &last_stat {
print!("stopped");
} else {
print!("{}",&s);
}
&s.clear();
std::thread::sleep(std::time::Duration::from_secs(1));
}
}
If the firewallв is stopped, nothing is displayed. But if I show the variable in the output, then the code works and display "unknown".
use ssh2::Session;
use std::io::prelude::*;
use std::net::{TcpStream};
fn main() {
// Connect to the SSH server
let tcp = TcpStream::connect("192.168.1.251:22").unwrap();
let mut sess = Session::new().unwrap();
sess.set_tcp_stream(tcp);
sess.handshake().unwrap();
sess.userauth_password("root", "password").unwrap();
let mut s = String::new();
let last_stat = String::from("unknown");
loop {
let mut channel = sess.channel_session().unwrap();
channel.exec("systemctl is-active firewalld").unwrap();
channel.read_to_string(&mut s).unwrap();
if &s.to_string().trim() == &last_stat {
print!("{}",&s);
} else {
print!("{}",&s);
}
&s.clear();
std::thread::sleep(std::time::Duration::from_secs(1));
}
}
Why i must display variable &s to code works?
many thanks.

Resources