linuxshellrust

Save the command run from rust cli tool into linux history


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.


Solution

  • 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.