RC04: Shell Scripting Adventures

2023-09-30

I'm attending the Fall 1 batch at Recurse Center! Posts in this series cover things I'm working on or find interesting during my time here.

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:

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

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
		
Usage:
  blog command [args]

Options:
  -h|--help                  Show this message and exit
		
Commands:
  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

Usage:
  rlist [options] command

Options:
  -h|--help    Show this message and exit

Commands:
  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.

#!/bin/zsh

mkfunc() {

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

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

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

	# Remove this block if function contains no subcommands
	case $1 in
		# Add other cases here
		*) echo "invalid option - $1" ; _usage ;;	
	esac
}
EOF	
	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.