1

I'm writing a CLI app in Rust and need to run commands differently depending on if it is being run in CMD or Powershell. There's this SO question that gives a command "(dir 2>&1 *`|echo CMD);&<# rem #>echo POWERSHELL" to run to output "CMD" or "POWERSHELL", but I can't run that as a Command in Rust.

This code returns the following error: Error { kind: NotFound, message: "program not found" }

let output = Command::new("(dir 2>&1 *`|echo CMD);&<# rem #>echo POWERSHELL")
        .output();

The problem is that some CMD/PowerShell commands don't work from Rust's std::process::Command and need to be called directly with CMD or PowerShell, but without that command, I can't seem to figure out how to detect the shell.

Idk if there's another way with Rust to determine what Windows shell is being ran, but any consistent method would also work for what I have in mind.

Update: To clarify my use case, I'm writing a program to set environment variables temporarily and run a given command provided by the user.

# sets $Env:ENV1=VALUE, then runs the command "dir" with args ["env:"]
temporary-env -e ENV1=VALUE1 dir -- env:

This example doesn't needs to determine the shell type, but other commands like dir have different syntax for args and parameters based on which shell is used, so I want to replicate that behavior based on which shell.

Mason
  • 15
  • 4
  • 1
    Why don't just run the command directly from your specified shell `cmd /c command` or `powershell -c command`? No need to care about it anymore – phuclv Jul 30 '22 at 01:49
  • The CLI app I'm using lets you pass in commands and run them, so I want the commands to function as if they were in that shell. I want the stdio to function the same way it would when run in that shell. – Mason Jul 30 '22 at 02:18
  • `cmd` or `powershell` **are** commands, just pass the whole thing into anything that expects a command – phuclv Jul 30 '22 at 02:41
  • The issue I run into is that cmd and powershell commands require different syntax. I'll update the question to try to explain why I think I need to differentiate for my use case. – Mason Jul 30 '22 at 02:56
  • obviously. If you use cmd then you must provide a command in cmd syntax. Same to powershell. Who tells you to run a cmd command in powershell? By running only a single shell you don't need to care about the syntax, just stick to that shell's syntax – phuclv Jul 30 '22 at 02:59
  • 3
    @Mason I get what you are trying to do, I think, but I think Rust is the wrong language for it. As soon as you enter a Rust program, you loose all information about what context it is being run in. There is no reliable way of doing what you want to achieve here. You could look it up [heuristically](https://www.cyberciti.biz/tips/how-do-i-find-out-what-shell-im-using.html). Either way, I think it will be rather flaky and error prone, not just finding it out, but all the rest as well. For example, running an interactive app like `vim` through your program. – Finomnis Jul 30 '22 at 09:15
  • There are programs that do similar things, like `screen` or `tmux`, but even they are flakey at times. – Finomnis Jul 30 '22 at 09:17
  • Have you considered `Command::new("program").env("ENV1", "value")`? – Mathias R. Jessen Jul 30 '22 at 10:44
  • @Finomnis That explanation makes a lot of sense, it just seems to weird that there's no shell-specific artifacts left over once the rust program is ran. Thanks for the explanation! @MathiasR.Jessen That works for external commands like PATH, but doesn't work for shell specific commands. For instance, running echo (`Command::new("echo").output()`) returns a "program not found" error since it's a shell command, while something like `py` runs fine if it's in the PATH (`Command::new("py").arg("-V").output()`) – Mason Jul 31 '22 at 00:43
  • 2
    @Mason one of the design philosophies of rust crates is that you should be able to write them once and then compile them everywhere, including platforms that don't even exist yet. To prevent having a big jungle of platform specific libraries, and to make it easy to port rust to new targets. And for the most part, this works great. But the entire Rust standard library is created with this philosophy in mind, and therefore your usecase is hard to realize. – Finomnis Jul 31 '22 at 07:19

2 Answers2

2

You can execute commands that will only work in one shell (but not crash in the other) to determine which shell you are using. E. g.:

echo %PATH%

Will output the content of your PATH environment variable in CMD, but just the string %PATH% in PowerShell. Vice versa:

echo $env:Path

Will output the content of your PATH environment variable in PowerShell, but just the string $env:Path in CMD. So, depending on the output, you can make a guess on which shell you are using.

This may currently work between CMD and PowerShell, but as phuclv already stated, this is not a stable design for a software. What if the user chooses a third - yet unknown - shell?

stackprotector
  • 10,498
  • 4
  • 35
  • 64
1

Regardless of the platform, whether Windows, Linux, BSD... you never need to determine the current shell type. You decide the shell you want to use and pass a command in that syntax. If you need to run a PowerShell command just run

powershell "temporary-env -e ENV1=VALUE1 dir -- env:"
powershell "$env:MYVAR = "value"; somecommand"

powershell -c dir will always be run as a PowerShell command so the syntax and output will always be consistent. Similarly bash -c dir or zsh -c dir also runs a command in that shell

You can't detect the shell environment reliably because programs aren't always run from a terminal, and the parent can strip environment information when executing the child process. The only thing you can do is to walk the process tree to see if any ancestor is a known shell, but of course it won't always work

phuclv
  • 37,963
  • 15
  • 156
  • 475
  • I think there's a disconnect in what I'm looking for and what my question is coming off as. I'm trying to write a program in rust (temporary-env) that takes in parameters and runs a provided command and any extra parameters. In rust, the only way to do this is with [Command](https://doc.rust-lang.org/std/process/struct.Command.html), which can't certain commands such as `echo` without running it through a shell (powershell or cmd). I don't want hard code which shell, since users may use my program with cmd or powershell, so using `powershell -c` will force them to use different syntax in cmd. – Mason Jul 30 '22 at 04:29
  • @Mason that's not possible. For example your program can be executed from another GUI program that's not a shell so there's no shell information at all. Even if running from a shell it's easy to erase shell information. The user is expected to specify which shell they want to use – phuclv Jul 30 '22 at 04:34