Open oprypin opened 4 years ago
Where the compiler use the shell interpreter, it can be refactored not to use it. For example by using environment variables (e.g. for link flags) and using an array for arguments.
shell: true
should raise on Windows.
shell: true
should not raise - cmd.exe
is the shell.
I think that passing arguments to shell: true
on windows should either ignore the arguments, or raise. I don't mind which.
As @j8r said though, the compiler can be modified to not use the shell and instead construct the args array without ${@}
The reason that people actually use shell: true
is to not have to split their command manually. I.e. `ls "foo bar"`
vs ["ls", "foo bar"]
.
My main plan now is to not support only args
and shell
at the same time:
raise NotImplementedError.new("Process with args and shell: true is not supported on Windows")
And shell: true
, I think, should not actually invoke any shell, just pass the command unmodified to CreateProcess
, as opposed to the normal mode which has to turn the array into a command string.
Because on Windows the thing you pass to a process is a single string with all args, possibly quoted, unlike on Unix where you pass an array.
If you disagree that there should be no support for implicitly invoking cmd.exe
, then note that if this occupies the shell: true
spot, then how can we satisfy the need for a mode where one just doesn't want to mess with the input string and also doesn't want any shell?
I should also note that for running commands with the `
operator it would be really nice if it didn't actually invoke cmd.exe every time. That fits nicely into what I said, as this just uses shell: true
.
If you disagree that there should be no support for implicitly invoking
cmd.exe
, then note that if this occupies theshell: true
spot, then how can we satisfy the need for a mode where one just doesn't want to mess with the input string and also doesn't want any shell?
Passing a single argument in args with shell: false
should be equivalent to that.
cmd.exe
is the shell on windows.
Passing a single argument in args with
shell: false
should be equivalent to that.
Oh no no, it shouldn't.
What do you mean, Process.new("my executable.exe")
or Process.new(args: ["my file.txt"])
? Either of these need to be safely quoted, not kept as is.
Ah I think you mean Process.new("my executable.exe", args: "\"my file.txt\"")
The topic is likely wider than only just shell
- revisiting Process
.
On thing is for differentiate commands and arguments is separating them with a --
to prevent potential issues. Here is the on-windows friendly logic: https://github.com/crystal-lang/crystal/blob/7e2e84042a393223660a216fbc5d0d5cd6bb80a5/src/process.cr#L403
@j8r Sure, the topic is wide, but the behavior of the other aspects is obvious, and already implemented for a future PR.
I don't think --
is relevant to the discussion though.
Maybe, I don't know how if Windows disambiguate command and arguments?
Process
is still very UNIX centric - fork
, chroot
, exec
.
We could do something similar as System::User
and System::Group
- extending the module with Windows/UNIX specific logic depending of the platform - here constructing arguments to pass to the shell interpreter: prepare_args
.
It may makes no sense to have both command
, args
and shell: true
on Windows - but it has to be kept for consistency with other platforms which makes the difference.
@j8r indeed. That is implemented in https://github.com/oprypin/crystal/compare/winapi
Not invoking cmd.exe
means that command prompt built-ins like echo
and dir
cannot be run by the backtick (unless the backtick itself calls cmd.exe
). If we have plain shell scripts everywhere, e.g. all those pkg-config
calls we have in the standard library, and those backticks call /bin/sh
, I don't think Windows should be an exception here.
POSIX has "the shell" (you just set a text file to be executable and the shell will be called) and Windows doesn't have "the shell". You arbitrarily picked cmd and not PowerShell, how did you make that choice?
%COMSPEC%
If we anticipate `
to be able to use an arbitrary "non-default" shell then POSIX shouldn't get a preferential treatment here.
I think it's perfectly sensible to use cmd.exe
for now as "the shell" of Windows, in the same vein that we use /bin/bash
elsewhere. If we find a reason to change that in the future, we can discuss that then, but it's better to have some sensible behavior to start with.
Not sure if this is an unexpected behavior or it's by design ;-)
from E:\scoop\global\apps\crystal\current\src\crystal\system\win32\process.cr:223 in 'spawn' from E:\scoop\global\apps\crystal\current\src\process.cr:256 in 'initialize' from E:\scoop\global\apps\crystal\current\src\process.cr:248 in 'new'
status = Process.run(command: "npm -v", shell: true)
p! status.success?
status = Process.run(command: "cmd", args: ["-c","npm -v"])
p! status.success?
# or
status = Process.run(command: "powershell", args: ["-NoProfile", "-NoLogo", "npm -v"])
p! status.success?
cmd
has no concept of args
, it must receive the whole command unquoted or else the quotes go to the command itself.
These two are equivalent and wrong because indeed rather than telling cmd
to run npm -v
, they tell it to run "npm -v"
Process.run("cmd", args: ["/c","npm -v"])
Process.run("cmd /c \"npm -v\"", shell: true)
The only correct way is
Process.run("cmd /c npm -v", shell: true)
it seems to work ;-)
I misunderstood that shell:true will automatically add sh -c
or cmd /c
to the command on different platforms
Let's examine the current implementation of
Process.new(command, args, shell: true)
.https://github.com/crystal-lang/crystal/blob/4401e90f001c975838b6708cc70868f18824d1e5/src/process.cr#L387-L405
For context, this allows you to pass a free-form command but also safely pass a list of arguments to it. E.g.
It relies on POSIX shell to expand the arguments for it, which is a nice implementation, however, this kind of puts us in an uncertain situation for also supporting Windows.
The problem is that the literal text
"${@}"
must be present in the specified command, butSo I think the best way forward needs to avoid relying on this specific character sequence. But I don't know how to do it without losing functionality.
And in general, what is
Process.run(command, args, shell: true)
even supposed to do on Windows? If we disregard the desire to avoid hacks, sure, I could just do a literal replacement from"${@}"
toargs
, safely quoted and joined. But hm.....Should this just raise "not implemented"? Maybe. But the compiler does rely on this feature in this crucial code: https://github.com/crystal-lang/crystal/blob/0bb3fe98d0b2698e646ab6eb24097eb6e4e8bafa/src/compiler/crystal/compiler.cr#L358 https://github.com/crystal-lang/crystal/blob/0bb3fe98d0b2698e646ab6eb24097eb6e4e8bafa/src/compiler/crystal/compiler.cr#L395
Side note: how Python does this:
So it has the equivalent functionality but it's never advertised as anything special, the POSIX shell just happens to do this if multiple args are passed.
And on Windows, ... Python just doesn't do anything useful. I guess it runs
cmd /C "command" "arg1" "arg2"
I also quite dislike that in Crystal the
command
andargs
are separate; usually it's more natural to have thecommand
be just the first item ofargs
. Yes, it's another topic, but note that it could make some things make more sense here.