With clap is it possible to infer the subcommand from the executable name?

Hi,

this is my first question here and I hope I get it right and don't come across as overly stupid immediately.

I am currently working on an existing CLI tool which I hope to convert into a more generic version of what it currently is. This is mostly so I learn the ropes with Rust.

I was scouring the documentation for clap (I'm using 3.0.0-beta.2 as per the README) and wasn't able to find what I am looking for.

With clap subcommands by default seem to behave similar to how git <subcommand> works and I think I even saw that example mentioned. I would like for that to work as well. However, if the binary exists under a basename that corresponds to a subcommand, I'd like it to behave as if I had invoked basename subcommand, where basename is the name of the binary (minus .exe file extension on Windows). On unixoid systems that could be in the form of a hardlink or a symlink and on Windows it could be a hardlink.

In essence I'd like it to behave similar to how Busybox behaves. I found and briefly looked at Rustybox but that appears to be mostly an automatically translated version of the C version of Busybox, which doesn't help me figure out how to do it in Rust, though.

I am aware that under Linux conventionally I'd be able to look at /proc/self/exe and in Win32 I could use GetModuleFileName to read the path to the binary that started the process. Is there a more cross-platform way to do that in Rust itself, or is this an example of having to implement this for each individual platform within a crate?

Thanks in advance for any insights you can offer.

I always read argv[0] on both Linux and Windows and never had any problems?

1 Like

Thanks AndreKR. So I take it clap has no such feature and you are suggesting to use the manual approach?

I just wondered if there is any advantage to using such an unusual (for me) approach.

Unfortunately I'm not too familiar with clap.

Ah, I see. Well, I guess I hadn't even considered argv[0] as that's conventionally part of the C runtime on any given system. But it's a fair point.

Thanks in any case for taking the time to respond.

You mean? :

use std::env;
use std::path::Path;

fn main()  {
    let args: Vec<String> = env::args().collect();
    let executable = Path::new(&args[0]).file_name().unwrap().to_str().unwrap();
    println!("I am: {}", executable);
    ...
}

Man that is hard work!

The Vec seems a bit complicated, just do env::args().next().unwrap().

Iā€™d probably write it like this:

use std::env;
use std::path::Path;

fn cmd_name()->Option<String> {
    Some( Path::new(&env::args().next()?)
               .file_stem()?
               .to_string_lossy()
               .into_owned()
    )
}

fn main()  {
    println!("I am: {}", cmd_name().unwrap());
    /* ... */    
}

Cool.

Sorry I'm old school. I do everything with arrays, for loops and indexing :slight_smile:

Thanks for the multiple ways. @ZiCog arguably this isn't hard for you. But everything is hard until it gets easy. And I am still in that phase where it hasn't gotten easy. So thanks for lending a hand!

And moreover thanks to everyone for providing variations on what I am trying to do. This is the best way of learning: reading code, wondering what its parts do and then reading up on those points.

(I'll test them one by one before marking one as the solution. But not tonight :grin:)

Using env::args() to pass to Path will roundtrip an OsString through String (and will panic in the case of non-unicode). Instead use env::args_os().

Oh, it's not easy.

You got me curious an that is what I came up with after a quick run through the examples in the docs I found.

1 Like

So putting it all together I now have:

use std::env;
use std::path::Path;

fn cmd_name()->Option<String> {
    Some( Path::new(&env::args_os().next()?)
               .file_stem()?
               .to_string_lossy()
               .into_owned()
    )
}

fn main() -> Result<()> {
    println!("I am: {}", cmd_name().unwrap());
    ...
}

Excellent!

clap::AppSettings::NoBinaryName looks like it might be useful:

Specifies that the parser should not assume the first argument passed is the binary name.

2 Likes