Bash is great and all, but it’s not something I’ll pick up in a day. I was looking for something a little bit more convenient to write scripts in. While looking, I’ve stumbled upon this little utility from Google called zx
. And it’s a better way to write scripts using JavaScript.
I thought I’d give zx
a try. It comes with a bunch of things out of the box, like chalk
and fetch
. I know, Node.js already lets me write scripts, but dealing with a bunch of the crap around escaping and sanitizing inputs was painful.
The Script Way
Before I talk about all the great things zx
promised, let’s talk about the basics of writing and using scripts first.
Scripts are all text files and need to start with a shebang at the top (also known as sha-bang, hashbang, pound-bang or hash-pling). The shebang tells the operating system to interpret the rest of the file using that interpreter directive, effectively starting the interpreter and passing the text file along as a parameter.
So, when scripts start with #!/bin/bash
or #!/bin/sh
, the OS actually runs $ /bin/bash /path/to/script
behind the scenes every time you execute the script.
Before you can execute the script, you need to declare it in the system as executable. On Unix systems (macOS included), running $ chmod +x ./script.sh
or $ chmod 775 ./script.sh
will do the trick.
After you’ve given permissions to your script to be executed, you can run it with $ ./script.sh
.
Bash Scripts
A Bash script starts with the bash shebang, followed by a lot of black magic. 😅 For example, to add two numbers that are given as command-line arguments, a script looks like this:
#!/bin/bash
echo "$1 + $2 = $(($1 + $2))"
To run it, save it as add.sh
and then run the following commands in your Terminal:
$ chmod +x ./add.sh
$ ./add.sh 5 7
The output is going to be 5 + 7 = 12
.
It looks pretty simple if you’ve figured out that $index
is the command-line argument. I’ve had to look that up while learning shell scripting.
zx
Scripts
Before you can use zx
to run scripts, you’ll need to install it globally via npm, with $ npm i -g zx
. Why didn’t you need to install bash
? Because bash
comes installed by default with Unix systems.
Similarly to all other scripts, a zx
script will start with a shebang. This time, a little more complicated, the zx
shebang. Followed by a lot of JavaScript. Let’s try to recreate the above shell script that adds two numbers given as command-line arguments.
#!/usr/bin/env zx
console.log(`${process.argv[0]} + ${process.argv[1]} = ${process.argv[0] + process.argv[1]}`)
To run it, save it as add.mjs
and then run the following commands in your Terminal:
$ chmod +x ./add.mjs
$ ./add.mjs 5 7
The output is going to be /Users/laka/.nvm/versions/node/v16.1.0/bin/node + /usr/local/bin/zx = /Users/laka/.nvm/versions/node/v16.1.0/bin/node/usr/local/bin/zx
😅. And that’s because process.argv
, another Node.js wonder, gets called with three extra arguments before you get to 5 and 7. Let’s re-write the script to account for that:
#!/usr/bin/env zx
console.log(`${process.argv[3]} + ${process.argv[4]} = ${process.argv[3] + process.argv[4]}`)
If you run the script now with $ ./add.mjs 5 7
, the output is going to be 5 + 7 = 57
. Because JavaScript 🤦. And JavaScript thinks those are strings and concatenates them instead of doing math. Re-writing the script again to deal with numbers instead of strings, it looks like:
#!/usr/bin/env zx
console.log(`${process.argv[3]} + ${process.argv[4]} = ${parseInt(process.argv[3], 10) + parseInt(process.argv[4], 10)}`)
The Bash script looked a lot cleaner, right? I agree. And if I ever need to add two numbers from the command line, a Bash script would be a way better option! Bash doesn’t shine in a lot of other areas, though. Like parsing JSON files. I gave up trying to figure how to parse JSON files halfway through the StackOverflow post explaining it. But this is where zx
shines.
I already know how to parse JSON in JavaScript. And here is what the zx
script for it looks like, using the built-in fetch
module:
#!/usr/bin/env zx
let response = await fetch('https://raw.githubusercontent.com/AlexLakatos/computer-puns/main/puns.json')
if (response.ok) {
let puns = await response.json()
let randomPun = Math.floor(Math.random() * puns.length)
console.log(chalk.red(puns[randomPun].pun))
console.log(chalk.green(puns[randomPun].punchline))
}
Because I was fancy and used the built-in chalk
module, this zx
script outputs a random pun from https://puns.dev in the command-line.
Building something similar in shell
had me rage-quit halfway through the process. And that’s OK. Finding the right tool for the job is what this post was all about.