What I Learned at Work this Week: How Does my Shell Script Work?

Photo by Alexandr Podvalny from Pexels

We onboarded a new member of our team this week. They happened to be an internal promotion which, incidentally, is amazing because not only are they talented but they brought with them a ton of institutional knowledge that is a valuable add to us on the Solutions Team. That also means that the transition process is a little mixed-and-matched — some things they’ve had set up since they started working for the company but other things are brand new to them.

Among those in the latter category is an oft-used shell script that can hash company names from strings into a series of numbers. When my company’s script runs on a client site, it saves that client’s identity as a “hash” based on its name. When we write custom logic for that client (for example, wait an extra 1000 milliseconds before displaying an overlay), we specify the client’s hash, so we have to be able to determine that value fairly regularly as we write code. A year ago, I added a bash script to my zsh profile, but when I had to explain it to someone new, I realized I didn’t have a great grasp on it.

Andrew Zola’s definition on TechTarget describes hashing as the process of transforming any given key or a string of characters into another value. We might use a hash to protect our data by transforming a readable string like “find password here” into a number like “138572221.” Hashing can also be useful in efficiently storing data, like if we took a value and hashed it to find a number that would determine its location in a table. Whatever our purpose, it’s important that this transformation be consistent, because we want to know how to reference our hashed value in the future.

We will therefore use a hashing function to put each of our values through the same process as we transform them. This process can be something as simple as applying a modulo operation, as illustrated here. Their example presents this hash function:

int h(int x) {
return x % 16;
}

When we run numbers through this function, we receive a new value which is equal to the remainder of their original value / 16. So 10 returns 6, 4 returns 0, and 5 returns 1. The example has the new numbers being used for location storage — the number 10 ends up in position 6 on a hash table and 4 ends up in position 0. This doesn’t necessarily scale, but that’s why it’s an example.

The last things we want to remember about hashing for now is that usually a hashing function is one-way, meaning we’ll always get the same result when we put values into it, but we can’t determine the original value if we feed in a hash (we can get 0 from 4, but we can’t get 4 from 0). Finally, remember that the logic of our hashing functions should be protected because revealing our methods of changing values could defeat the purpose. We’ll be looking at a script that executes a hashing function, but we won’t see the function itself.

When my new teammate told me that he hadn’t added the hashing shortcut to his bash profile, I told him that I would help him add it…later. I had forgotten how to find it, but once I actually looked, I realized it was quite easy. I use oh-my-zsh to enhance my terminal, so instead of a bash profile, I have a .zshrc file. I found it by navigating to my root directory and running the ls -a command:

I opened up my .zshrc file and found mostly oh-my-zsh custom settings. When I scrolled down, though, I found the script I was looking for:

mhash () {
node -e "var a = require('/Users/mdiaz/Development/code/tag/app/hash'); console.log(a('$1'))"
}

That was all I had to send to my coworker. But what if he asked me what it means?

Here we have a function called mhash. The first thing we see inside the function is node -e and then a string that actually encompasses two lines. So what’s node -e?

Node, according to nodejs.org, is an asynchronous event-driven JavaScript runtime. I have no doubt that Node can do lots of cool, exciting, and unique things, but the relevance to us is that it allows us to run JavaScript from our command line. After installing it, I ran the help command to learn more about what the flags did:

node --help

I found -e after some scrolling:

evaluate script

Evaluate script seems simple enough based on the context we already have. We pass it an argument of a stringified version of our JS. So what are we running?

var a = require(('/Users/mdiaz/Development/code/tag/app/hash');

We can once again consult the Node.js documentation to see that require is a function that reads a JavaScript file, executes the file, and then proceeds to return the exports object. In this case, we’re providing a path argument that leads to the location of a file called hash on my computer (I had to tell my coworker to adjust it for his use). We can’t look at the file here, because it’s private, but what I can say is that it defines and exports a hashing function. That function uses a ton of bitwise operators to change numbers around, so it should do a good job of thoroughly hashing.

We now have this var, a, which is a hashing function. What do we do with it?

console.log(a('$1'))

We pass it $1, which returns to our shell/bash/zsh syntax (I spent way too long trying to figure out what $1 meant in JS). As you probably guessed, when this script is run in our terminal, the $1 string is interpreted as pulling the first argument passed to the function. So if we run this command in our terminal:

mhash myname

The first argument is myname and the $1 string interpolates that value, therefore logging the resulting hash directly into the console.

It’s a great experience when we can use established knowledge to help us understand something we’re not as familiar with. Shell scripts are foreign to me, but this one ended up being mostly JavaScript after all. There was one hurdle where part of the code could have different interpretations depending on the context, but understanding JavaScript still helped me push through. Whether we’re studying something we’re familiar with, or something brand new, we’re preparing ourselves to understand a variety of coding principles.

Solutions Engineer