RC04: Shell Scripting Adventures

| recurse

One of the things that helped me rediscover the joy of programming was making tiny shell scripts/functions for a specific purpose.

As I started using the command line more often, these functions helped execute commands that I would otherwise have to type repeatedly. And that set off a chain reaction - the more functions I made, the more ideas I'd get.

The first one I made was tzc, to convert time from another timezone to my local time. It was inspired by two things:

Things I like about making tiny functions:

I made a couple of functions while at RC - particularly over the last two weeks - which I'll share in this post.

note #

I came across quicknote recently, and really liked the way it was structured. It made it easier to capture notes and search them, so I made its zsh equivalent:

note() (
    cd $dir
    case $1 in
        "open")    shift ; micro $@ ;;
        "ls")      fd . $dir --type file | cut -d'/' -f5- ;;
        "grep")    rg $2 ;;
        "rm")      shift ; rm $@ ;;
        *)         _usage ; return ;;

A neat trick I learnt while making this is writing the function block in regular braces () - which runs commands in a subshell as opposed to curly braces {} - to prevent changing the directory of my current shell session.

The full code with error handling and usage information can be found here.

blog #

This function is my take on a basic CLI content management system.

$ blog -h
blog - commands to manage your Jekyll blog
  blog command [args]

  -h|--help                  Show this message and exit
  new filename               Create a new post
  update commitmsg           Build and deploy changes to existing content
  publish filename           Publish post now
  publish filename time      Schedule post for a later time
  preview                    View recent post/draft locally
  preview all                View entire website locally

These commands have existed as a bunch of separate shell scripts and aliases. Some of them have featured on this blog - a publish script and a post on scheduling.

The upgraded function code can be found here.

rlist #

I use Safari's Reading List to save links, and I wanted a way to manage them from the command line, particularly to view all links and delete a link once I'm done reading.

$ rlist -h
rlist - Manage Safari's Reading List from the command line

  rlist [options] command

  -h|--help    Show this message and exit

  init         Extracts links and saves to $HOME/notes/rlist.tsv
  search       Search all links and open chosen link in browser
  delete       Delete chosen link from list

The Reading List is stored in ~/Library/Safari/Bookmarks.plist, and the function uses a utility called PlistBuddy to interact with the file.

Here's a demo of the search command, that works like a fuzzy finder, along with the option to open the link in a browser.

This is possibly the coolest script I've made yet, as it took a while to understand how PlistBuddy works, and using gum for the fancy formatting added to the coolness. 😎

mkfunc #

Lastly, a meta function whose naming is inspired by mkdir. It creates a file with starter code for a shell function when called.


mkfunc() {

    if [[ ( $# -eq 0 ) || ( "$*" =~ "-h" ) || ("$*" =~ "--help") ]] ; then
            echo "Usage: mkfunc name"

    tee $HOME/.oh-my-zsh/functions/$1 << 'EOF'
func() {
    _usage() {
        # Add help text here

    if [[ ( $# -eq 0 ) || ( "$*" =~ "-h" ) || ( "$*" =~ "--help") ]] ; then

    # Remove this block if function contains no subcommands
    case $1 in
        # Add other cases here
        *) echo "invalid option - $1" ; _usage ;;   
    chmod u+x $1
    $EDITOR $1

This function contains simpler help text, as there are no additional commands. The heredoc delimiter EOF is wrapped with single quotes to prevent execution of the variables within the heredoc ($*, $@,$1).

Thanks for tuning in to another episode of "things I didn't plan to work on"! It's interesting how I get more post ideas from side quests rather than the main ones 😅

You can find all the above scripts (and more, whenever I make them) at pjg11/scripts.