#!/usr/bin/env zx await $`cat package.json | grep name` let branch = await $`git branch --show-current` await $`dep deploy --branch=${branch}` await Promise.all([ $`sleep 1; echo 1`, $`sleep 2; echo 2`, $`sleep 3; echo 3`, ]) let name = 'foo bar' await $`mkdir /tmp/${name}`
Bash is great, but when it comes to writing scripts,
people usually choose a more convenient programming language.
JavaScript is a perfect choice, but standard Node.js library
requires additional hassle before using. zx package provides
useful wrappers around child_process, escapes arguments and
gives sensible defaults.
npm i -g zx
Write your scripts in a file with .mjs extension in order to
be able to use await on top level. If you prefer .js extension,
wrap your script in something like void async function () {...}().
Add next shebang at the beginning of your script:
#!/usr/bin/env zxNow you will be able to run your script as:
chmod +x ./script.mjs ./script.mjs
Or via zx bin:
zx ./script.mjs
When using zx via bin or shebang, all $, cd, fetch, etc
are available without imports.
Executes given string using exec function
from child_process package and returns Promise<ProcessOutput>.
let count = parseInt(await $`ls -1 | wc -l`) console.log(`Files count: ${count}`)
Example. Upload files in parallel:
let hosts = [...] await Promise.all(hosts.map(host => $`rsync -azP ./src ${host}:/var/www` ))
If executed program returns non-zero exit code, ProcessOutput will be thrown.
try { await $`exit 1` } catch (p) { console.log(`Exit code: ${p.exitCode}`) console.log(`Error: ${p.stderr}`) }
class ProcessOutput { readonly exitCode: number readonly stdout: string readonly stderr: string toString(): string }
Changes working directory.
cd('/tmp') await $`pwd` // outputs /tmp
This is a wrapper around node-fetch package.
let resp = await fetch('http://wttr.in') if (resp.ok) { console.log(await resp.text()) }
This is a wrapper around readline package.
type QuestionOptions = { choices: string[] } function question(query: string, options?: QuestionOptions): Promise<string>
Usage:
let username = await question('What is your username? ') let token = await question('Choose env variable: ', { choices: Object.keys(process.env) })
The chalk package is available without importing inside scripts.
console.log(chalk.blue('Hello world!'))
The fs package is available without importing inside scripts.
let content = await fs.readFile('./package.json')
Promisified version imported by default. Same as if you write:
import {promises as fs} from 'fs'
The os package is available without importing inside scripts.
await $`cd ${os.homedir()} && mkdir example`
Specifies what shell is used. Default is which bash.
$.shell = '/usr/bin/bash'
Specifies command what will be added to all command.
Default is set -euo pipefail;.
Specifies a function what will be used for escaping special characters in command substitution.
Default is shq package.
Specifies verbosity. Default: true.
In verbose mode prints executed commands with outputs of it. Same as
set -x in bash.
It's possible to use $ and others with explicit import.
#!/usr/bin/env node import {$} from 'zx' await $`date`
process.env.FOO = 'bar' await $`echo $FOO`
If arg to zx bin starts with https://, it will be downloaded and executed.
zx https://medv.io/example-script.mjs
Disclaimer: This is not an officially supported Google product.