Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 5824522

Browse filesBrowse files
cjihrigMylesBorins
authored andcommitted
child_process: add shell option to spawn()
This commit adds a shell option, to spawn() and spawnSync(). This option allows child processes to be spawned with or without a shell. The option also allows a custom shell to be defined, for compatibility with exec()'s shell option. Fixes: #1009 PR-URL: #4598 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 30d60cf commit 5824522
Copy full SHA for 5824522

File tree

Expand file treeCollapse file tree

4 files changed

+144
-29
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

4 files changed

+144
-29
lines changed
Open diff view settings
Collapse file

‎doc/api/child_process.md‎

Copy file name to clipboardExpand all lines: doc/api/child_process.md
+14-5Lines changed: 14 additions & 5 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,12 @@ The importance of the distinction between `child_process.exec()` and
7777
`child_process.execFile()` can vary based on platform. On Unix-type operating
7878
systems (Unix, Linux, OSX) `child_process.execFile()` can be more efficient
7979
because it does not spawn a shell. On Windows, however, `.bat` and `.cmd`
80-
files are not executable on their own without a terminal and therefore cannot
81-
be launched using `child_process.execFile()` (or even `child_process.spawn()`).
82-
When running on Windows, `.bat` and `.cmd` files can only be invoked using
83-
either `child_process.exec()` or by spawning `cmd.exe` and passing the `.bat`
84-
or `.cmd` file as an argument (which is what `child_process.exec()` does).
80+
files are not executable on their own without a terminal, and therefore cannot
81+
be launched using `child_process.execFile()`. When running on Windows, `.bat`
82+
and `.cmd` files can be invoked using `child_process.spawn()` with the `shell`
83+
option set, with `child_process.exec()`, or by spawning `cmd.exe` and passing
84+
the `.bat` or `.cmd` file as an argument (which is what the `shell` option and
85+
`child_process.exec()` do).
8586

