Alexander Klapheke

I’m a data scientist and self-taught coder (Python, R, Haskell) with a background in linguistics and math and over 10 years’ experience explaining technical concepts to laypeople.

Useful shell scripts

1,767 words CC-BY View source

You should have brought many things, he thought. But you did not bring them, old man. Now is no time to think of what you do not have. Think of what you can do with what there is.

With increasingly sophisticated tools available, it may seem silly to mess around with shell scripts, which are nearly half a century old and have a clunky, unintuitive syntax. Nevertheless, shell scripts are fast, ubiquitous on UNIX-based systems, and, if they live in your ~/.bashrc (or equivalent) file, always at hand. Much of their power lies in the pipe operator (|), which is basically function composition in reverse, allowing commands to be strung together without storing intermediate results. In addition, you can add custom tab completion to any function in one line with the complete command. This can make them an excellent replacement for high-level tools that, while powerful, are often built on an Indra’s net of dependencies. Below are some of the everyday tasks I’ve used the shell to make simpler.

1 Data science

A lot of data processing can be done using standard commands such as cut, paste, column, comm, and combine. For example, the following displays the first ten lines of a CSV file of any size, even a multi-gigabyte file too large to load into pandas.

Here, column -ts, formats the file into columns with a comma delimiter, and less -S overflows long lines rather than wrapping them. It’s simple enough to package this into a function that can live in your ~/.bashrc:

Using sed and cut, we can also write a function that navigates through CSV files, à la pandas’ .loc[] function, taking either single numbers or ranges (1-2) as arguments:

You can do more interesting file processing tasks as well. For a CSV file that’s truly massive, the split utility can parse it out into equally sized chunks, and a simple wrapper script can do this while preserving the header in each file:

Now you can process the file one piece at a time.

2 Jupyter

Of course, data analyses of any sophistication require more advanced tools. Jupyter notebooks are a great mini-IDE for creating literate Python programs, but hard to work with in the terminal; one glaring omission is a way to create a new notebook from the command line. This script, which lives in my ~/.bashrc, lets me type jn <filename> (with or without file extension) at the command line, opening the file if it exists and creating it if not.1

Sometimes, though, I don’t want to start a whole Jupyter server just to examine the source code in a notebook. You can export the notebooks as pure code with jupyter-nbconvert --to python, but this is slow and include a lot of cruft like cell numbers. Fortunately, the notebooks themselves are just JSON files, and are easy to parse with a tool like jq. The following function extracts the code from a notebook:

This makes it easy to treat the notebooks just like .py files:

Finally, you can add tab completion to both these functions to quickly navigate to your notebook files.

3 tmux

Another nuisance of Jupyter is that it keeps open the terminal it was launched from for writing log output, which is rarely useful. The solution is to use tmux, a “terminal multiplexer” that allows you to manage terminals by naming them, splitting them onscreen into windows, and moving them into the background (“detaching”) and foreground (“attaching”). I have a shortcut that executes tmux new-session -A -s Jupyter—this opens a “Jupyter” session, creating it if it doesn’t exist. From there, I create a new window in the session with C-b c, give it a name with C-b ,, and launch the notebook server with jn. I can then detach it with C-b d so that it’s running in the background. If I want to close down the server, I can get back to it from any terminal with C-b w.

For times when I just want to jump into a detached session to close it, I have the following helper function:

This ability to detach windows is also invaluable when using ssh. For instance, when logging onto an EC2 instance, I can attach a tmux instance on the server, name it, then detach and close the connection, coming back to it later when I think it’s almost finished. That way, I am never logged out of the server, and even if my connection breaks, the command won’t. To automate this, I put the following block in my ~/.ssh/config:

Host *.amazonaws.com
User ubuntu
IdentityFile ~/.ssh/aws-ssh.pem
RequestTTY force
RemoteCommand tmux new -A -s aws
LocalForward 9999 localhost:8888

Now, when I ssh ec2-whatever.compute.amazonaws.com, it will automatically log in and attach to the tmux instance called aws, creating it if there is none (the RequestTTY line is there to make sure that tmux starts in a shell). After running a command, I hit C-b C-b d to detach the remote session,2 and I am disconnected from the server, but the command is still running on the server.

(The LocalForward line, incidentally, sets up port forwarding for Jupyter notebooks—using one, though, means I can’t detach the remote session, as that would sever the connection between local notebook interface and remote kernel.)

4 Man page replacements

Lots of small utilities like cheat and tldr offer “example pages”, supplementing man pages by storing oft-used formulas. The pages themselves are extremely useful, making this one of my most-used commands,3 but the utilities themselves tend to be on the bloated side. It’s simple enough to implement this in a few lines of BASH.

Just run cheat <command> to view its page, and cheat -e <command> to edit it.

This template can be expanded on; for instance, if you want syntax highlighting, you can install source-highlight, and replace cat with source-highlight --src-lang shell --out-format esc --input.

5 Passwords

I like to use XKCD-style passwords. They are memorable and secure because they draw from a massive entropy pool we already carry around in our heads: the lexicon. A typical English speaker knows on the order of 10410^4 words,4 and a typical diceware wordlist contains 65=7,7766^5 = 7,776 of those. Choosing six words from the list, without replacement, provides log2(65!(656)!)77\log_2\left(\frac{ {6^5}!}{ {(6^5-6)}!}\right) \approx 77 bits of entropy.5

The --random-source flag is important here: it provides true hardware randomness, like you’d get rolling physical dice. By default, the command uses a pseudorandom number generator, which is deterministic; these can be guessed and even gamed, hurting security by making some permutations of words more likely than others.

Of course, if fatuous rules like including punctuation are enforced, you can easily generate “old-fashioned” passwords:

6 Music

It’s not really known for it, but Reddit is home to a huge network of music subreddits, each representing a genre or subgenre, to which users post YouTube or SoundCloud links. There are services that aggregate these into an internet radio station, but it is simple enough to parse the JSON yourself and pipe it to VLC (make sure it’s running an updated version of youtube.lua), or your media player of choice.

The --novideo and --preferred-resolution=360 flags save bandwidth by getting only the audio stream, and --network-caching=30000 makes sure the songs buffer long enough that there’s no lag.

We can even add “stations” manually to the tab completion command as a way of bookmarking them:

If you want to combine subreddits, you can just concatenate them with a plus sign (swinghouse+triphop); VLC will play the top songs from both.


  1. The nbformat and nbformat_minor keys are for my version of Jupyter. For other versions, you can get the appropriate numbers with:

  2. The extra prefix tells local tmux to send the command through to the server.

  3. In fact, it ties for sixth in the past ten weeks: y is a wrapper for youtube-dl, and lyrics is a BASH script that, well, looks up song lyrics. To get these stats:

  4. See, e.g., William E. Nagy and Richard C. Anderson, “How Many Words Are There in Printed School English?” Reading Research Quarterly 19, no. 3 (1984): 304–30, doi:10.2307/747823.

  5. Assuming Kerckhoffs’s principle, which I guess we’ll have to now that I’ve blogged about it.