Synchronous shell scripting for Node.js.
npm install shellsyncSynchronous shell scripting for Node.js.
* Pragmatic: automate tasks using synchronous code, using familiar shell commands.
* Powerful: combine the shell world with functions, modules, libraries, try/catch/finally, regular expressions, and so on, from JavaScript or TypeScript.
* Safe: avoid most Bash pitfalls and use automatic, safe variable escaping.
* Robust: use uninterruptable sections and harden your code with standard testing frameworks and strong support for mocking.
- Usage
- Safe Variable Escaping
- Writing Tests
- Debugging
- Uninterruptable Sections
- Examples
- API
- License
- See Also
Use sh to synchronously run shell commands:
``javascriptcd /tmp
const sh = require("shellsync");
const filename = "file name with spaces.txt";
sh;cat ${filename}
sh; // read filename\ with\ spaces.txt`
Note how the above uses ES6 tagged template literals,
calling the sh function without parentheses. This makes the invocations slightly shorter and allows shellsync to safely escape any values passed to it.
Use sh, sh.array, or sh.json to capture values:
`javascriptecho hello
let v1 = sh; // set v1 to "hello"lsof -t -i :8080
let v2 = sh.array; // set v2 to all process ids listening on port 8080echo '{"foo": "bar"}'
let v3 = sh.json; // set v3 to {"foo": "bar"}`
Use sh.test to determine command success (by default, failure throws):
`javascriptwhich node
if (!sh.test) {`
throw new Error("Node is not on the path!");
}
The commands above only output what is written to stderr. Use sh.out to also print stdout, or use shh completely mute stdout and stderr:
`javascriptgit init
const {shh} = require("shellsync");
shh; // git init (no output printed)echo "SHOUTING!"
sh.out; // print "SHOUTING!" to stdout`
> _"The vast majority of [shell scripting] pitfalls are in some way related to unquoted expansions"_ – Bash Pitfalls wiki
shellsync safely quotes variables automatically:
`javascriptecho "hello" > cat ${filename}
let filename = "filename with spaces.txt";
sh; // write to filename\ with\ spaces.txt`
Use unquoted() to disable automatic quoting:
`javascriptls; ${unquoted(command2)}
import {unquoted} from "shellsync";
let command2 = "sudo apt-get install foo";
sh; // ls; sudo apt-get install foo`
If you write your scripts using TypeScript with strictNullChecks, undefined variables in shellsync invocations are reported as an error.
> _"I find that writing unit tests actually increases my programming speed."_ – Martin Fowler
Test your shellsync scripts using mocking and standard testing frameworks such as Mocha or Jest.
Shell scripts often have many side effects, so it's a good habit to mock out commands
that touch the file system, interact with processes, and so on.
Use sh.mock(pattern, command) to mock shell command using glob patterns.git log
For example, use the pattern to mock any calls to git log, or use git * to mock all calls togit accross the board. If you have multiple mocks, the longest (most specific) matching pattern wins:
`javascriptgit status
// Script under test
function script() {
return sh;
}
// Mocha tests
it("mocks git status", () => {
let mock = sh.mock("git status", echo mock for git status); // instead of 'git status', run 'echo ...'
assert.equal(script(), "mock for git status");
assert(mock.called);
});
it("mocks arbitrary git command", () => {
let mock = sh.mock("git *", echo git command called: $1);`
assert.equal(script(), "git command called: status");
assert(mock.called);
});
It's a good habit to mock out all shell commands that have side effects.
Use sh.mockAllCommmands() to ensure a mock exists _all_ shell commands.sh.unmock(pattern)
You can then selectively add mocks or use to unmock command:
`javascriptgit status
// Script under test
function script() {
return sh;
}
// Before each Mocha test, mock the world
beforeEach(() => sh.mockAllCommands());
// Mocha tests
it("fails when no mocks are defined", () => {
program(); // FAILS: no mock was defined for "git status"
});
it("runs with git status mocked", () => {
sh.mock("git status");
program(); // passes, returns ""
});
it("runs with all git commands mocked", () => {
sh.unmock("git *");
program(); // passes, returns response of git status
});
`
Finally, sh.unmockAllCommands() restores all mocked commands to the original shell command.
`javascript`
// After each Mocha test, restore all mocked commands
afterEach(() => sh.unmockAllCommands());
Under the hood, shellsync implements mocking by defining shell functions for mocked commands
(e.g., git() { ... }) and using a DEBUG trapsh.mockAllCommands()
to intercept unmocked commands for .
Use sh.options.debug to trace all commands executed by your scripts or your mocks:
`javascriptcd /
sh.options.debug = true;
sh.mock("ls *", "echo ls was mocked");
sh;ls -l
sh;
// Prints:
// + cd /
// + ls -l
// + : mock for ls :
// + echo ls was mocked
// ls was mocked
`
> _"Please do not interrupt me while I'm ignoring you"_ – unknown author
Users can press Control-C in CLI programs, which means they can end scripts
halfway _any statement_. That means they can leave a system
in an undefined state. In Node.js, Control-C even ends a program ignoring any finally
clauses that might be used for cleanup.
Use sh.handleSignals() for sections of code where these signals should be temporarily ignored:
`javascript
sh.handleSignals(); // begin critical section
shcommand1;command2
sh;command3
sh;command4
sh;
sh.handleSignalsEnd(); // end critical section
`
Note that sh.handleSignals() affects both shell and Node.js code. If you're concerned your program won't end until the heat death of the universe and need to offer Control-C as an early way out, you can also pass a timeout in milliseconds: sh.handleSignals({timeout: 3000}).
See /examples.
Execute a command, return stdout.
Execute a command, return true in case of success.
Execute a command, return stdout split by null characters (if found) or by newline characters.
Use sh.options.fieldSeperator to pick a custom delimiter character.
Execute a command, parse the result as JSON.
Disable processing of SIGINT/TERM/QUIT signals. Optionally accepts a timeout in milliseconds, or null for no timeout.
When invoked, any signals pending since the last invocation get processed.
Re-enable processing of SIGINT/TERM/QUIT signals.
When invoked, any signals pending since the last invocation get processed.
Print output to stdout.
Define a mock: instead of pattern, run command.git
Patterns consist of one or more words and support globbing from the second word, e.g., git status, git s*. The longest (most specific) pattern is used in case multiple
mocks are defined.
Force mocks for all shell commands, throwing an error when an unmocked command is used.
Does not handle commands in subshells or shell functions.
Remove a specific mock by pattern. Best used with mockAllCommands().
Remove all mocks. When pattern is specified, remove a single mock.
Similar to sh, but return the command that would be executed.
Create an unquoted part of a command template.
See the options for child_process.
Run in debug mode, printing commands that are executed.
The delimiter used for sh.array.
Whether to prefer executables installed in node_modules (using npm-run-path). Default true.
Return a shell with specific options assigned. See sh.options. Example use:
`javascriptcat > file.txt
const input = "some text to write to a file";
sh({input});`
Same as sh; doesn't print anything to stdout or stderr.
A mock object.
#### Mock.called: number
Indicates how often this mock was called.
MIT.
* shell-tag - Run shell commands with template strings
* shell-escape-tag - Run shell commands with template strings and control over escaping
* any-shell-escape - Escape shell commands
* shelljs - Portable implementation of Unix shell commands such as echo and grep`
* shunit2 – unit testing for Bash
* bats – Bash automated testing system
* Wooledge Bash pitfalls - Bash Pitfalls wiki page