8687
```js
8788
// On Windows Only ...
@@ -303,6 +304,10 @@ added: v0.1.90
303304
[`options.detached`][])
304305
* `uid` {Number} Sets the user identity of the process. (See setuid(2).)
305306
* `gid` {Number} Sets the group identity of the process. (See setgid(2).)
307+
* `shell` {Boolean|String} If `true`, runs `command` inside of a shell. Uses
308+
'/bin/sh' on UNIX, and 'cmd.exe' on Windows. A different shell can be
309+
specified as a string. The shell should understand the `-c` switch on UNIX,
310+
or `/s /c` on Windows. Defaults to `false` (no shell).
306311
* return: {ChildProcess}
307312

308313
The `child_process.spawn()` method spawns a new process using the given
@@ -635,6 +640,10 @@ added: v0.11.12
635640
* `maxBuffer` {Number} largest amount of data (in bytes) allowed on stdout or
636641
stderr - if exceeded child process is killed
637642
* `encoding` {String} The encoding used for all stdio inputs and outputs. (Default: 'buffer')
643+
* `shell` {Boolean|String} If `true`, runs `command` inside of a shell. Uses
644+
'/bin/sh' on UNIX, and 'cmd.exe' on Windows. A different shell can be
645+
specified as a string. The shell should understand the `-c` switch on UNIX,
646+
or `/s /c` on Windows. Defaults to `false` (no shell).
638647
* return: {Object}
639648
* `pid` {Number} Pid of the child process
640649
* `output` {Array} Array of results from stdio output
Collapse file

‎lib/child_process.js‎

Copy file name to clipboardExpand all lines: lib/child_process.js
+29-24Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ exports._forkChild = function(fd) {
7171

7272

7373
function normalizeExecArgs(command /*, options, callback*/) {
74-
var file, args, options, callback;
74+
let options;
75+
let callback;
7576

7677
if (typeof arguments[1] === 'function') {
7778
options = undefined;
@@ -81,25 +82,12 @@ function normalizeExecArgs(command /*, options, callback*/) {
8182
callback = arguments[2];
8283
}
8384

84-
if (process.platform === 'win32') {
85-
file = process.env.comspec || 'cmd.exe';
86-
args = ['/s', '/c', '"' + command + '"'];
87-
// Make a shallow copy before patching so we don't clobber the user's
88-
// options object.
89-
options = util._extend({}, options);
90-
options.windowsVerbatimArguments = true;
91-
} else {
92-
file = '/bin/sh';
93-
args = ['-c', command];
94-
}
95-
96-
if (options && options.shell)
97-
file = options.shell;
85+
// Make a shallow copy so we don't clobber the user's options object.
86+
options = Object.assign({}, options);
87+
options.shell = typeof options.shell === 'string' ? options.shell : true;
9888

9989
return {
100-
cmd: command,
101-
file: file,
102-
args: args,
90+
file: command,
10391
options: options,
10492
callback: callback
10593
};
@@ -109,7 +97,6 @@ function normalizeExecArgs(command /*, options, callback*/) {
10997
exports.exec = function(command /*, options, callback*/) {
11098
var opts = normalizeExecArgs.apply(null, arguments);
11199
return exports.execFile(opts.file,
112-
opts.args,
113100
opts.options,
114101
opts.callback);
115102
};
@@ -123,7 +110,8 @@ exports.execFile = function(file /*, args, options, callback*/) {
123110
maxBuffer: 200 * 1024,
124111
killSignal: 'SIGTERM',
125112
cwd: null,
126-
env: null
113+
env: null,
114+
shell: false
127115
};
128116

129117
// Parse the optional positional parameters.
@@ -153,6 +141,7 @@ exports.execFile = function(file /*, args, options, callback*/) {
153141
env: options.env,
154142
gid: options.gid,
155143
uid: options.uid,
144+
shell: options.shell,
156145
windowsVerbatimArguments: !!options.windowsVerbatimArguments
157146
});
158147

@@ -331,7 +320,23 @@ function normalizeSpawnArguments(file /*, args, options*/) {
331320
else if (options === null || typeof options !== 'object')
332321
throw new TypeError('options argument must be an object');
333322

334-
options = util._extend({}, options);
323+
// Make a shallow copy so we don't clobber the user's options object.
324+
options = Object.assign({}, options);
325+
326+
if (options.shell) {
327+
const command = [file].concat(args).join(' ');
328+
329+
if (process.platform === 'win32') {
330+
file = typeof options.shell === 'string' ? options.shell :
331+
process.env.comspec || 'cmd.exe';
332+
args = ['/s', '/c', '"' + command + '"'];
333+
options.windowsVerbatimArguments = true;
334+
} else {
335+
file = typeof options.shell === 'string' ? options.shell : '/bin/sh';
336+
args = ['-c', command];
337+
}
338+
}
339+
335340
args.unshift(file);
336341

337342
var env = options.env || process.env;
@@ -492,12 +497,12 @@ function execFileSync(/*command, args, options*/) {
492497
exports.execFileSync = execFileSync;
493498

494499

495-
function execSync(/*command, options*/) {
500+
function execSync(command /*, options*/) {
496501
var opts = normalizeExecArgs.apply(null, arguments);
497502
var inheritStderr = opts.options ? !opts.options.stdio : true;
498503

499-
var ret = spawnSync(opts.file, opts.args, opts.options);
500-
ret.cmd = opts.cmd;
504+
var ret = spawnSync(opts.file, opts.options);
505+
ret.cmd = command;
501506

502507
if (inheritStderr && ret.stderr)
503508
process.stderr.write(ret.stderr);
Collapse file
+64Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const cp = require('child_process');
5+
6+
// Verify that a shell is, in fact, executed
7+
const doesNotExist = cp.spawn('does-not-exist', {shell: true});
8+
9+
assert.notEqual(doesNotExist.spawnfile, 'does-not-exist');
10+
doesNotExist.on('error', common.fail);
11+
doesNotExist.on('exit', common.mustCall((code, signal) => {
12+
assert.strictEqual(signal, null);
13+
14+
if (common.isWindows)
15+
assert.strictEqual(code, 1); // Exit code of cmd.exe
16+
else
17+
assert.strictEqual(code, 127); // Exit code of /bin/sh
18+
}));
19+
20+
// Verify that passing arguments works
21+
const echo = cp.spawn('echo', ['foo'], {
22+
encoding: 'utf8',
23+
shell: true
24+
});
25+
let echoOutput = '';
26+
27+
assert.strictEqual(echo.spawnargs[echo.spawnargs.length - 1].replace(/"/g, ''),
28+
'echo foo');
29+
echo.stdout.on('data', (data) => {
30+
echoOutput += data;
31+
});
32+
echo.on('close', common.mustCall((code, signal) => {
33+
assert.strictEqual(echoOutput.trim(), 'foo');
34+
}));
35+
36+
// Verify that shell features can be used
37+
const cmd = common.isWindows ? 'echo bar | more' : 'echo bar | cat';
38+
const command = cp.spawn(cmd, {
39+
encoding: 'utf8',
40+
shell: true
41+
});
42+
let commandOutput = '';
43+
44+
command.stdout.on('data', (data) => {
45+
commandOutput += data;
46+
});
47+
command.on('close', common.mustCall((code, signal) => {
48+
assert.strictEqual(commandOutput.trim(), 'bar');
49+
}));
50+
51+
// Verify that the environment is properly inherited
52+
const env = cp.spawn(`"${process.execPath}" -pe process.env.BAZ`, {
53+
env: Object.assign({}, process.env, {BAZ: 'buzz'}),
54+
encoding: 'utf8',
55+
shell: true
56+
});
57+
let envOutput = '';
58+
59+
env.stdout.on('data', (data) => {
60+
envOutput += data;
61+
});
62+
env.on('close', common.mustCall((code, signal) => {
63+
assert.strictEqual(envOutput.trim(), 'buzz');
64+
}));
Collapse file
+37Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const cp = require('child_process');
5+
6+
// Verify that a shell is, in fact, executed
7+
const doesNotExist = cp.spawnSync('does-not-exist', {shell: true});
8+
9+
assert.notEqual(doesNotExist.file, 'does-not-exist');
10+
assert.strictEqual(doesNotExist.error, undefined);
11+
assert.strictEqual(doesNotExist.signal, null);
12+
13+
if (common.isWindows)
14+
assert.strictEqual(doesNotExist.status, 1); // Exit code of cmd.exe
15+
else
16+
assert.strictEqual(doesNotExist.status, 127); // Exit code of /bin/sh
17+
18+
// Verify that passing arguments works
19+
const echo = cp.spawnSync('echo', ['foo'], {shell: true});
20+
21+
assert.strictEqual(echo.args[echo.args.length - 1].replace(/"/g, ''),
22+
'echo foo');
23+
assert.strictEqual(echo.stdout.toString().trim(), 'foo');
24+
25+
// Verify that shell features can be used
26+
const cmd = common.isWindows ? 'echo bar | more' : 'echo bar | cat';
27+
const command = cp.spawnSync(cmd, {shell: true});
28+
29+
assert.strictEqual(command.stdout.toString().trim(), 'bar');
30+
31+
// Verify that the environment is properly inherited
32+
const env = cp.spawnSync(`"${process.execPath}" -pe process.env.BAZ`, {
33+
env: Object.assign({}, process.env, {BAZ: 'buzz'}),
34+
shell: true
35+
});
36+
37+
assert.strictEqual(env.stdout.toString().trim(), 'buzz');

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.