kota's memex

see also

zsh

bash

resources

https://shellhaters.org/

https://github.com/dylanaraps/pure-sh-bible

arguments

$0 - name of command itself
$1 - first argument
$# - count of arguments

default variable assignment

If variable not set or null, use default. If VARIABLE was unset or null, it still is after this (no assignment done).

FOO="${VARIABLE:-default}"

If variable not set or null, set it to default.

FOO="${VARIABLE:=default}"

if statements

if [ $1 -gt 100 ]
then
  echo "big"
else
  echo "small"
fi

Learn about the different comparisons with man test.

basic looping

for loops iterate through a set of values until the list is exhausted:

for i in 1 2 3 4 5
do
  echo "$i"
done

while loops run a block of code while a condition is true:

count=0
while [ "$count" != 100 ]
do
	echo $count
	count=$((count+1))
done

loop over files and directories

# Greedy example.
for file in *; do
    [ -e "$file" ] || [ -L "$file" ] || continue
    printf '%s\n' "$file"
done

# PNG files in dir.
for file in ~/Pictures/*.png; do
    [ -f "$file" ] || continue
    printf '%s\n' "$file"
done

# Iterate over directories.
for dir in ~/Downloads/*/; do
    [ -d "$dir" ] || continue
    printf '%s\n' "$dir"
done

replace symlinks with the files they link to

unsym is a good name

#!/bin/sh
set -e
for link; do
    test -h "$link" || continue

    dir=$(dirname "$link")
    reltarget=$(readlink "$link")
    case $reltarget in
        /*) abstarget=$reltarget;;
        *)  abstarget=$dir/$reltarget;;
    esac

    rm -fv "$link"
    cp -afv "$abstarget" "$link" || {
        # on failure, restore the symlink
        rm -rfv "$link"
        ln -sfv "$reltarget" "$link"
    }
done

redirecting to /dev/null

Don't do it lol: https://torresjrjr.com/archive/2021-05-03-stop-using-dev-null/

# Silence the stdout output (fd 1) of ping (only print errors)
ping fediverse.party >&-

# Silence all output of curl
curl -v https://fediverse.party  >&- 2>&-

idempotent scripts

Idempotent scripts can be called multiple times and each time it's called, it will have the same effects on the system. This means, a second call will exit with the same result and won’t have any side effects. For example, mkdir is non-idempotent by default. If called twice it will error the second time. The -p flag suppresses this error, fixing mkdir.

creating an empty file

Touch is by default idempotent.
touch example.txt

creating a directory

mkdir -p mydir

creating a symbolic link

ln -sf source target

removing a file

rm -f example.txt

modifying a file

Sometimes you're adding a new line to an existing file (i.e: /etc/fstab). This means, you need to make sure not to add it the second time if you run your script. One way of making this idempotent is to make sure to check for certain placeholders via grep:

if ! grep -qF "/mnt/dev" /etc/fstab; then
  echo "/dev/sda1 /mnt/dev ext4 defaults 0 0" | sudo tee -a /etc/fstab
fi

Here the -q means silent mode and -F enables fixed string mode. Grep will silently fail if /mnt/dev doesn't exist so the echo statement will never be called.

formatting a device

blkid "$VOLUME_NAME" || mkfs.ext4 "$VOLUME_NAME"
This command prints attributes for a given block device. Hence prepending basically means to proceed with formatting only when blkid fails, which is an indication that the given volume is not formatted yet.

mounting a device

The mountpoint command checks whether a file or directory is a mount point. The -q flag just makes sure it doesn't output anything and silently exits. In this case, if the mount point doesn't exist, it’ll go forward and mount the volume.

if ! mountpoint -q "$DATA_DIR"; then
  mount -o discard,defaults,noatime "$VOLUME_NAME" "$DATA_DIR"
fi

strip trailing /

When getting a path from an argument you often want to strip off the last / if it exists.
REPO_DIR=${1%%"${1##*[!/]}"}