“Bash is good, so I choose JavaScript”
We spend most of our daily front-end development time in Javascript, and we’re not as familiar with writing bash as WE are with Javascript when writing shell scripts, so why not choose a more convenient programming language to write it in? The Google ZX tool makes this possible. It has been successfully introduced on GitHub as a more convenient and friendly tool to help developers write scripts.
How does it work?
Install and initialize (NPM i-g zx), Node version restriction (>= 14.8.0)
// demo.mjs
await Promise.all([
$`sleep 1; echo 3`,
$`sleep 2; echo 2`,
$`sleep 3; echo 1`,]),let tName = 'google/zx'
await $`echo '/hello/${tName}'`
Copy the code
The command line executes zx demo.mjs with a 3-second countdown to see /hello/ Google /zx
That’s a simple example of how to use it, but let’s get down to business
What capabilities are provided?
Three ways to use it
zx << ‘EOF’
if (typeof firstArg === 'undefined' || firstArg === The '-') {... }Copy the code
If the input command does not have firstArg, the logic will be handled by scriptFromStdin:
-
Use process.stdin.istty to determine whether the terminal condition is present
-
If not, set the UTF8 character encoding
-
Loop and concatenate the input line by line
-
Create a randomly named MJS file in the temporary folder
Math.random().toString(36).substr(2) + ‘.mjs’ for example:
zx <<'EOF'
await $`pwd`
EOF
Copy the code
Pull script execution from remote end
if (firstArg.startsWith('http://') || firstArg.startsWith('https://')) { ... }
Copy the code
Pull data is mainly realized through scriptFromHttp, which includes the following steps:
-
Use the Node-FETCH package to fetch the data of the corresponding URL
-
When the data is pulled, it is named by the PATHName of the URL
-
Create files in a temporary folder
Local script file execution
If the preceding two judgments are not entered, the following logic is executed: if the link begins with a /, the link is used directly; if file begins with a call to url.fileurltopath to obtain the absolute path string; if the other logic is path.resolve, the path is obtained
if (firstArg.startsWith('/')) {
filepath = firstArg
} else if (firstArg.startsWith('file:///')) {
filepath = url.fileURLToPath(firstArg)
} else {
filepath = resolve(firstArg)
}
Copy the code
Supports multiple file formats importPath
Empty suffix
In this case, zX directly reads the file information by readFile, obtains the current file directory and filename, writes a new MJS file, and enters the importPath method again with import execution
Markdown files
Zx can also execute the scripts written by Markdow. Only the parts wrapped in code are parsed. The transformMarkdown method is used to cut into arrays with \ N and then scan them line by line and match them with the re. Finally, the data is converted into a string as script data by concatenating the newline character \n, and the file is generated in the same way as the Markdown format example above:
# Markdown Scripts
It's possible to write scripts using markdown. Only code blocks will be executed
by zx.
> You can run this markdown file:
> > ` ` `
> zx docs/markdown.md
> ` ` `
```js
await $`whoami`
await $`echo ${__dirname}`
Copy the code
The __filename
will be pointed to markdown.md:
console.log(chalk.yellowBright(__filename))
Copy the code
We can use imports here as well:
await import('chalk')
Copy the code
A bash code (with bash
or sh
language tags) also will be executed:
VAR=$(date)
echo "$VAR" | wc -c
Copy the code
Other code blocks are ignored:
body .hero {
margin: 42px;
}
Copy the code
##### Ts file core part is to execute 'compile' method for TSC to compile a JS, Let TSC = $' nPM_config_YES =true NPX -p typescript TSC --target esNext --lib esnext --module esnext --moduleResolution node ${input}` await tscCopy the code
There is a loading, which is implemented by setInterval
let i = 0
let interval = setInterval(() = > {
process.stdout.write(' '
+ ['⠋'.'⠙'.'⠹'.'⠸'.'⠼'.'⠴'.'⠦'.'⠧'.'⠇'.'⠏'][i++ % 10]
+ '\r')},100)...clearInterval(interval)
Copy the code
The $function
The first is the ability to handle ARGs using tag functions
If an array is passed in, the array is concatenated, for example:
let flags = [
'--oneline'.'--decorate'.'--color',]await $`git log ${flags}`
// zx handles the logic by formatting it first and then concatenating it with a space
if (Array.isArray(args[i])) {
s = args[i].map(x= > $.quote(substitute(x))).join(' ')}Copy the code
The _fun method is triggered when CMD concatenates the translated ARgs and creates an instance of the ProcessPromise class that calls then
then(onfulfilled, onrejected) {
if (this._run) this._run()
return super.then(onfulfilled, onrejected)
}
Copy the code
The core of the _run method is child_process.spawn
Spawn: starts a child process to execute commands exec: starts a child process to execute commands. Unlike spawn, it has a callback function that knows the status of the child process execFile: Fork: Starts a child process to execute the executable. Fork: is similar to spawn except that it specifies the module that the child process needs to execute
Child_process listens for exit and close events, and then outputs an output with a code of 0 indicating success, or an error message in the exitCodeInfo method if it fails
function exitCodeInfo(exitCode) {
return {
2: 'Misuse of shell builtins',
126: 'Invoked command cannot execute',
127: 'Command not found',
128: 'Invalid exit argument',
129: 'Hangup',
130: 'Interrupt',
131: 'Quit and dump core',
132: 'Illegal instruction',
133: 'Trace/breakpoint trap',
134: 'Process aborted',
135: 'Bus error: "access to undefined portion of memory object"',
136: 'Floating point exception: "erroneous arithmetic operation"',
137: 'Kill (terminate immediately)',
138: 'User-defined 1',
139: 'Segmentation violation',
140: 'User-defined 2',
141: 'Write to pipe with no one reading',
142: 'Signal raised by alarm',
143: 'Termination (request to terminate) ',145: 'Child process terminated, stopped (or continued*)',
146: 'Continue if stopped',
147: 'Stop executing temporarily',
148: 'Terminal stop signal',
149: 'Background process attempting to read from tty ("in") ',150: 'Background process attempting to write to tty ("out") ',151: 'Urgent data available on socket',
152: 'CPU time limit exceeded',
153: 'File size limit exceeded',
154: 'Signal raised by timer counting virtual time: "virtual timer expired"',
155: 'Profiling timer expired',
157: 'Pollable event',
159: 'Bad syscall',
}[exitCode]
}
Copy the code
Other Funtions
cd()
Change the current working directory with $.cwd set
if(! fs.existsSync(path)) {let __from = (new Error().stack.split(/^\s*at\s/m) [2]).trim()
console.error(`cd: ${path}: No such directory`)
console.error(` at ${__from}`)
process.exit(1)}Copy the code
fetch()
Node-fetch is actually used to pull data
nodeFetch(url: RequestInfo, init? : RequestInit): Promise<Response>Copy the code
question()
Readline syntax sugar supports select mode by passing in an array by setting options.choices
sleep()
Use util.promisify to wrap a layer of setTimeout and return a promise, such as await sleep(1000) callback style is more elegant
nothrow()
Set _nothrow to true, child Process listens for close events, and returns the code! == 0 can also resolve
Examples:
await nothrow($`grep something from-file`)
Copy the code
Packages that can be used directly
chalk package
console.log(chalk.blue('Hello world! '))
Copy the code
fs package ( fs-extra )
let content = await fs.readFile('./package.json')
Copy the code
globby package
This package provides methods to traverse the file system and return pathnames that match the defined set of specified patterns according to the rules used by the UnixBashshell, while returning results in any order, making it fast, simple, and efficient to use.
let packages = await globby(['package.json'.'packages/*/package.json'])
let pictures = globby.globbySync('content/*.(jpg|png)')
// Also, globby available via the glob shortcut:
await $`svgo The ${await glob('*.svg')}`
Copy the code
os package
Provides operating system-specific methods and properties
await $`cd ${os.homedir()} && mkdir example`
Copy the code
minimist package
// Example
$ node example/parse.js -x 3 -y 4 -n5 -abc --beep=boop foo bar baz
{ _: [ 'foo'.'bar'.'baz'].x: 3.y: 4.n: 5.a: true.b: true.c: true.beep: 'boop' }
Copy the code
Parameter configuration
$.quote
Here is the main implementation of a quote method, mainly used for special character translation
function quote(arg) {
if (/^[a-z0-9/_.-]+$/i.test(arg) || arg === ' ') {
return arg
}
return ` $' `
+ arg
.replace(/\\/g.'\ \ \ \')
.replace(/'/g.'\ \ \')
.replace(/\f/g.'\\f')
.replace(/\n/g.'\\n')
.replace(/\r/g.'\\r')
.replace(/\t/g.'\\t')
.replace(/\v/g.'\\v')
.replace(/\0/g.'\ \ 0')
+ ` '`
}
Copy the code
$verbose (– quiet)
$. Verbose = false $. Verbose = false $. Verbose = false $.
-
CD command to prompt the current path
-
Remote pull script that prompts the URL of the current fetch
$.cwd
The default is undefined. $. CWD can be set using the CD method to get the current working directory
$. Shell (shell)
Specify the shell to use. For example:
$.shell = '/usr/bin/bash'
Copy the code
$.prefix
Commands to set the prefixes of all run commands default to -euo pipefail or use –prefix, for example: –prefix=’set -e; ‘
Polyfills
Nodejs does not provide __dirname and __filename in ESM, and does not define require(), so zX has these variables in global for the convenience of users
let __filename = resolve(origin)
let __dirname = dirname(__filename)
let require = createRequire(origin)
Object.assign(global, {__filename, __dirname, require})
Copy the code
That’s all for this introduction