Shell scripts

tips

zsh

load new nnmaildir groups

#!/usr/bin/zsh

set -e

MAILSPOOL="${HOME}/.mailspool"
NNMAILDIR="${HOME}/.nnmaildir"

EXCLUDE="^zz"

function find_maildirs {
    local dir="${1}"

    cd "${dir}"

    # take into account the exclude re
    for f in $(ls -d *(/) | egrep -v "${EXCLUDE}"); do
        if [ -d "${f}/new" -a -d "${f}/cur" -a -d "${f}/tmp" ]; then
            echo "$(pwd)/${f}"
        else
            find_maildirs "${f}"
        fi
    done

    cd -
} 

function tell_gnus {
    local group="${1}"

    if [ "$(emacsclient -e t)" != 't' ]; then
        return 1
    fi

    echo -n 'Attempting to tell gnus.... '
    emacsclient -e "(gnus-subscribe-group \"nnmaildir:${group}\")"
}

function main {
    cd "${NNMAILDIR}"

    for md in $(find_maildirs "${MAILSPOOL}"); do
        name="${md##*/}"

        # we make some assumptions about links pointing to the right
        # place.
        if [ ! -h "${name}" ]; then
            echo "New group '${name}'"

            ln -s "${md}" "${name}" # && tell_gnus "${name}"
        fi
    done
}

main "${@}"

cheat at scrabble

If you really must:

#!/usr/bin/env zsh

set -e

# desc:
#     Cheat at scrabble by passing in your letters and getting out a
#     word list. Optionally run a (grep -e) regex on the words.
#
# options:
#     n/a
#
# usage:
#     scrabble-cheat.zsh <letters> [regexp]
#     $ scrabble-cheat.zsh inoxaqs '^s.n.+q$'
#
# author:
#     Phil Jackson (phil@shellarchive.co.uk)

CHEAT_URL="http://spod.cx/cheat-o-matic.shtml?letters="
CACHE_LOC="${HOME}/.scrabble-cheat/"

typeset -A SCORES
SCORES=(
    A 1 B 3 C 3 D 2 E 1 F 4 G 2 H 4 I 1 J 8 K 5 L 1 M 3
    N 1 O 1 P 3 Q 10 R 1 S 1 T 1 U 1 V 4 W 4 X 8 Y 4 Z 10
    _ 0
)

function get_words {
    local letters="${(j::)${(os::)1}}"

    # grab from cache if we can
    if [ -f "${CACHE_LOC}/${letters}" ]; then
        echo "wordlist from cache:" >&2
        words=$(cat "${CACHE_LOC}/${letters}")
    else
        words=$(wget -O - "${CHEAT_URL}${letters}" 2>/dev/null | \
            perl -ne 'm{/words/info.shtml\?word=(\w+)} and print "$1\n"')

        echo "${words}" > "${CACHE_LOC}/${letters}"
    fi

    echo "${words}"
}

function main {
    letters="${1}"
    re="${2}"

    [[ ! -d "${CACHE_LOC}" ]] && mkdir -p "${CACHE_LOC}"

    while getopts :v OPT; do
        case $OPT in
            v|+v)
                verbose=1
                ;;
            *)
                print "usage: ${0##*/} [+-v} [--] letters..."
                exit 2
        esac
    done
    shift OPTIND-1
    OPTIND=1

    # print the words with SCORES (sorted by score)
    words=$(get_words "${letters}")
    for w in ${(ps:\n:)words}; do
        sum=0
        for l in ${(s::)w}; do
            (( sum = sum + ${SCORES[${l}]} ))
        done

        # did we get a bingo?
        if [[ ${#w} > 7 ]]; then
            (( sum += 50 ))
        fi

        if [[ -n "${re}" ]]; then
            if echo ${w} | perl -ne "/${re}/i or exit 1" &>/dev/null; then
                printf "%3d: %s\n" ${sum} ${w}
            fi
        else
            printf "%3d: %s\n" ${sum} ${w}
        fi
    done | sort -n
}

main "${@}"

zsh ps1 and rps1 for developers

As I do lots of interactive development I use the following PS1 and RPS1 in zsh so that I can keep an eye on ${?}:

PS1='%m $ '
RPS1=$'%1~/ %(?.%{\e[0;32m%}ok.%{\e[0;31m%}failed %?)%{\e[0m%}'

This will give you the host and the '$' prompt on the left with the cwd and result of the last command on the right like this:

re-compile and re-source your profile

function src {
    autoload -U zrecompile

    for f in ~/.zshrc ~/.zcompdump ~/.zsh/completion; do
        [ -f "${f}" ] && zrecompile -p "${f}"
    done

    source ~/.zshrc
}

set the xterm title to the name of the current/last command

# set xterm titles upto date
function precmd { print -Pn "\e]0;%m:%~\a" }
function preexec { print -Pn "\e]0;$1\a" }

saner word boundaries in zle

If you use emacs and M-f/M-b then you might want to do the following:

export WORDCHARS=''

insert a newline

Inserting a newline with zle can be accomplished with:

bindkey "^J" self-insert

bash

recursively execute a command (bash)

#!/bin/bash

# desc:
#     Execute the command given on the command line
#     recursivly on all of the directories below the
#     cwd.
#
# options:
#     -c: cause all.bash to process cwd as well as all
#         directories below cwd.
#     -v: make the ouput more verbose
#     -h: show basic usage
#
# usage:
#     all.bash [OPTIONS] [FILES]...
#     $ all.bash -c '[ -x go.bash ] && ./go.bash'
#
# author:
#     Phil Jackson (phil@shellarchive.co.uk)

VERBOSE=0
CURRENT=0
usage="${0##*/} [+-vch} [--] command"

function transverse {
    for file in *; do
        if [ -d "${file}" ]; then
            # Move to subdir
            cd "${file}"

            # Tell the user
            [ ${VERBOSE} -eq 1 ] && echo "${PWD}:" >&2

            # Exec our arguments
            eval "${@}"

            # Call this function from new dir
            transverse "${@}"

            # Back to original dir
            cd ..
        fi
    done
}

while getopts :cvh OPT; do
    case ${OPT} in
        v|+v)
            VERBOSE=1
            ;;
        c|+c)
            CURRENT=1
            ;;
        h|+h)
            echo ${usage} >&2
            ;;
        *)
            echo ${usage} >&2
            exit 2
            ;;
    esac
