What is zx?
zx
is a tool from Google that lets you write shell scripts using JavaScript (or TypeScript). If you’ve ever struggled with Bash’s quirks or wished you could use modern programming features in your scripts, zx is good way for you.
Why I Switched: My Scripting Journey
For years, I used Bash for all my automation because of its simplicity and power when working with Linux-related tasks. ? But as my scripts grew larger and more complex, managing them became a headache. That's when I discovered zx—and every become more simple again. ?
The Good: Why I Like zx
- Familiar JavaScript Syntax: Use variables, loops, async/await, and all the logic you know from JS.
- Error Handling: One of the difficult problems with shell scripts is error handling and bug tracing. With try/catch makes it easy to handle failures gracefully.
- NPM Ecosystem: Import any npm package—fetch APIs, parse YAML, color your output, and more.
- Cross-Platform: Works on Linux, macOS, and Windows.
- Readable and Maintainable: Scripts are easier to read, debug, and extend.
- Built-in Utilities: Functions like
cd()
,question()
, and libraries likechalk
andfs-extra
are ready to use. - Top-level await: No need to wrap everything in async functions—just use
await
at the top level. - Great for APIs and Modern Workflows: Fetch data, parse JSON, and interact with web services easily.
The Bad: Where zx Falls Short
- Requires Node.js: Not available by default on all systems (unlike Bash). It is good with systems that have Nodejs available.
- Performance: For tiny, one-off scripts, Bash can be faster to start.
- Shell Command Differences: Some shell commands behave differently across OSes—be careful if you want true portability.
- Learning Curve: If you’re new to JavaScript, there’s a bit to learn.
- Not Always the Best for Text Processing: Bash’s built-in tools like
awk
andsed
are still unbeatable for some quick text hacks -- I had some trouble using thesed
command to replace text. It automatically inserted a $ character for no apparent reason. It might be a legacy bug. So I had to usefs
instead.
zx vs Bash: A Deep Dive Comparison
Feature | zx (JavaScript) | Bash (Shell) |
---|---|---|
Syntax | Modern JS | Shell scripting |
Error Handling | try/catch | Exit codes, traps |
Libraries | NPM ecosystem | Built-in Unix tools |
Portability | Node.js required | Native on Unix |
Readability | High (for JS devs) | Varies |
Async Support | Yes (async/await) | No (workarounds) |
Text Processing | JS methods, NPM | awk, sed, grep |
User Input | question() | read |
Debugging | JS tools, console.log | echo, set -x |
Community | JS/NPM ecosystem | Huge, mature |
When to use zx:
- You want to use JS logic, npm packages, or async code.
- Your script is more than a few lines or needs to be maintained.
- You want better error handling and readability.
- You need to interact with APIs or do complex logic.
When to use Bash:
- You need a quick, portable one-liner.
- You’re working in a minimal environment (no Node.js).
- You need advanced text processing with classic Unix tools.
- You want maximum compatibility with existing shell scripts.
Getting Started with zx
Prerequisites
- Node.js v14.13.1 or higher
- Some basic JavaScript knowledge
Installation
Install globally for quick scripts:
npm install -g zx
Or locally in your project for version control:
npm install --save-dev zx
Your First zx Script
Create a file called hello.mjs
:
#!/usr/bin/env zx
$.verbose = true // comment it out if you don't want too much log
await $`docker run hello-world`
console.info("command executed successfully")
Make it executable and run it:
chmod +x hello.mjs
./hello.mjs
Example: Automate Project Setup with zx
Here’s a real-world example: a script to bootstrap a new Node.js project directory.
#!/usr/bin/env zx
import { $, question, cd, fs, chalk } from 'zx'
const dir = await question('Project directory name? ')
await fs.mkdir(dir)
cd(dir)
await $`git init`
await $`npm init -y`
const pkgs = (await question('Packages to install (space separated)? ')).split(' ').filter(Boolean)
if (pkgs.length) await $`npm install ${pkgs}`
await fs.writeFile('README.md', `# ${dir}\n`)
console.log(chalk.green('Project setup complete!'))
How to run:
- Save as
setup.mjs
and make it executable:chmod +x setup.mjs
- Run:
./setup.mjs
How zx Makes Scripting Easier (with Examples)
Use Variables and Logic
const files = ['foo.txt', 'bar.txt']
for (const file of files) {
await $`echo "Hello" > ${file}`
}
Fetch Data from APIs
import fetch from 'node-fetch'
const res = await fetch('https://api.github.com/repos/google/zx')
const data = await res.json()
console.log('Stars:', data.stargazers_count)
Handle Errors Gracefully
try {
await $`cat notfound.txt`
} catch (err) {
console.error('File not found!')
}
Pro Tips for zx Users
- Turn off verbose mode:
$.verbose = false
for cleaner output. - Use npm packages: Need to parse YAML, fetch APIs, or color output? Just import the package.
- Mix and match: Call Bash scripts from zx, or use zx for logic and Bash for quick text processing.
- Debug easily: Use
console.log
or a Node.js debugger. - Cross-platform caution: Test your scripts on all target OSes if portability matters.
- Use built-in utilities:
cd()
,question()
, and more are available out of the box. - Leverage minimist: Command-line arguments are parsed for you as
argv
.
Creative Ways to Use zx
- Use zx to automate repetitive dev tasks: deploys, backups, or even generating reports.
- Combine zx with APIs: fetch data, process it, and output results—all in one script.
- Build interactive CLI tools with prompts and colored output.
- Replace fragile Bash scripts in your CI/CD pipelines with zx for better maintainability.
- Use zx for cross-platform automation where Bash scripts would need lots of tweaks.
- Build scripts that send notifications, update dashboards, or even tweet!
Final Thoughts
Switching to zx made my scripting life easier, especially for anything more complex than a one-liner. If you’re a developer who loves JavaScript, give zx a try—you might never look back!
Happy scripting!