zx is a new star project on the 2021 gibhub. It allows us to easily use JavaScript / TypeScript (which contains TypeScript type declaration) instead of bash to build command-line scripts. The script provides convenience for those who master the front-end development to build a scaffold. Recently, I paid attention to the project and translated its enabling documents for sharing as notes.
zx official Github homepage: https://github.com/google/zx
zx official npm homepage: https://www.npmjs.com/package/zx
Address: https://blog.csdn.net/qq_28550263/article/details/123403575
translator: jcLee95
catalogue zx🐚 zx
#!/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 writing scripts, people usually choose a more convenient programming language.
JavaScript is a perfect choice, but standard node JS library needs extra trouble before using. zx surrounds child_process provides a useful wrapper to escape parameters and give reasonable default values.
install
npm i -g zx
Requirement: Node version > = 16.0.0
file
When the extension is Script in mjs file so that await can be used at the top level. If you like it js extension, which can wrap your script like void async function() {...} ().
Add the following statement to the beginning of your zx script:
#!/usr/bin/env zx
You will now be able to run your script like this:
chmod +x ./script.mjs ./script.mjs
Or through zx executable:
zx ./script.mjs
All functions ($, cd, fetch, etc.) can be used directly without any import.
Or explicitly import global variables (for better automation in VS Code).
import 'zx/globals'
$`command`
Using child_ The spawn function in the process package executes the given string and returns processpromise < processoutput >.
Everything goes through ${...} Will be automatically escaped and referenced.
let name = 'foo & bar' await $`mkdir ${name}`
No additional quotation marks are required. stay quotes Read more about it in
If desired, you can pass a set of parameters:
let flags = [ '--oneline', '--decorate', '--color', ] await $`git log ${flags}`
If the executed program returns a non-zero exit code, ProcessOutput is thrown.
try { await $`exit 1` } catch (p) { console.log(`Exit code: ${p.exitCode}`) console.log(`Error: ${p.stderr}`) }
ProcessPromise
class ProcessPromise<T> extends Promise<T> { readonly stdin: Writable readonly stdout: Readable readonly stderr: Readable readonly exitCode: Promise<number> pipe(dest): ProcessPromise<T> kill(signal = 'SIGTERM'): Promise<void> }
The pipe() method can be used to redirect stdout:
await $`cat file.txt`.pipe(process.stdout)
Read more about pipelines Information about.
ProcessOutput
class ProcessOutput { readonly stdout: string readonly stderr: string readonly exitCode: number readonly signal: 'SIGTERM' | 'SIGKILL' | ... toString(): string }
function
cd()
Change the current working directory.
cd('/tmp') await $`pwd` // outputs /tmp
fetch()
node-fetch Packaging of bags.
let resp = await fetch('https://wttr.in') if (resp.ok) { console.log(await resp.text()) }
question()
readline A wrapper for a bag.
Usage:
let bear = await question('What kind of bear is best? ') let token = await question('Choose env variable: ', { choices: Object.keys(process.env) })
In the second parameter, you can refer to the array of options for automatic completion of custom table symbols.
function question(query?: string, options?: QuestionOptions): Promise<string> type QuestionOptions = { choices: string[] }
sleep()
Wrapper for the setTimeout function.
await sleep(1000)
nothrow()
Change the behavior of $to not throw exceptions on non-zero exit codes.
function nothrow<P>(p: P): P
Usage:
await nothrow($`grep something from-file`) // Inside a pipe(): await $`find ./examples -type f -print0` .pipe(nothrow($`xargs -0 grep something`)) .pipe($`wc -l`)
If you only need exitCode, you can use the next code:
if (await $`[[ -d path ]]`.exitCode == 0) { ... } // amount to: if ((await nothrow($`[[ -d path ]]`)).exitCode == 0) { ... }
quiet()
Change the behavior of $to disable verbose output.
function quiet<P>(p: P): P
Usage:
await quiet($`grep something from-file`) // Commands and output are not displayed.
package
The following packages can be used without importing internal scripts.
chalk package
chalk Bag.
console.log(chalk.blue('Hello world!'))
yaml package
yaml Bag.
console.log(YAML.parse('foo: bar').foo)
fs package
fs-extra Bag.
let content = await fs.readFile('./package.json')
globby package
globby package
let packages = await globby(['package.json', 'packages/*/package.json']) let pictures = globby.globbySync('content/*.(jpg|png)')
In addition, globby can be obtained through glob shortcut:
await $`svgo ${await glob('*.svg')}`
os package
os package
await $`cd ${os.homedir()} && mkdir example`
path package
path Bag.
await $`mkdir ${path.join(basedir, 'output')}`
minimist package
minimist Bag.
Supplied as a global constant argv.
to configure
$.shell
Specify what shell to use. The default is which bash.
$.shell = '/usr/bin/bash'
Or use a CLI parameter: -- shell=/bin/bash
$.prefix
Specifies the command that will prefix all running commands.
The default is set -euo pipefail
Or use a CLI parameter: -- prefix='set -e; '
$.quote
Specifies a function to escape special characters during command replacement.
$.verbose
Specify the detail level.
The default value is true
In verbose mode, zx prints all executed commands and their output.
Or use a CLI parameter: -- quiet to set $ verbose = false.
Polyfills
__filename & __dirname
stay ESM In the module, node JS does not provide__ filename and__ dirname global variable. Because such global variables are very convenient in scripts, zx provides these for mjs file (when using zx executable).
require()
stay ESM In the module, the require() function is not defined.
zx provides the require() function, so it can be used with Used with imports in mjs files (when using zx executables).
let {version} = require('./package.json')
Experimental
zx also provides some experimental functions. Please in discuss Leave feedback on these features in.
retry()
Retry the command several times. Will return after the first successful attempt, or will be raised after the specified number of attempts.
import {retry} from 'zx/experimental' let {stdout} = await retry(5)`curl localhost`
echo()
Acceptable ProcessOutput Console Log() alternative.
import {echo} from 'zx/experimental' let branch = await $`git branch --show-current` echo`Current branch is ${branch}.` // perhaps echo('Current branch is', branch)
startSpinner()
Start a simple CLI spinner and return the stop() function.
import {startSpinner} from 'zx/experimental' let stop = startSpinner() await $`long-running command` stop()
FAQ
Passing environment variables
process.env.FOO = 'bar' await $`echo $FOO`
Pass value array
If an array of values is passed as a parameter to $, the items of the array will be escaped separately and connected by spaces.
For example:
let files = [...] await $`tar cz ${files}`
Import from other scripts
You can use $and other functions through explicit import:
#!/usr/bin/env node import {$} from 'zx' await $`date`
Script without extension
If the script does not have a file extension (for example,. Git / hooks / pre commit), zx assumes it is a ESM modular.
Markdown script
zx can execute with markdown Script written by.
zx docs/markdown.md
TypeScript script
import {$} from 'zx' // Or import 'zx/globals' void async function () { await $`ls -la` }()
use ts-node As an esm node loader.
node --loader ts-node/esm script.ts
You must i in package Set in JSON "type": "module" And in tsconfig Set in JSON "module": "ESNext" .
{ "type": "module" }
{ "compilerOptions": { "module": "ESNext" } }
Execute remote script
If the parameter of zx executable starts with https: / /, the file will be downloaded and executed.
zx https://medv.io/example-script.mjs
zx https://medv.io/game-of-life.mjs
Execute script from stdin
zx supports script execution from stdin.
zx <<'EOF' await $`pwd` EOF
License
Disclaimer: This is not an officially supported Google product.
Pass parameters to ${...} No quotation marks are required. If necessary, it will be added automatically.
let name = 'foo & bar' await $`mkdir ${name}`
For quotation marks, zx uses a special bash syntax (the next command is valid bash):
mkdir $'foo & bar' $'ls' $'-la'
If you add quotation marks "${name}", an incorrect command will be generated.
If you need to add something extra, consider putting it in curly braces.
await $`mkdir ${'path/to-dir/' + name}`
This will also work:
await $`mkdir path/to-dir/${name}`
Parameter array
zx can also be in ${...} Accept arrays or parameters in. The items in the array will be referenced separately and connected by spaces.
Do not add join(' ').
let flags = [ '--oneline', '--decorate', '--color', ] await $`git log ${flags}`
If you already have a string with an array, it's your responsibility to parse it correctly and distinguish different parameters. Like this:
await $`git log ${'--oneline --decorate --color'.split(' ')}`
globbing and~
When everything passes ${...} Will be escaped, cannot use ~ or glob syntax.
To achieve this, zx provides globby package.
Not like this:
let files = '~/dev/**/*.md' // wrong await $`ls ${files}`
Use glob function and os package:
let files = await glob(os.homedir() + '/dev/**/*.md') await $`ls ${files}`
zx supports node JS stream, a special pipe() method, can be used to redirect standard output.
await $`echo "Hello, stdout!"` .pipe(fs.createWriteStream('/tmp/output.txt')) await $`cat /tmp/output.txt`
Process created with $from process Stdin gets stdin, but we can also write to child processes:
let p = $`read var; echo "$var";` p.stdin.write('Hello, stdin!\n') let {stdout} = await p
The pipeline can be used to display the real-time output of the program:
$.verbose = false await $`echo 1; sleep 1; echo 2; sleep 1; echo 3;` .pipe(process.stdout)
Pipe stdout and stderr:
let echo = $`echo stdout; echo stderr 1>&2` echo.stdout.pipe(process.stdout) echo.stderr.pipe(process.stdout) await echo
In addition, the pipe() method can combine $programs. Same as | in bash:
let greeting = await $`printf "hello"` .pipe($`awk '{printf $1", world!"}'`) .pipe($`tr '[a-z]' '[A-Z]'`) console.log(greeting.stdout)
Using pipe() and nothrow():
await $`find ./examples -type f -print0` .pipe(nothrow($`xargs -0 grep ${'missing' + 'part'}`)) .pipe($`wc -l`)
It is possible to write scripts using markdown. zx executes only blocks of code.
You can run this markdown file:
zx docs/markdown.md
await $`whoami` await $`echo ${__dirname}`
__ filename will point to markdown md:
console.log(chalk.yellowBright(__filename))
We can also use import here:
await import('chalk')
Bash code (with bash or sh language tags) will also be executed:
VAR=$(date) echo "$VAR" | wc -c
Other code blocks ignored:
body .hero { margin: 42px; }