Skip to content
This repository has been archived by the owner on Apr 22, 2023. It is now read-only.

child_process.spawn fails on Windows given a space in both the command and an argument #25895

Closed
smrq opened this issue Aug 24, 2015 · 10 comments

Comments

@smrq
Copy link

smrq commented Aug 24, 2015

Repro: https://gist.github.com/smrq/f028b22bc748af9e68a7

The gist of this issue is that on Windows, child_process.spawn handles the command incorrectly when both it and one of its arguments contains a space. So, this works fine:

var spawn = require('child_process').spawn;

spawn('nospaces.cmd', ['arg with spaces']);
spawn('command with spaces.cmd', ['nospaces']);

But this yields 'command' is not recognized as an internal or external command, operable program or batch file.:

spawn('command with spaces.cmd', ['arg with spaces']);

Tested on Windows 7 with node 0.12.7.

@maxrimue
Copy link

I think this happens because Windows tries to execute command with spaces.cmd which won't work, because Windows doesn't recognise spaces like that. What happens if you instead try:

spawn('"command with spaces.cmd"', ['nospaces']);

@smrq
Copy link
Author

smrq commented Aug 25, 2015

Attempting to use spawn on a quoted command like that yields an ENOENT (it seems that the quotes are interpreted as a literal part of the filename, despite quotes being an invalid character in Windows filenames).

You can see this for yourself by updating the repro above with:

var cmd = spaceInCmd ? '"command file.cmd"' : 'commandfile.cmd';

Which yields:

C:\Code\node-junk>node test.js
-- No spaces --
bar bazquux
child exited with code 0

-- Space in command file name --
Error: spawn "command file.cmd" ENOENT

-- Space in argument --
bar "baz quux"
child exited with code 0

-- Space in both command file name and argument --
Error: spawn "command file.cmd" ENOENT

Note that spawn handles unquoted commands with spaces, if and only if none of the arguments also have a space. The line you posted works fine when the command file is unquoted. spawn deals with spaces fine in both positions independently, but not at the same time, which is what makes this behavior particularly odd.

@maxrimue
Copy link

But aside that problem, why would you even need spaces in the arguments? Could you provide an example?

@smrq
Copy link
Author

smrq commented Aug 25, 2015

Sure, an example command that works on the command line might look like:

> "C:\path to test runner\run-tests.cmd" --input "C:\path to source\tests\test-file.js"

Which would most naturally translate to:

spawn('C:\path to test runner\run-tests.cmd',
    ['--input', 'C:\path to source\tests\test-file.js'],
    { stdio: 'inherit' });

You can potentially hack around this in a number of ways, depending on the circumstances -- in this instance in particular, spawning with cwd: "C:\path to test runner" means you won't have a space in the command file:

spawn('run-tests.cmd',
    ['--input', 'C:\path to source\tests\test-file.js'],
    { stdio: 'inherit', cwd: 'C:\path to test runner' });

...apart from any effects of having a different working directory when executing the command file.

However, I have not been able to imagine a workaround that would work with this situation if the actual filenames contained spaces, not just the directories. Well, other than renaming the files, if that's within your control.

More specifically, I encountered this problem within gulp-protractor (relevant line), which is fixable with hacking cwd, but I don't have a general-case workaround because of the possibility of filenames with spaces in them.

@sbialobok
Copy link

I've similarly run into this issue using grunt-nunit-runner (line). Current workaround is to add the command to the path to avoid having to type "Program Files", but would be great if environment variables didn't need to be set for this to work.

@endquote
Copy link

Another sort-of workaround is to use "PROGRA~1" instead of "Program Files" in paths. This probably doesn't work in every Windows configuration, though.

@progmars
Copy link

progmars commented Feb 5, 2016

I could not figure out how to use spawn() to call git commit -m "Version update" on Windows but finally I found a strange workaround.

Maybe my issue is different because I'm using command = process.env.comspec || "cmd.exe" to be able to execute various kinds of executable files (exe, cmd etc.)

If I call spawn with arguments "commit", "-m", "\"Version update\"", (escaped quotes because git expects a quoted string comment) then my final function call arguments look as follows:

spawnSync("cmd.exe",
    [ "/s","/c","\"git\"","commit","-m","\"Version update\""], 
    {stdio: "inherit", windowsVerbatimArguments: true})

I get

 'git" commit -m "Version' is not recognized as an internal or external command,
operable program or batch file.

It seems, Windows treats entire string as a command, but has stripped the start quote and the end quote, so the command instead of being

"git" commit -m "Version update"

becomes

git" commit -m "Version update

If I remove windowsVerbatimArguments: true, then I get:
'\"git\"' is not recognized as an internal or external command, operable program or batch file.

If I try single quotes "'Version update'", then I get
error: pathspec 'update'' did not match any file(s) known to git.

At first I thought that the /s flag is the culprit, but removing it didn't help at all.

It turned out that /s strips redundant quotes only inside the string but the outer quotes are never preserved. MSDN docs say:

/S    Strip " quote characters from command.
         If command starts with a quote, the first and last quote chars in command
         will be removed, whether /s is specified or not.

So, finally it came to me that I should always enforce additional quotes like this:

allArgs = [
            "/s", // leave quotes as they are
            "/c", // run and exit
            // !!! order of c and s is important - c must come last!!!
            '"', // enforce starting quote
            command // command itself. Notice that you'll have to pass it quoted if it contains spaces
        ].concat(args)
       .concat('"'); // enforce closing quote

// switch the command to cmd shell instead of the original command
command = process.env.comspec || "cmd.exe";

var reslt = spawnSync(command, allArgs, {stdio: "inherit", windowsVerbatimArguments: true});

Now finally I can execute files on paths with spaces.

Later I found superspawn library, which is using similar approach:
https://github.com/MarcDiethelm/superspawn/blob/master/lib/index.js

Yeah, Windows is weird. But I guess this workaround with piping everything through cmd shell and enforcing additional quotes around entire command string should work with any command, theoretically.

I've yet to port my node.js scripts to Linux soon, so I'll see what challenges it will bring...

@s100
Copy link

s100 commented Jun 22, 2016

I see exactly the same issue in Node.js v5.10.1, but I see that this is the node-v0.x-archive repository. Should a separate issue be raised somewhere else, or does it possibly already exist?

@cjihrig
Copy link

cjihrig commented Jun 22, 2016

@s100
Copy link

s100 commented Jun 22, 2016

Raised.

@Trott Trott closed this as completed Apr 22, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants