I have a very simple rust cli application which reads a json file and list the commands available and user can navigate the list of commands using the keys w,a,s,d and can execute it using the enter key
I would like to persist the executed command in the linux command history so that i do not have to execute the cli tool again to run a command. How can i achieve this ?
Below is the code that i'm running to execute the command:
let mut command: Command;
if cfg!(target_os = "windows") {
command = Command::new("cmd");
command.arg("/C").arg(npm_command);
} else {
command = Command::new("sh");
command.arg("-c").arg(npm_command);
}
command
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit());
command
.spawn()
.expect("failed to spawn sh process")
.wait()
.expect("failed to wait for sh process");
My goal is when i use ctrl+r, i should be able to search it and execute the previously executed command.
I think you should modify your shell's history file directly from your Rust program. Here's what you can do:
use std::process::{Command, Stdio};
use std::env;
use std::fs::OpenOptions;
use std::io::Write;
fn execute_and_save_to_history(npm_command: &str) -> Result<(), Box<dyn std::error::Error>> {
// Run the command here normally first
let mut command = Command::new("sh");
command.arg("-c").arg(npm_command);
command
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit());
command
.spawn()?
.wait()?;
// Figure out which shell they're using and history file location
let shell = env::var("SHELL").unwrap_or_else(|_| String::from("/bin/bash"));
let history_file = if shell.contains("zsh") {
format!("{}/.zsh_history", env::var("HOME")?)
} else {
// Default to bash history !!!
format!("{}/.bash_history", env::var("HOME")?)
};
// Append the command to history file here
let mut file = OpenOptions::new()
.append(true)
.create(true)
.open(history_file)?;
if shell.contains("zsh") {
// Zsh needs timestamp format
writeln!(file, ": {}:0;{}", chrono::Utc::now().timestamp(), npm_command)?;
} else {
// Bash is simple :)
writeln!(file, "{}", npm_command)?;
}
Ok(())
}
You'll need to add the chrono crate to your Cargo.toml:
[dependencies]
chrono = "0.4"
I should mention that this approach has a small limitation - the command won't be immediately available in the current terminal session. The history file only gets re-read on shell startup or when you run history -r
. But this should be good enough for most use cases where you want to find the command later with Ctrl+R.
I tried a bunch of other approaches like using the history -s
command directly but that only affects the shell that runs the command (not the parent shell your app is launched from)
One final note - if you care about preserving all the history metadata (like timestamps in zsh), you might need to tweak the zsh history line format a bit more but this basic approach should work for most needs.