done

shift $[ OPTIND - 1 ]

[ ${#} -lt 1 ] && {
    echo ${usage} >&2
    exit 2
}

# Exec in current dir if user asked for it
[ ${CURRENT} -eq 1 ] && eval "${@}"

transverse "${@}"

randomise lines of files/input

#!/usr/bin/env bash

# desc:
#     Randomise the lines given from a pipe or from
#     files on the command line
#
# options:
#     none
#
# usage:
#     randomise.bash [FILE]...
#     $ ls -1 | randomise.bash
#     $ randomise.bash ~/file1.txt ~/file2.txt
#
# author:
#     Phil Jackson (phil@shellarchive.co.uk)

# prepend RANDOM, sort then cut the random number
function do_randomisation {
    while read line; do
        echo "$RANDOM $line"
    done | sort -n | cut -d ' ' -f '2-'
}

# process stdin over files
if [ -t 0 ]; then
    if [ ${#} -lt 1 ]; then
        echo "Please supply some input/filenames" >&2
        exit 1
    fi

    cat "${@}" | do_randomisation
else
    do_randomisation
fi

get a random file from cwd

#!/usr/bin/env bash

# desc:
#     Get a random file/dir from cwd. This script
#     requires randomise.bash which is available from
#     shellarchive.co.uk.
#
# options:
#     none
#
# usage:
#     getrandfile.bash [TYPE]
#     $ getrandfile.bash
#     $ getrandfile.bash f # return a file
#     $ getrandfile.bash d # return a dir
#
# todo:
#     Should be much more configurable.
#     _Really_ needs to return 1 on failure!
#
# author:
#     Phil Jackson (phil@shellarchive.co.uk)

find "${1:-.}" -maxdepth 1 -type "${2:-f}" \! -name '.*' | \
    randomise.bash | tail -n 1

generic...ish

Merge directory contents (with links)

I wrote this as part of my dotfiles installation procedure. If you have directories 1, 2 and 3 all with files you want to link into ${HOME} then running:

link-files 1 "${HOME}" \
    && link-files 2 "${HOME}" \
    && link-files 3 "${HOME}"

Will do the trick, assuming this is defined:

function link-files {
    from="${1}"; to="${2}"

    # first we need the same directory structure
    ( cd "${from}" && find . \( \( -name "..?" -or -name \*.elc \) \
        -prune -or -true \) -type d -exec mkdir -p "${to}/"\{\} \; )

    # now link the files to "to"
    ( cd "${from}" && find . -type f -exec ln -fs \
        "${from}/"\{\} "${to}/"\{\} \; )
}

a template for a 'one at a time' script

#!/usr/bin/env bash

set -e

PID_FILE="$(basename ${0%.*}).pid"

function is_already_running {
    pid_file="${1}"

    if [ -f "${pid_file}" ]; then
        current_pid=$(cat "${pid_file}")
        if ps -p "${current_pid}" >/dev/null; then
            return 0
        else
            # file is there but shouldn't be..
            rm -f "${pid_file}"
        fi
    fi

    # no other script is running
    return 1
}

function main {
    if is_already_running "${PID_FILE}"; then
        echo "Another copy of '$(basename ${0})' is already running" >&2
        exit 1
    else
        echo ${$} > "${PID_FILE}"
    fi

    # try our hardest to always get rid of pidfile when we EXIT
    trap 'rm -f "${PID_FILE}" >/dev/null 2>&1' 0
    trap "exit 2" 1 2 3 15
}

main "${@}"

execute a script on entering a directory

Stick an executable script called .on-enter-script in a directory, then put the following snippet into your .*rc and now when you cd around it will be executed. Note that this has potential security implications.

function cd {
    ENTER_SCRIPT='./.on-enter-script'

    if builtin cd "${@}"; then
        if [ -f "${ENTER_SCRIPT}" -a -x "${ENTER_SCRIPT}" ]; then
            "${ENTER_SCRIPT}"
        fi
    fi
}