Useful Shell Scripts

Edward Wibowo,

#!/bin/sh: the start of a (POSIX-compliant) shell script.

As someone who spends a lot of time in a terminal emulator, shell scripts are an essential tool to get tasks done. Tucked away in my scripts directory, ~/.config/scripts/, is an array of easily accessible scripts added to my $PATH variable. On a side note: I am not entirely sure if ~/.config/scripts/ is really an appropriate location to dump scripts… 🤷

This blog post serves as a snapshot of some scripts I frequently use. You can find my other scripts somewhere in my dotfiles repository.

Recent packages (recentpkgs)

#!/bin/sh

expac --timefmt='%Y-%m-%d %T' '%l\t%n (%w)' |
	sort | grep "(explicit)" |
	if [ -n "$1" ]; then tail -n "$1"; else cat; fi |
	awk '{$NF=""}1'

Any Linux user on an Arch-based distribution can probably narrate about the vast software jungle that is the Arch User Repository (AUR). In conjunction with the well-established official Arch repositories, it is extremely easy to accumulate hundreds of unneeded packages over time. Personally, I often find myself tinkering and experimenting with a bunch of random packages. So, to avoid succumbing to a package graveyard, I created the recentpkgs script. Simply, it lists my explicitly installed packages sorted by date. If I am in the mood to clean out my system a bit, I would just run the script and prune any packages I don’t find useful.

I could also pass a number N to the script to list the N most recently installed packages. For example, the 5 most recently installed packages can be listed like so:

~ recentpkgs 5
2021-07-12 10:45:12 libxft-bgra
2021-07-12 12:42:43 sshfs
2021-07-12 14:33:14 clang
2021-07-13 10:43:30 slop
2021-07-13 11:43:54 valgrind

After this, it is just a matter of running sudo pacman -Rns on any redundant packages.

Change wallpaper (changewall)

#!/bin/sh
# Change the current wallpaper.

WALLDIR=$HOME/.config/wallpaper

# Wrapper function to identify if given program exists.
has() {
	_cmd=$(command -v "$1") 2>/dev/null || return 1
	[ -x "$_cmd" ] || return 1
}

setwall() {
	[ -f "$1" ] &&
		has xwallpaper && xwallpaper --zoom "$1"
}

usage() {
	printf "%s" "\
USAGE:
    changewall [FLAGS] [IMAGE]

FLAGS:
    -s    Use sxiv to select marked wallpaper
    -h    Prints help information
"
}

while getopts "i:slh" OPT; do
	case "$OPT" in
	s)
		has sxiv &&
			setwall "$(sxiv -ot "$WALLDIR")"
		exit 0
		;;
	h)
		usage
		exit 0
		;;
	*)
		usage
		exit 1
		;;
	esac
done

# Fallback. If no valid argument was found, try set wallpaper to first argument.
[ -n "$1" ] && setwall "$1"

On paper, one may assume that changing wallpaper is not an arduous task. Which is a correct assumption. But I prefer a bit of extra flair.

The changewall script offers two ways to change a wallpaper:

  1. Pass the path to the desired wallpaper image.
  2. Use sxiv to choose a wallpaper from a directory of images.

The first option is quite self-explanatory: you simply just pass the path as the first argument.

The second option, however, is the one I usually like to use. I have a wallpaper directory located in ~/.config/wallpapers (again, not a great place to put wallpapers) with a bunch of wallpapers I deem good enough to be a backdrop to my windows. Running with the -s argument then invokes sxiv and presents the wallpapers in thumbnail mode. After this, any images that have been marked with sxiv will have their paths printed out to standard output. This path is then set as the new wallpaper.

Query clean (queryclean)

#!/bin/sh

for d in *; do
	[ -d "$d/.git" ] &&
		[ -z "$(git --git-dir="$d/.git" --work-tree="$d" status --porcelain)" ] &&
		echo "$d"
done

If you’re like me, you may have a directory filled with random git repositories. Some of which may be amid some modifications, and others may just be collecting dust. For this reason, I created this script to quickly poll if any directories have any unstaged changes (local changes that aren’t being tracked by git). This should mean that any directories listed by the queryclean script should be safe to delete.

This script takes advantage of git’s --porcelain argument, leveraging an “easy-to-parse format for scripts”. Thus, it only takes -z to return whether or not the length of the given string is zero, corresponding to if there are any unstaged changes.