Find the name of the current interactive shell in Rust

41 Views Asked by At

I'm trying the get the name of the current interactive shell that my command-line program is being executed on. In my terminal, I can run this command:

λ ps -p $$ -o 'comm='
zsh

And it correctly prints the name of the shell. If I run it with sh -c, same results:

λ sh -c "ps -p $$ -o 'comm='"
zsh

Trying it with Bash, same results:

λ bash
amir@shabani:~/Code/ami$ ps -p $$ -o 'comm='
bash
amir@shabani:~/Code/ami$ sh -c "ps -p $$ -o 'comm='"
bash

Now when I execute the same command in a Rust program, it prints sh.

use std::process::{Command, ExitCode};

pub fn shell() -> ExitCode {
    let output = Command::new("sh")
        .arg("-c")
        .arg("ps -p $$ -o 'comm='")
        .output()
        .expect("Failed to execute command");

    if output.status.success() {
        let shell_name = String::from_utf8_lossy(&output.stdout);
        println!("The current shell is: {}", shell_name.trim());
        ExitCode::SUCCESS
    } else {
        eprintln!("Error executing command");
        ExitCode::FAILURE
    }
}

No matter where I run the program, zsh or bash, it always prints sh. What's the problem?

1

There are 1 best solutions below

0
Chayim Friedman On

The easiest way to have something is to get the name of the parent process (the spawning process). This won't necessarily be the shell that spawned you: if you run the program via cargo run, for example, it will be Cargo. But if you run the program directly from the shell, it will be what you want.

You can use the sysinfo crate to get the parent process in a cross-platform manner:

fn main() {
    let system = sysinfo::System::new_with_specifics(
        sysinfo::RefreshKind::new().with_processes(sysinfo::ProcessRefreshKind::new()),
    );
    let my_pid = sysinfo::get_current_pid().expect("unable to get PID of the current process");
    let parent_pid = system
        .process(my_pid)
        .expect("no self process?")
        .parent()
        .expect("unable to get parent process");
    let parent_process = system
        .process(parent_pid)
        .expect("unable to get parent process");
    let parent_name = parent_process.name();

    println!("Spawning shell (or not): {parent_name}");
}

If you want more precise analysis, you can start from enumerating all living shell processes, and asking for their children recursively. If your process is in the list, that means it was spawned (directly or indirectly) from this shell.

Note that this only work if the shell is still alive; it won't work if it exited.