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 }