Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lib: improve option shell on child_process #27327

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 41 additions & 20 deletions doc/api/child_process.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,11 @@ changes:
**Default:** `null`.
* `env` {Object} Environment key-value pairs. **Default:** `process.env`.
* `encoding` {string} **Default:** `'utf8'`
* `shell` {string} Shell to execute the command with. See
[Shell Requirements][] and [Default Windows Shell][]. **Default:**
`'/bin/sh'` on Unix, `process.env.ComSpec` on Windows.
* `shell` {boolean|string|Array} If `true`, runs `command` inside of a shell.
Uses `'/bin/sh'` on Unix, and `process.env.ComSpec` on Windows. A different
shell can be specified as a string. Or specify a shell with arguments.
See [Shell Requirements][] and [Default Windows Shell][].
**Default:** `false` (no shell).
* `timeout` {number} **Default:** `0`
* `maxBuffer` {number} Largest amount of data in bytes allowed on stdout or
stderr. If exceeded, the child process is terminated and any output is
Expand Down Expand Up @@ -241,6 +243,20 @@ async function lsExample() {
lsExample();
```

If `shell` is an `Array`, shell will be spawned with argument followed.

```js
const assert = require('assert');
const { exec } = require('child_process');
// https://docs.microsoft.com/windows-server/administration/windows-commands/cmd
// exec cmd with no argument will displays
// the version and copyright information of the operating system.
exec('echo foo bar', { shell: ['cmd'] }, (error, stdout) => {
assert(error == null);
assert(!stdout.includes('foo') && !stdout.includes('bar'));
});
```

### `child_process.execFile(file[, args][, options][, callback])`
<!-- YAML
added: v0.1.91
Expand Down Expand Up @@ -268,10 +284,11 @@ changes:
normally be created on Windows systems. **Default:** `false`.
* `windowsVerbatimArguments` {boolean} No quoting or escaping of arguments is
done on Windows. Ignored on Unix. **Default:** `false`.
* `shell` {boolean|string} If `true`, runs `command` inside of a shell. Uses
`'/bin/sh'` on Unix, and `process.env.ComSpec` on Windows. A different
shell can be specified as a string. See [Shell Requirements][] and
[Default Windows Shell][]. **Default:** `false` (no shell).
* `shell` {boolean|string|Array} If `true`, runs `command` inside of a shell.
Uses `'/bin/sh'` on Unix, and `process.env.ComSpec` on Windows. A different
shell can be specified as a string. Or specify a shell with arguments.
See [Shell Requirements][] and [Default Windows Shell][].
**Default:** `false` (no shell).
* `callback` {Function} Called with the output when process terminates.
* `error` {Error}
* `stdout` {string|Buffer}
Expand Down Expand Up @@ -431,10 +448,11 @@ changes:
* `serialization` {string} Specify the kind of serialization used for sending
messages between processes. Possible values are `'json'` and `'advanced'`.
See [Advanced Serialization][] for more details. **Default:** `'json'`.
* `shell` {boolean|string} If `true`, runs `command` inside of a shell. Uses
`'/bin/sh'` on Unix, and `process.env.ComSpec` on Windows. A different
shell can be specified as a string. See [Shell Requirements][] and
[Default Windows Shell][]. **Default:** `false` (no shell).
* `shell` {boolean|string|Array} If `true`, runs `command` inside of a shell.
Uses `'/bin/sh'` on Unix, and `process.env.ComSpec` on Windows. A different
shell can be specified as a string. Or specify a shell with arguments.
See [Shell Requirements][] and [Default Windows Shell][].
**Default:** `false` (no shell).
* `windowsVerbatimArguments` {boolean} No quoting or escaping of arguments is
done on Windows. Ignored on Unix. This is set to `true` automatically
when `shell` is specified and is CMD. **Default:** `false`.
Expand Down Expand Up @@ -749,10 +767,11 @@ changes:
**Default:** `'buffer'`.
* `windowsHide` {boolean} Hide the subprocess console window that would
normally be created on Windows systems. **Default:** `false`.
* `shell` {boolean|string} If `true`, runs `command` inside of a shell. Uses
`'/bin/sh'` on Unix, and `process.env.ComSpec` on Windows. A different
shell can be specified as a string. See [Shell Requirements][] and
[Default Windows Shell][]. **Default:** `false` (no shell).
* `shell` {boolean|string|Array} If `true`, runs `command` inside of a shell.
Uses `'/bin/sh'` on Unix, and `process.env.ComSpec` on Windows. A different
shell can be specified as a string. Or specify a shell with arguments.
See [Shell Requirements][] and [Default Windows Shell][].
**Default:** `false` (no shell).
* Returns: {Buffer|string} The stdout from the command.

The `child_process.execFileSync()` method is generally identical to
Expand Down Expand Up @@ -799,7 +818,8 @@ changes:
be output to the parent process' stderr unless `stdio` is specified.
**Default:** `'pipe'`.
* `env` {Object} Environment key-value pairs. **Default:** `process.env`.
* `shell` {string} Shell to execute the command with. See
* `shell` {string|Array} Shell to execute the command with.
Or specify a shell with arguments. See
[Shell Requirements][] and [Default Windows Shell][]. **Default:**
`'/bin/sh'` on Unix, `process.env.ComSpec` on Windows.
* `uid` {number} Sets the user identity of the process. (See setuid(2)).
Expand Down Expand Up @@ -878,10 +898,11 @@ changes:
**Default:** `1024 * 1024`.
* `encoding` {string} The encoding used for all stdio inputs and outputs.
**Default:** `'buffer'`.
* `shell` {boolean|string} If `true`, runs `command` inside of a shell. Uses
`'/bin/sh'` on Unix, and `process.env.ComSpec` on Windows. A different
shell can be specified as a string. See [Shell Requirements][] and
[Default Windows Shell][]. **Default:** `false` (no shell).
* `shell` {boolean|string|Array} If `true`, runs `command` inside of a shell.
Uses `'/bin/sh'` on Unix, and `process.env.ComSpec` on Windows. A different
shell can be specified as a string. Or specify a shell with arguments.
See [Shell Requirements][] and [Default Windows Shell][].
**Default:** `false` (no shell).
* `windowsVerbatimArguments` {boolean} No quoting or escaping of arguments is
done on Windows. Ignored on Unix. This is set to `true` automatically
when `shell` is specified and is CMD. **Default:** `false`.
Expand Down
25 changes: 21 additions & 4 deletions lib/child_process.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
'use strict';

const {
Array,
ArrayIsArray,
Error,
NumberIsInteger,
Expand Down Expand Up @@ -138,7 +139,8 @@ function normalizeExecArgs(command, options, callback) {

// Make a shallow copy so we don't clobber the user's options object.
options = { ...options };
options.shell = typeof options.shell === 'string' ? options.shell : true;
if (typeof options.shell !== 'string' && !Array.isArray(options.shell))
options.shell = true;
himself65 marked this conversation as resolved.
Show resolved Hide resolved

return {
file: command,
Expand Down Expand Up @@ -451,8 +453,15 @@ function normalizeSpawnArguments(file, args, options) {
if (options.shell != null &&
typeof options.shell !== 'boolean' &&
typeof options.shell !== 'string') {
throw new ERR_INVALID_ARG_TYPE('options.shell',
['boolean', 'string'], options.shell);
if (!Array.isArray(options.shell)) {
throw new ERR_INVALID_ARG_TYPE('options.shell',
['boolean', 'string', 'Array'],
options.shell);
} else if (options.shell.length < 1) {
throw new ERR_INVALID_ARG_VALUE('options.shell',
options.shell,
'cannot be empty');
}
}

// Validate argv0, if present.
Expand Down Expand Up @@ -483,18 +492,26 @@ function normalizeSpawnArguments(file, args, options) {
if (process.platform === 'win32') {
if (typeof options.shell === 'string')
file = options.shell;
else if (Array.isArray(options.shell))
file = options.shell[0];
else
file = process.env.comspec || 'cmd.exe';
// '/d /s /c' is used only for cmd.exe.
if (/^(?:.*\\)?cmd(?:\.exe)?$/i.test(file)) {
args = ['/d', '/s', '/c', `"${command}"`];
let parameters = ['/d', '/s', '/c'];
if (Array.isArray(options.shell)) {
parameters = options.shell.slice(1);
}
args = [...parameters, `"${command}"`];
windowsVerbatimArguments = true;
} else {
args = ['-c', command];
}
} else {
if (typeof options.shell === 'string')
file = options.shell;
else if (Array.isArray(options.shell))
file = options.shell[0];
else if (process.platform === 'android')
file = '/system/bin/sh';
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ test('cmd');
testCopy('cmd.exe', `${system32}\\cmd.exe`);
test('cmd.exe');
test('CMD');
test(['cmd', '/d', '/s', '/c']);

// Test PowerShell
test('powershell');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ if (!common.isWindows) {
pass('shell', false);
fail('shell', 0, invalidArgTypeError);
fail('shell', 1, invalidArgTypeError);
fail('shell', [], invalidArgTypeError);
himself65 marked this conversation as resolved.
Show resolved Hide resolved
fail('shell', [], { code: 'ERR_INVALID_ARG_VALUE', name: 'TypeError' });
fail('shell', {}, invalidArgTypeError);
fail('shell', common.mustNotCall(), invalidArgTypeError);
}
Expand Down