├── printscreen ├── README ├── mailrun ├── randomWallpaper ├── randomJunk ├── git-archprojects ├── getProp ├── simplentp ├── waffles ├── fakepac ├── hex2rgb ├── git-blamestat ├── aurdiff ├── detect-virt.c ├── bcc ├── ig ├── permpath ├── pqu ├── git-out ├── geoloc ├── sortwp ├── list2html ├── windows ├── dmenu_launch ├── resquash ├── ethstat ├── elf2pkgs ├── mancx ├── errno ├── walk ├── aurcomm ├── csv2html ├── qinit ├── pp ├── xo ├── sabs ├── hglink ├── alsavol ├── sogrep ├── git-all ├── latest ├── git-retrotag ├── flac2mp3 ├── kload ├── boxes ├── sprunge └── orb /printscreen: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | exec scrot "$@" '%Y.%m.%d-$wx$h.png' 4 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | A collection of Bash scripts I've written and use on a semi-regular basis. 2 | 3 | -------------------------------------------------------------------------------- /mailrun: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | pgrep offlineimap >/dev/null || offlineimap -o -u quiet >/dev/null & 4 | 5 | exit 0 6 | -------------------------------------------------------------------------------- /randomWallpaper: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | find "$HOME/var/pic/wallpaper/16:10" -type f -print0 | shuf -z -n1 4 | [[ -t 1 ]] && echo 5 | 6 | -------------------------------------------------------------------------------- /randomJunk: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ALLOWEDCHARS='[:alnum:][:punct:]' 4 | 5 | length=${1:-10} quantity=${2:-1} 6 | 7 | < /dev/urandom tr -dc $ALLOWEDCHARS | fold -w $length | head -n $quantity 8 | -------------------------------------------------------------------------------- /git-archprojects: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if (( $# < 2 )); then 4 | echo "usage: ${0##*/}: " 5 | exit 1 6 | fi 7 | 8 | exec git format-patch --to='arch-projects@archlinux.org' --subject-prefix="$1][PATCH" "${@:2}" 9 | 10 | -------------------------------------------------------------------------------- /getProp: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #xprop |awk ' 4 | # /^WM_CLASS/{sub(/.* =/, "instance:"); sub(/,/, "\nclass:"); print} 5 | # /^WM_NAME/{sub(/.* =/, "title:"); print}' 6 | 7 | xprop | sed -n ' 8 | /^WM_CLASS/s/.* = "\(.*\)", "\(.*\)"/instance: \1\nclass: \2/p; 9 | /^WM_NAME/s/.* = "\(.*\)"/title: \1/p' 10 | -------------------------------------------------------------------------------- /simplentp: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | (( UID == 0 )) || { echo "Error: Must be root!"; exit 1; }>&2 4 | 5 | printf "Setting hwclock to localtime from time.nist.gov...\n" 6 | hwclock --set --date="$(awk 'NF > 1 {print $2,$3,"UTC"}' < /dev/tcp/time.nist.gov/13)" || exit 1 7 | printf "Sync'ing hwclock to system...\n" 8 | hwclock -s 9 | printf "The time is now: %s\n" "$(hwclock)" 10 | -------------------------------------------------------------------------------- /waffles: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | WAFFLE_DIR="/mnt/Gluttony/waffles" 4 | 5 | declare -a waffles 6 | while read -d $'\0' waffle; do 7 | waffles+=("$waffle") 8 | done < <(find "$WAFFLE_DIR" -links 1 -iregex '.*\.\(flac\|mp3\)' -printf '%h\0' | uniq -z) 9 | 10 | if [[ -n $1 ]]; then 11 | items=("${waffles[@]:$1:${2:-1}}") 12 | else 13 | items=("${waffles[@]}") 14 | fi 15 | 16 | (( ${#items[@]} )) && printf "%q\n" "${items[@]}" 17 | 18 | -------------------------------------------------------------------------------- /fakepac: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | shopt -s extglob 4 | 5 | fakedb=/dev/shm/fakepacdb 6 | realdb=/var/lib/pacman 7 | 8 | [[ ! -d $fakedb ]] && { mkdir -p "$fakedb/sync" || exit 1; } 9 | [[ ! -L $fakedb/local ]] && { ln -s "$realdb/local" "$fakedb" || exit 2; } 10 | 11 | case $1 in 12 | -Sy|-Syy) exec fakeroot pacman --dbpath "$fakedb" $1 ;; 13 | -@(S|U)*) echo "action not allowed" ;; 14 | *) exec pacman --dbpath "$fakedb" $1 ;; 15 | esac 16 | 17 | -------------------------------------------------------------------------------- /hex2rgb: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | [[ -z $1 ]] && { 4 | printf "Usage: %s " "${0##*/}" >&2 5 | exit 1 6 | } 7 | 8 | hex2dec() { 9 | for val; do 10 | printf "%3d " "0x$val" 11 | done 12 | printf "\n" 13 | } 14 | 15 | printf "\t\t%3s %3s %3s\n" "R" "G" "B" 16 | while read line; do 17 | [[ ! $line =~ ^\*color ]] && continue 18 | printf "${line% *}\t" 19 | hexcode=${line/\*color* /} 20 | hex2dec ${hexcode:1:2} ${hexcode:3:2} ${hexcode:5:2} 21 | done < $1 22 | 23 | -------------------------------------------------------------------------------- /git-blamestat: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # show code ownership of a git repo by lines of code 4 | # 5 | # modified from original, found at: 6 | # http://stackoverflow.com/questions/4589731/git-blame-statistics 7 | # 8 | 9 | IFS=$'\n' read -r -d '' -a all < \ 10 | <(git ls-files | file -f - | sed '/: .*text/s/: .*//') 11 | 12 | for file in "${@:-${all[@]}}"; do git blame -M -C "$file"; done | 13 | sed -r 's/.*\((.*)[0-9]{4}-[0-9]{2}-[0-9]{2} .*/\1/;s/ +$//' | 14 | sort | 15 | uniq -c | 16 | sort -n 17 | 18 | -------------------------------------------------------------------------------- /aurdiff: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # diff an AUR pkgbuild based on your local copy 5 | # 6 | 7 | DIFF=${DIFF:-diff} 8 | 9 | if [[ ! -f PKGBUILD ]]; then 10 | printf "error: No PKGBUILD found in working directory.\n" 11 | exit 1 12 | fi >&2 13 | 14 | eval $(grep '^pkgname=' PKGBUILD) 15 | if [[ -z $pkgname ]]; then 16 | printf "error: pkgname not found in PKGBUILD\n" 17 | exit 1 18 | fi >&2 19 | 20 | diff ${@:--u} PKGBUILD \ 21 | <(curl -s "https://aur.archlinux.org/packages/${pkgname:0:2}/$pkgname/PKGBUILD") 22 | -------------------------------------------------------------------------------- /detect-virt.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #if defined (__i386__) 4 | #define REG_a "eax" 5 | #define REG_b "ebx" 6 | #elif defined (__amd64__) 7 | #define REG_a "rax" 8 | #define REG_b "rbx" 9 | #endif 10 | 11 | int main(void) 12 | { 13 | uint32_t eax, ecx; 14 | 15 | eax = 1; 16 | __asm__ __volatile__ ( 17 | " push %%" REG_b " \n\t" 18 | " cpuid \n\t" 19 | " pop %%" REG_b " \n\t" 20 | 21 | : "=a" (eax), "=c" (ecx) 22 | : "0" (eax) 23 | ); 24 | 25 | return !!!(ecx & 0x80000000U); 26 | } 27 | -------------------------------------------------------------------------------- /bcc: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | CC="gcc -std=c99" 4 | CFLAGS="-O2 -pipe -Wall -Wextra -pedantic" 5 | src=/tmp/tmp$$.c 6 | out=/tmp/tmp$$ 7 | 8 | trap 'rm -f "$out" "$src"' EXIT 9 | 10 | cat > "$src" < 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | int main(void) { 23 | $@ 24 | return 0; 25 | } 26 | HEREDOCTHEREDOCEVERYWHEREADOCDOC 27 | 28 | $CC $CFLAGS $src -o "$out" && "$out" 29 | 30 | -------------------------------------------------------------------------------- /ig: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # 'interactive' grep 5 | # 6 | 7 | showfile() { 8 | local file linenum choice=$1 9 | 10 | # sanitize $choice of control characters and split 11 | IFS=':' read file linenum _ < <(sed 's/\[[[:digit:]]*[mK]//g' <<< "$choice") 12 | 13 | vim "$file" +$linenum 14 | } 15 | 16 | (( $# < 2 )) && { echo "usage: ${0##*/} [grep-options] files..."; exit 1; } >&2 17 | 18 | IFS=$'\n' read -r -d $'\0' -a results < <(grep --color=always -n "$@") 19 | 20 | (( ${#results[@]} )) || exit 1 21 | 22 | PS3="select a # or ^C to end] " 23 | while [[ -z $choice ]]; do 24 | select choice in "${results[@]}"; do 25 | showfile "$choice" 26 | done 27 | done 28 | 29 | -------------------------------------------------------------------------------- /permpath: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # list directory permissions along the path 3 | 4 | shopt -s extglob 5 | 6 | rlflags='-' 7 | while getopts fem opt; do 8 | [[ $opt == @(f|e|m) ]] && rlflags+="$opt" 9 | done 10 | shift $(( OPTIND - 1 )) 11 | 12 | [[ $rlflags = - && ! -L ${!:PWD} ]] && rlflags='-f' 13 | 14 | target=$(readlink $rlflags "${1:-$PWD}") 15 | 16 | if [[ ! -e $target ]]; then 17 | echo "${0##*/}: cannot stat \`$target': No such file or directory" 18 | exit 1 19 | fi 20 | 21 | IFS='/' read -r -a dirpath <<< "$target" 22 | 23 | for part in "${dirpath[@]}"; do 24 | working+="$part" 25 | [[ -z $working ]] || stat $working --printf "%A %U %G %n\n" 26 | working+='/' 27 | done | column -t 28 | 29 | -------------------------------------------------------------------------------- /pqu: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ignored() { 4 | local pkg=$1 5 | for i in "${ignored[@]}"; do 6 | [[ $pkg = $i ]] && return 0 7 | done 8 | return 1 9 | } 10 | 11 | while read -a i; do 12 | ignored+=("${i[@]}") 13 | done < <(sed '/^[[:space:]]*IgnorePkg/!d;s|.*=[[:space:]]*||' /etc/pacman.conf) 14 | 15 | declare -A updates 16 | while read upd ver; do 17 | if ignored "$upd"; then 18 | printf ':: ignoring %s\n' "$upd" 19 | continue 20 | fi 21 | updates["$upd"]=$ver 22 | done < <(pacman -Qu) 23 | 24 | (( ${#updates[*]} )) || exit 0 25 | 26 | while read pkg newver; do 27 | printf '%-40.40s %12s => %s\n' "$pkg" "${updates[${pkg#*/}]}" "$newver" 28 | done < <(expac -1S '%r/%n %v' "${!updates[@]}") | sort 29 | 30 | exit 1 31 | -------------------------------------------------------------------------------- /git-out: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # behavior like mercurial's "out" command 4 | # 5 | 6 | shopt -s extglob 7 | 8 | curbranch=$(git symbolic-ref -q HEAD) 9 | curbranch=${curbranch#refs/heads/} 10 | 11 | if [[ -z $curbranch ]]; then 12 | printf 'error: current branch is unknown!\n' >&2 13 | exit 1 14 | fi 15 | 16 | [[ $1 ]] && IFS='/' read -r remote merge <<< "$1" 17 | 18 | if [[ -z $remote ]]; then 19 | remote=$(git config "branch.$curbranch.remote" 2>/dev/null || echo "origin") 20 | remote=${remote##*/} 21 | fi 22 | 23 | if [[ -z $merge ]]; then 24 | merge=$(git config "branch.$curbranch.merge" 2>/dev/null || echo "$curbranch") 25 | merge=${merge##*/?(+)} 26 | fi 27 | 28 | printf 'Comparing with %s/%s\n' "${remote:-origin}" "${merge:-master}" 29 | git cherry -v ${remote:-origin}/${merge:-master} 30 | -------------------------------------------------------------------------------- /geoloc: -------------------------------------------------------------------------------- 1 | #!/bin/gawk -f 2 | 3 | function getcoord(string, a) { 4 | split(string, a, ":") 5 | gsub(/\"/, "", a[2]) 6 | return a[2] 7 | } 8 | 9 | function httpget() { 10 | host = "geoiplookup.wikimedia.org" 11 | socket = "/inet/tcp/0/" host "/80" 12 | 13 | printf "GET / HTTP/1.1\r\nHost: %s\n\n", host |& socket 14 | for (header in headers) { 15 | printf "%s\r\n", headers[header] |& socket 16 | } 17 | printf "\r\n" |& socket 18 | 19 | NR = 0 20 | in_body = 0 21 | while (socket |& getline) { 22 | if (/^\r$/) { 23 | socket |& getline 24 | return $0 25 | } 26 | } 27 | } 28 | 29 | BEGIN { 30 | data = httpget() 31 | 32 | split(data, coords, /,/) 33 | lat = getcoord(coords[3]) 34 | lon = getcoord(coords[4]) 35 | 36 | printf "http://maps.google.com/maps?q=%s,%s\n", lat, lon 37 | } 38 | -------------------------------------------------------------------------------- /sortwp: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if ! type -P identify >/dev/null; then 4 | echo "identify not found -- missing imagemagick?" 5 | exit 1 6 | fi 7 | 8 | wallpaper_root="$HOME/pic/wallpaper" 9 | 10 | while read -r -d '' pic; do 11 | read -r width height < <(identify '%w %h' "$pic") 12 | 13 | case $(echo "scale=3;$width / $height" | bc) in 14 | 1.777) ratio='16:9' ;; 15 | 1.600) ratio='16:10' ;; 16 | 1.333) ratio='4:3' ;; 17 | 1.250) ratio='5:4' ;; 18 | *) ratio='other' 19 | printf -v prefix '%sx%x_' "$width" "$height" ;; 20 | esac 21 | 22 | if [[ $ratio && ! -d $wallpaper_root/$ratio/$widthx$height ]]; then 23 | mkdir -vp "$wallpaper_root/$ratio/${width}x$height" 24 | fi 25 | 26 | mv -v "$pic" "$wallpaper_root/$ratio/$prefix${pic##*/}" 27 | unset prefix 28 | done < <(find "${1:-.}" -maxdepth 1 -type f -print0) 29 | 30 | -------------------------------------------------------------------------------- /list2html: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | (( $# != 2 )) && { printf "Usage: %s \n" "${0##*/}"; exit 1; } >&2 4 | 5 | # Redirect STDOUT to an html file 6 | exec >> output.html 7 | 8 | # Start table 9 | printf "\n" 10 | 11 | count=0 # Initialize a counter for columns 12 | while read line; do 13 | if (( ! count )); then 14 | # We're at the start of a new row, open it. 15 | printf "\t\n" 16 | fi 17 | 18 | if (( count < $1 )); then 19 | # Print next line from data file 20 | printf "\t\t\n" "$line" 21 | fi 22 | 23 | (( count++ )) 24 | 25 | if (( count == $1 )); then 26 | # We're at the end of a row, close it. 27 | printf "\t\n" 28 | count=0 29 | fi 30 | done < $2 31 | 32 | # Kludge for when columns doesn't divide equally into data size 33 | (( count )) && printf "\t\n" 34 | 35 | # End table 36 | printf "
%s
\n" 37 | -------------------------------------------------------------------------------- /windows: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # a useless progress bar, windows style. doesn't tell you anything except that 5 | # this particular process hasn't (yet) crashed. 6 | # 7 | 8 | declare bc1='-' # "filled" progress 9 | declare bc2='-' # "unfilled" progress 10 | declare pulser='<│││>' # windows is awesome 11 | declare -i bw=40 # bar width 12 | declare -i pw=${#pulser} # pulser width 13 | st='.02s' # sleep time 14 | 15 | printbar() { 16 | # leading visible bar 17 | printf '\e[1;35m%*s\e[0m' "$1" | tr ' ' "$bc1" 18 | 19 | # pulser 20 | printf '\e[1;34m%s\e[0m' "$pulser" 21 | 22 | # trailing visible bar 23 | printf '\e[1;35m%*s\e[0m\r' "$(( bw - pw - $1 ))" | tr ' ' "$bc2" 24 | 25 | sleep $st 26 | } 27 | 28 | while true; do 29 | for (( i = 0; i < bw-pw; i++ )); do 30 | printbar $i 31 | done 32 | for (( i = bw-pw; i > 0; i--)); do 33 | printbar $i 34 | done 35 | done 36 | 37 | -------------------------------------------------------------------------------- /dmenu_launch: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | font="-*-nu-*-*-*-*-11-*-*-*-*-*-*-*" 4 | term=urxvtc 5 | normbgcolor="#303030" 6 | normfgcolor="#FFFFFF" 7 | selbgcolor="#3465A4" 8 | selfgcolor="#FFFFFF" 9 | 10 | cache=~/.cache/dmenu 11 | 12 | dmenu_opts=( 13 | -i 14 | -b 15 | -fn "$font" 16 | -nb "$normbgcolor" 17 | -nf "$normfgcolor" 18 | -sb "$selbgcolor" 19 | -sf "$selfgcolor" 20 | ) 21 | 22 | regen_cache() { 23 | find "${paths[@]}" -maxdepth 1 \( -type f -o -type l \) -executable -printf '%f\n' | 24 | sort -u >"$cache" 25 | } 26 | 27 | 28 | getbins() { 29 | IFS=':' read -r -a paths <<< "$PATH" 30 | for path in "${paths[@]}"; do 31 | if [[ $path -nt $cache ]]; then 32 | regen_cache 33 | break 34 | fi 35 | done 36 | cat "$cache" 37 | } 38 | 39 | cmd=$(getbins | dmenu "${dmenu_opts[@]}") 40 | 41 | # dispatch! 42 | case $cmd in 43 | ncmpcpp|htop|vim) exec $term -name $cmd -e $cmd ;; 44 | tmux) exec $term -name tmux -geometry 122x77 -e tmux -L main attach ;; 45 | *) exec $cmd 46 | esac 47 | 48 | -------------------------------------------------------------------------------- /resquash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | . /etc/rc.conf 4 | . /etc/rc.d/functions 5 | SQUASH_ROOT=/squashed 6 | 7 | if [[ -z $1 ]] || [[ ! -d ${SQUASH_ROOT}/$1 ]]; then 8 | echo "Invalid target or none specified. Valid targets are: " 9 | squashes=($(ls $SQUASH_ROOT)) 10 | echo -e "\n Used\tDir\n ---------------" 11 | for dir in ${squashes[*]}; do 12 | echo -e " $(du -sh ${SQUASH_ROOT}/${dir}/rw 2> /dev/null | cut -d\/ -f1)${dir}" 13 | done 14 | echo 15 | exit 1 16 | fi 17 | 18 | if [[ $UID -ne 0 ]]; then 19 | sudo $0 $* 20 | exit 21 | fi 22 | 23 | stat_busy "Squashing /${1}" 24 | mksquashfs /$1 ${SQUASH_ROOT}/${1}/${1}_tmp.sfs -b 65536 || exit 1 25 | stat_done 26 | 27 | stat_busy "Unmounting old UnionFS" 28 | umount -l /${1} 29 | umount -l -d ${SQUASH_ROOT}/${1}/ro 30 | stat_done 31 | 32 | stat_busy "Replacing with new SquashFS" 33 | rm ${SQUASH_ROOT}/${1}/${1}.sfs 34 | mv ${SQUASH_ROOT}/${1}/${1}{_tmp,}.sfs 35 | rm -rf ${SQUASH_ROOT}/${1}/rw/* 36 | stat_done 37 | 38 | stat_busy "Mounting new /${1}" 39 | mount ${SQUASH_ROOT}/${1}/ro 40 | mount /${1} 41 | stat_done 42 | -------------------------------------------------------------------------------- /ethstat: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec gawk ' 4 | function humanize_size(size, count, sizestr) { 5 | count = 1 6 | 7 | while (size + 0 > 1024) { 8 | size /= 1024 9 | count++ 10 | } 11 | 12 | sizestr = sprintf("%.2f", size) 13 | sub(/\.?0+$/, "", sizestr) 14 | return sprintf("%s %s", sizestr, suffix[count]) 15 | } 16 | 17 | BEGIN { 18 | # preload array for humanize_size 19 | split("B KiB MiB GiB TiB PiB EiB ZiB YiB", suffix) 20 | 21 | # build array of devices and stats 22 | for(i = 1; i < ARGC; i++) { 23 | rc = split(ARGV[i], fn, /\//) 24 | getline devices[fn[5]][fn[7]] < ARGV[i] 25 | } 26 | 27 | # dump 28 | printf "%8s%12s%12s%8s\n", "Device", "Sent", "Received", "Ratio" 29 | for (dev in devices) { 30 | tx_human = humanize_size(devices[dev]["tx_bytes"]) 31 | rx_human = humanize_size(devices[dev]["rx_bytes"]) 32 | ratio = devices[dev]["rx_bytes"] ? devices[dev]["tx_bytes"] / devices[dev]["rx_bytes"] : 0 33 | 34 | printf "%8s%12s%12s%8.2f\n", dev, tx_human, rx_human, ratio 35 | } 36 | } 37 | ' /sys/class/net/*/statistics/[tr]x_bytes 38 | -------------------------------------------------------------------------------- /elf2pkgs: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # resolve ELF library deps to pacman packages 4 | # 5 | 6 | declare -r lddregex=$'(.+) => (.+) \(0x[a-fA-F0-9]+\)$' 7 | 8 | resolve_bin() { 9 | declare -A depmap 10 | declare -a linkage 11 | 12 | # leverage the linker to do lib => path resolution 13 | local lddout=$(ldd "$1" 2>/dev/null) || return 14 | 15 | while read -r line; do 16 | [[ $line =~ $lddregex ]] || continue 17 | depmap["${BASH_REMATCH[1]}"]=${BASH_REMATCH[2]} 18 | done <<< "$lddout" 19 | 20 | # dump directly linked deps, printing paths from our ldd "map" 21 | objdump -p "$1" 2>/dev/null | while read section soname; do 22 | [[ $section == NEEDED && ${depmap[$soname]} ]] && printf '%s\n' "${depmap[$soname]}" 23 | done | pacman -Qqo - 2>/dev/null 24 | } 25 | 26 | resolve_dir() { 27 | local bin 28 | 29 | while read -rd '' bin; do 30 | resolve_bin "$bin" 31 | done < <(find "$1" -type f -executable -print0) 32 | } 33 | 34 | for bin; do 35 | if [[ -f $bin ]]; then 36 | resolve_bin "$bin" 37 | elif [[ -d $bin ]]; then 38 | resolve_dir "$bin" 39 | fi 40 | done | sort -u 41 | -------------------------------------------------------------------------------- /mancx: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # http://man.cx manpage extractor 5 | # requires: xmllint (libxml2) 6 | # 7 | 8 | BASEURL='http://man.cx' 9 | XMLLINT=('xmllint' '--html') 10 | XPATH_EXPR='//*[@id="manpage"]' 11 | 12 | usage() { 13 | printf "Usage: %s [section] manpage" "${0##*/}" >&2 14 | } 15 | 16 | # less gets color, else its a NOOP 17 | if [[ ${PAGER:-less} = less ]]; then 18 | colorify() { 19 | sed '/^[[:upper:][:blank:]]\+$/{ 20 | s/^/'"$LESS_TERMCAP_md"'/ 21 | s/$/'"$LESS_TERMCAP_ue"'/ 22 | }' 23 | } 24 | else 25 | colorify() { cat; } 26 | fi 27 | 28 | case $# in 29 | 1) PAGE=$1 ;; 30 | 2) SECTION="($1)" 31 | PAGE=$2 ;; 32 | *) usage; exit 1 ;; 33 | esac 34 | 35 | # if found locally, exit 36 | man ${SECTION//[^[:digit:]]/} $PAGE 2>/dev/null && exit 37 | 38 | manpage=$(curl -s $BASEURL/$PAGE$SECTION | 39 | "${XMLLINT[@]}" --xpath "$XPATH_EXPR" - 2>/dev/null | 40 | w3m -T text/html | 41 | colorify) 42 | 43 | if [[ $manpage ]]; then 44 | "${PAGER:-less}" <<< "$manpage" 45 | else 46 | printf "No mancx entry for %s\n" "$PAGE" 47 | exit 1 48 | fi 49 | -------------------------------------------------------------------------------- /errno: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # tool to convert symbols like EPERM to their numeric values and vice versa 4 | # 5 | 6 | shopt -s extglob 7 | 8 | base=/usr/include/asm-generic/errno-base.h 9 | upper=/usr/include/asm-generic/errno.h 10 | 11 | exec 0< <(cat "$base" "$upper") 12 | 13 | numeric_to_symbol() { 14 | awk -v "num=$1" '/^#define/ && $3 == num { 15 | printf "%s = '$1'\n", $2 16 | exit 1 17 | }' 18 | } 19 | 20 | symbol_to_numeric() { 21 | awk -v "sym=$1" '/^#define/ && $2 == sym { 22 | printf "'$1' = %s\n", $3 23 | exit 1 24 | }' 25 | } 26 | 27 | perror() { 28 | printf '#include 29 | #include 30 | int main(void) { errno = %s; perror("message"); return 0; } 31 | ' "$1" | gcc -x c - -o /tmp/a.out; [[ -x /tmp/a.out ]] && /tmp/a.out; rm -f /tmp/a.out 32 | } 33 | 34 | [[ $1 ]] || { printf 'usage: %s [symbol|errno]\n' "${0##*/}"; exit 1; }>&2 35 | 36 | declare -u arg=$1 37 | 38 | # if its numeric, try to get the symbol 39 | case "$arg" in 40 | +([[:digit:]])) numeric_to_symbol "$arg" ;; 41 | +([[:upper:]])) symbol_to_numeric "$arg" ;; 42 | *) printf 'unknown input: %s\n'>&2; exit 1 ;; 43 | esac 44 | 45 | # if successful (awk returns error) print the error message 46 | (( $? )) && perror "$arg" 47 | 48 | -------------------------------------------------------------------------------- /walk: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # emulates the 'tree' utility 5 | # 6 | 7 | shopt -s nullglob 8 | 9 | indent=0 10 | ind_sz=3 11 | dirs_first=0 12 | files_only=0 13 | 14 | printentry() { 15 | printf "%*s%s\n" "$indent" "" "$1" 16 | } 17 | 18 | get_pwd_contents() { 19 | local dir nondir 20 | 21 | for f in *; do 22 | [[ -d $f ]] && dir+=("$f") || nondir+=("$f") 23 | done 24 | 25 | dir_contents=("${dir[@]}" "${nondir[@]}") 26 | } 27 | 28 | walk() { 29 | local dir_contents 30 | 31 | if (( !files_only )); then 32 | printentry "$(printf "\e[0;34m%s\e[0m" "$1")" 33 | (( indent += ind_sz )) 34 | fi 35 | 36 | pushd "$1" &>/dev/null 37 | 38 | (( dirs_first )) && get_pwd_contents || dir_contents=(*) 39 | for entry in "${dir_contents[@]}"; do 40 | if [[ -d $entry && ! -L $entry ]]; then 41 | walk "$entry" 42 | else 43 | printentry "$entry" 44 | fi 45 | done 46 | 47 | (( !files_only )) && (( indent -= ind_sz )) 48 | popd &>/dev/null 49 | } 50 | 51 | while getopts "afFd" opt; do 52 | case $opt in 53 | a) shopt -s dotglob ;; 54 | d) dirs_first=1 ;; 55 | f) files_only=1 ;; 56 | esac 57 | done 58 | shift $(( OPTIND - 1 )) 59 | 60 | for basedir in "${@:-.}"; do 61 | walk "$basedir" 62 | done 63 | 64 | -------------------------------------------------------------------------------- /aurcomm: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # retreive aur comments for a package 5 | # - package name can be specified or pulled from a PKGBUILD in $PWD 6 | # - specify -a to show all comments 7 | # 8 | 9 | AURURL="http://aur.archlinux.org" 10 | ALL='&comments=all' 11 | printf -v CON '\e[1;34m' 12 | printf -v COFF '\e[0m' 13 | 14 | commfilter() { 15 | xmllint --html --xpath \ 16 | '//div[@class="pgbox"]/div[@class="comment-header"]/..' - 2>/dev/null | 17 | sed 's/<[^>]\+>//g;s/\t//g;s/\r$// 18 | /Comment by:/,/[[:alnum:]]\+/{/^$/d}; 19 | s/>/>/g;s/<//dev/null); then 41 | printf "error: package \`%s' not found\n" "$pkgname" 42 | exit 1 43 | fi 44 | 45 | curl -s "$AURURL/packages.php?ID=$pkgID$showall" | commfilter | fmt -sw$(tput cols) 46 | 47 | -------------------------------------------------------------------------------- /csv2html: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | NUMFIELDS=0 4 | OUT=() 5 | TLC=0 6 | 7 | to_html() { 8 | local lc=0 # line counter in case of error 9 | 10 | while IFS=',' read -r -a items; do 11 | (( ++lc )) 12 | OUT+=("\t\n") # start row 13 | 14 | # skip blank lines, but not lines with empty fields 15 | (( ${#items[@]} == 0 )) && continue 16 | 17 | # assert: all rows are same number of columns 18 | (( NUMFIELDS == 0 )) && NUMFIELDS=${#items[@]} 19 | if (( NUMFIELDS != ${#items[@]} )); then 20 | echo "error: Malformed CSV ($file: line $LC). Expected $NUMFIELDS fields, found ${#items[@]}." >&2 21 | exit 1 22 | fi 23 | 24 | # output each column, stripping NULL tokens 25 | for item in "${items[@]}"; do 26 | OUT+=("\t\t${item/==NULL==/}\n") 27 | done 28 | 29 | OUT+=("\t\n") # end row 30 | 31 | (( TLC += lc )) 32 | 33 | # replace blank fields with a dummy value so read doesn't skip them 34 | done < <(sed 's/^,/==NULL==,/; 35 | s/,,/,==NULL==,/g; 36 | s/,$/,==NULL==/' <&6) 37 | } 38 | 39 | OUT+=("\n") # header 40 | 41 | # use a new FD to unify where to read from 42 | if (( $# == 0 )); then 43 | exec 6<&0 44 | to_html 45 | else 46 | for file; do 47 | exec 6< "$file" 48 | to_html 49 | exec 6<&- 50 | done 51 | fi 52 | 53 | (( TLC > 0 )) && OUT+=("
\n") # footer 54 | 55 | -------------------------------------------------------------------------------- /qinit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # launcher script for qemu-kvm 4 | # 5 | # machines are sourced from a file which defines functions, each 6 | # specifying options for the particular VM, for example: 7 | # 8 | # vm_beatbox() { 9 | # mac="de:ad:be:ef:00:09" 10 | # options=(-nographic) 11 | # } 12 | # 13 | # If unspecified: 14 | # ${drives[@]}: will match all of "$imgroot/$vm".*.qcow2 15 | # 16 | 17 | ### Paths ################################ 18 | machines=${XDG_CONFIG_HOME:-$HOME/.config}/qemu/machines 19 | isoroot=/mnt/Gluttony/iso 20 | imgroot=/mnt/Haven/qemu-img 21 | 22 | ### Defaults ############################# 23 | mem=(-m 1024) 24 | cpus=(-cpu Nehalem -smp sockets=1,cores=2,threads=2) 25 | iso=$isoroot/archlinux.iso 26 | ### Launcher ############################# 27 | 28 | . "$machines" 29 | 30 | while getopts 'cd:' flag; do 31 | case $flag in 32 | c) usecdrom=1 ;; 33 | d) iso=$OPTARG ;; 34 | esac 35 | done 36 | shift $(( OPTIND - 1 )) 37 | 38 | if [[ -z $1 ]]; then 39 | printf 'Available VMs:\n' 40 | compgen -A function -- vm_ | sed 's/^vm_/ /' 41 | exit 0 42 | fi 43 | 44 | vm=$1; shift 45 | if ! type -t vm_$vm >/dev/null; then 46 | printf 'unknown VM: %s\n' "$vm" 47 | exit 1 48 | fi 49 | 50 | vm_$vm 51 | 52 | cdrom=(-cdrom "$iso" -boot d) 53 | net=(-net vde -net nic,model=virtio,macaddr="$mac") 54 | 55 | opts+=("${cpus[@]}" "${mem[@]}" "${net[@]}") 56 | 57 | if (( usecdrom )); then 58 | opts+=(-cdrom "$iso" -boot d) 59 | fi 60 | 61 | # default drives 62 | if (( ${#drives[*]} == 0 )); then 63 | for drive in "$imgroot/$vm".*.qcow2; do 64 | opts+=("-drive" "file=$drive,if=virtio,aio=native") 65 | done 66 | else 67 | opts+=("${drives[@]}") 68 | fi 69 | 70 | exec kvm "${opts[@]}" "$@" 71 | 72 | -------------------------------------------------------------------------------- /pp: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # A light wrapper for pacman, implementing parallel downloading 5 | # Requires: aria2 6 | # 7 | 8 | shopt -s extglob 9 | 10 | PACARGS=("$@") 11 | PACCACHE="/var/cache/pacman/pkg" 12 | SYNC=0 BORING_OPT=0 DL_ONLY=0 13 | 14 | # this is the easiest way to catch interesting operations we only want to catch 15 | # things like -S or -Syu. Everything else will be passed straight to pacman 16 | while getopts 'DQRSTUVb:cdefghiklmnopqr:stuvwy' opt; do 17 | case $opt in 18 | S) SYNC=1 ;; 19 | @(c|g|i|q|s)) BORING_OPT=1 ;; 20 | w) DL_ONLY=1 ;; 21 | esac 22 | done 23 | 24 | (( !SYNC || BORING_OPT )) && exec pacman $@ 25 | 26 | (( UID != 0 )) && { printf "Must be root!\n"; exit 1; } >&2 27 | 28 | # find alternate pacman cache location 29 | paccache=$(awk -F'[[:space:]]*=[[:space:]]*' '/^[[:space:]]*CacheDir/ { print $2; exit; }' /etc/pacman.conf) 30 | [[ $paccache ]] && PACCACHE="$paccache" 31 | unset paccache 32 | 33 | # read in urls to packages needed for transaction 34 | IFS=$'\n' read -r -d'\0' -a pkgs < <(pacman -p "${PACARGS[@]}" | grep -E '^(ht|f)tp') 35 | 36 | # exit on null array 37 | [[ -z "${pkgs[@]}" ]] && { printf "Nothing to do!\n"; exit 0; } 38 | 39 | # create a dl manifest, so we don't pass superfluous URLs to aria 40 | manifest=() 41 | for pkg in "${pkgs[@]}"; do 42 | [[ -f $PACCACHE/${pkg##*/} ]] || manifest+=("$pkg") 43 | done 44 | 45 | if [[ ${manifest[@]} ]]; then 46 | printf ":: Packages to be downloaded:\n" 47 | for pkg in "${manifest[@]}"; do 48 | printf " ==> %s\n" "${pkg##*/}" 49 | done 50 | fi 51 | 52 | # filthy. strip out any -y option 53 | ARGS=() 54 | for arg; do 55 | [[ $arg = -*y* ]] && arg=${arg//y/} 56 | ARGS+=("$arg") 57 | done 58 | 59 | aria2c -j 10 --dir "$PACCACHE" -i - < <(printf "%s\n" "${manifest[@]}") 60 | (( DL_ONLY )) || exec pacman "${ARGS[@]}" 61 | 62 | -------------------------------------------------------------------------------- /xo: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # A command line utility that mimics xdg-open 4 | # Requires Bash 4.0+ 5 | # 6 | 7 | # Arg check 8 | (( $# )) || { printf "Usage: %s \n" "${0##*/}" >&2; exit 1; } 9 | 10 | # Declare an associative array to keep track of our file types. 11 | # Index elements can be a full MIME type (e.g. image/png), just 12 | # the major MIME type (e.g. image) or a file extension (png). 13 | declare -A handler 14 | 15 | # To keep things clean, general programs should be declared for 16 | # groups of filetypes resulting in the same program being used 17 | # when a major MIME type won't correctly identify all filetypes. 18 | # openoffice.org documents are an example of this. 19 | doc=soffice 20 | image=feh 21 | video=mplayer 22 | default=${EDITOR:-vi} # Fallback -- should be a text editor 23 | 24 | handler[application/pdf]=zathura 25 | handler[application/vnd.oasis.opendocument.text]=$doc 26 | handler[doc]=$doc 27 | handler[image]=$image 28 | handler[odb]=$doc 29 | handler[odf]=$doc 30 | handler[ods]=$doc 31 | handler[text/rtf]=$doc 32 | handler[video]=$video 33 | handler[application/ogg]=$video 34 | handler[mkv]=$video 35 | handler[xls]=$doc 36 | 37 | # Determine the MIME type via 'file' and assign it to an array 38 | # mimetype[0] = major (e.g. image) 39 | # mimetype[1] = minor (e.g. png) 40 | IFS='/' read -rd ';' -a mimetype < <(file -bi --mime-type "$1") 41 | 42 | # Determine the extension as a fallback method 43 | ext=${1//*.} 44 | 45 | # Try to open by exact MIME type 46 | if [[ -n ${handler[${mimetype[0]}/${mimetype[1]}]} ]]; then 47 | ${handler[${mimetype[0]}/${mimetype[1]}]} "$@" 48 | 49 | # Try to open by major MIME type 50 | elif [[ -n ${handler[${mimetype[0]}]} ]]; then 51 | ${handler[${mimetype[0]}]} "$@" 52 | 53 | # Try to open by extension 54 | elif [[ -n ${handler[$ext]} ]]; then 55 | ${handler[$ext]} "$@" 56 | 57 | # Well, I'm out of ideas. Use the $default. 58 | else 59 | $default "$@" 60 | fi 61 | 62 | -------------------------------------------------------------------------------- /sabs: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # fetch the latest PKGBUILD from Arch's SVN 4 | # 5 | 6 | shopt -s extglob 7 | 8 | absroot=/var/abs 9 | svnrepo="svn://svn.archlinux.org" 10 | splitpkgcache=$HOME/.cache/splitpkg.cache 11 | 12 | build_splitpkg_cache() { 13 | printf '==> building split package cache...\n' >&2 14 | [[ -d $HOME/.cache ]] || mkdir -p "$HOME/.cache" 15 | 16 | . /etc/makepkg.conf 17 | { 18 | echo "declare -A splitpkgdb" 19 | for p in "$absroot"/*/*/PKGBUILD; do 20 | ( 21 | pkgdir=${p%/PKGBUILD} 22 | . "$pkgdir/PKGBUILD" 2>/dev/null 23 | for split in "${pkgname[@]}"; do 24 | printf "splitpkgdb[%s]=%s\n" "$split" "${pkgdir##*/}" 25 | done 26 | ) 27 | done | sort -u 28 | } >"$splitpkgcache" 29 | } 30 | 31 | getrepo() { 32 | for f in "$absroot"/*; do 33 | if [[ -d $f/$1 ]]; then 34 | echo "${f##*/}" 35 | return 0 36 | fi 37 | done 38 | 39 | return 1 40 | } 41 | 42 | if ! type -p svn >/dev/null; then 43 | printf '==> ERROR: subversion does not appear to be installed\n' >&2 44 | exit 1 45 | fi 46 | 47 | if [[ -z $1 ]]; then 48 | printf "Usage: %s [-r|--rebuild] targets...\n" "${0##*/}" 49 | exit 1 50 | fi 51 | 52 | if [[ $1 = -@(r|-rebuild) || ( ! -f $splitpkgcache && -d $absroot ) ]]; then 53 | build_splitpkg_cache 54 | exit 55 | fi 56 | 57 | [[ -s $splitpkgcache ]] && . "$splitpkgcache" 58 | 59 | for pkg; do 60 | # check split package cache first 61 | provider=${splitpkgdb[$pkg]} 62 | if [[ $provider && $pkg != "$provider" ]]; then 63 | printf '==> %s is built by %s\n' "$pkg" "$provider" 64 | pkg=$provider 65 | fi 66 | 67 | if ! repo=$(getrepo "$pkg"); then 68 | printf "error: package \`%s' not found\n" "$pkg" >&2 69 | continue 70 | fi 71 | 72 | case $repo in 73 | @(community|multilib)*) repo=community ;; 74 | *) repo=packages ;; 75 | esac 76 | 77 | if [[ -d $pkg ]]; then 78 | printf "error: directory \`%s' already exists\n" "$pkg" 79 | continue 80 | fi 81 | 82 | svn export "$svnrepo/$repo/$pkg/trunk" $pkg >/dev/null && 83 | printf ":: checked out %s\n" "$pkg" || 84 | printf "error: failed to get package \`%s'\n" "$pkg" 85 | 86 | done 87 | -------------------------------------------------------------------------------- /hglink: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # generate links to hgweb based on a file name and optional commit hash 5 | # optional dependency: curl (for link validation) 6 | # 7 | 8 | LINE= 9 | OPENINBROWSER=0 10 | 11 | get_hgroot() { 12 | curpath=${1:-$PWD} 13 | while [[ $curpath && ! -d $curpath/.hg ]]; do 14 | curpath=${curpath%/*} 15 | done 16 | [[ $curpath ]] && printf "%s\n" "$curpath" 17 | } 18 | 19 | usage() { 20 | printf "Usage: %s [ -l linenum ] [ -b ] [ hash ]" "${0##*/}" 21 | } >&2 22 | 23 | while getopts ":bhl:" opt; do 24 | case $opt in 25 | b) OPENINBROWSER=1 ;; 26 | h) usage; exit 1 ;; 27 | l) LINE=$OPTARG ;; 28 | \:) printf "Argument required for -%s\n" "$OPTARG" ;; 29 | \?) printf "error: bad arg, no bisuit\n" ;; 30 | esac >&2 31 | done 32 | shift $(( OPTIND - 1 )) 33 | 34 | (( $# )) || { usage; exit 1; } 35 | 36 | fullpath=$(readlink -m "$1") 37 | hg_root=$(get_hgroot "$fullpath") 38 | [[ -z $hg_root ]] && { printf "error: '%s' is not in an hg repo\n" "$1"; exit 1; } 39 | 40 | # we need to operate from within the hg repo. if we're not in it, go there 41 | [[ ${PWD#$hg_root} = $PWD ]] && cd $hg_root 42 | 43 | # where do we push to by default? 44 | remotepath=$(hg paths | awk -F' *= *' '/default/{ print $2 }') 45 | 46 | # commit hash to use -- hg provides no direct way (that i know of) to read the 47 | # remote's tip, so this is a filthy rotten hack in place of it 48 | remotetip=$(hg summ --remote | awk '/parent:/ { sub(/:.*/, "", $2); tip=$2 } 49 | /remote:/ { outgoing=$2 } 50 | END { print tip - outgoing }') 51 | rev=${2:-$(hg log -r $remotetip | awk -F: 'NR == 1 { print $3 }')} 52 | 53 | # relative path from repo root to file 54 | relpath=${fullpath#$hg_root/} 55 | if [[ -z $relpath ]]; then 56 | printf "error: failed to determine path to %s. tell dave he sucks at bash\n" "$1" 57 | exit 1 58 | fi 59 | 60 | # the build URL, appending a line anchor if provided 61 | urlout="${remotepath}/file/$rev/$relpath${LINE:+#l$LINE}" 62 | 63 | # if curl is installed... 64 | if type -P curl >/dev/null; then 65 | # hgweb kindly responds with a 404 on an invalid link -- so let's validate! 66 | if ! curl -sf "$urlout" >/dev/null; then 67 | printf "error: failed to create a valid link to '%s'. check your repo and filepath\n" "$1" 68 | exit 1 69 | fi 70 | fi 71 | 72 | # print or open in browser 73 | (( OPENINBROWSER )) && ${BROWSER:-chromium} "$urlout" || printf "%s\n" "$urlout" 74 | 75 | -------------------------------------------------------------------------------- /alsavol: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ -d /run/user/$USER ]]; then 4 | volfile=/run/user/$USER/volume 5 | else 6 | volfile=$HOME/.volume 7 | fi 8 | 9 | getvol() { 10 | local channel=$1 11 | 12 | (( $# == 1 )) || return 1 13 | amixer get "$channel" | sed -n '$s|.*\[\([[:digit:]]\+\)%\].*|\1|p' 14 | } 15 | 16 | setvol() { 17 | local channel=$1 level=$2 18 | 19 | (( $# == 2 )) || return 1 20 | amixer -q set "$channel" "$level%" 21 | } 22 | 23 | is_muted() { 24 | [[ -f $volfile ]] 25 | } 26 | 27 | mute() { 28 | local channel=$1 curvol= 29 | 30 | [[ $channel ]] || return 1 31 | curvol="$(getvol "$channel")" 32 | printf '%s\n' "$curvol" > "$volfile" 33 | setvol "$channel" 0 34 | 35 | printf '%s' 0 36 | } 37 | 38 | unmute() { 39 | local channel=$1 newvol= 40 | 41 | read -r newvol < "$volfile" 42 | setvol "$channel" "$newvol" 43 | rm -f "$volfile" 44 | 45 | printf '%s' "$newvol" 46 | } 47 | 48 | get_icon () { 49 | local icon_name display_volume=$1 50 | 51 | if (( display_volume > 67 )); then 52 | icon_name="high" 53 | elif (( display_volume > 33 )); then 54 | icon_name="medium" 55 | elif (( display_volume > 0 )); then 56 | icon_name="low" 57 | else 58 | icon_name="muted" 59 | fi 60 | 61 | printf "notification-audio-volume-%s" "$icon_name" 62 | } 63 | 64 | toggle() { 65 | local channel=$1 savedvol= newvol= 66 | 67 | [[ $channel ]] || return 1 68 | if is_muted; then 69 | unmute "$channel" 70 | else 71 | mute "$channel" 72 | fi 73 | } 74 | 75 | increase() { 76 | local channel=$1 increment=$2 curvol= newvol= 77 | 78 | (( $# == 2 )) || return 1 79 | 80 | is_muted && (( increment += $(unmute) )) 81 | 82 | curvol=$(getvol "$channel") 83 | newvol=$(( curvol + increment )) 84 | (( newvol > 100 )) && newvol=100 85 | 86 | setvol "$channel" "$(( newvol ))" 87 | echo "$newvol" 88 | } 89 | 90 | decrease() { 91 | local channel=$1 increment=$2 curvol= newvol= 92 | 93 | (( $# == 2 )) || return 1 94 | curvol=$(getvol "$channel") 95 | newvol=$(( curvol - increment )) 96 | (( newvol < 0 )) && newvol=0 97 | 98 | setvol "$channel" "$newvol" 99 | echo "$newvol" 100 | } 101 | 102 | action=$1 channel=$2 delta=$3 103 | type -t $action >/dev/null || exit 1 104 | 105 | disp_volume=$("$action" "$channel" "$delta") 106 | notify-send " " \ 107 | -i $(get_icon $disp_volume) \ 108 | -t 1000 \ 109 | -h int:value:$disp_volume \ 110 | -h string:synchronous:volume 111 | 112 | -------------------------------------------------------------------------------- /sogrep: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os, sys, argparse 4 | from subprocess import PIPE, Popen 5 | 6 | CACHEFILE = os.path.join(os.getenv('HOME'), '.cache/solinks') 7 | 8 | SEARCHPATHS = [ 9 | '/lib/', 10 | '/lib32/', 11 | '/usr/lib/', 12 | '/usr/lib32/', 13 | '/usr/local/lib/', 14 | '/usr/local/lib32/', 15 | '/bin/', 16 | '/sbin/', 17 | '/usr/bin/', 18 | '/usr/sbin/' 19 | '/opt/', 20 | ] 21 | 22 | def stdout_of(cmd): 23 | return Popen(cmd, 24 | stdout=PIPE, 25 | stderr=PIPE).communicate()[0].decode('utf-8') 26 | 27 | def has_prefix(i): 28 | for path in SEARCHPATHS: 29 | if i.startswith(path): 30 | return True 31 | 32 | return False 33 | 34 | def write_solinks(cachefile, pkg, files): 35 | out = stdout_of(['objdump', '-p'] + files) 36 | 37 | links = [] 38 | 39 | for line in out.split('\n'): 40 | try: 41 | section, solink = line.strip().split(' ', 1) 42 | except: 43 | continue 44 | 45 | if section == "NEEDED": 46 | links.append(solink.strip()) 47 | 48 | for link in sorted(set(links)): 49 | print("{}\t{}".format(pkg, link), file=cachefile) 50 | 51 | def sogrep(search): 52 | with open(CACHEFILE) as solinks: 53 | for line in solinks: 54 | pkgname, soname = line.rstrip().split('\t') 55 | if soname.startswith(search): 56 | print(pkgname) 57 | 58 | def createlinks(): 59 | pkgs = {} 60 | filelist = stdout_of(['pacman', '-Ql']) 61 | 62 | for f in filelist.split('\n'): 63 | if not f: 64 | break 65 | pkg, fname = f.split(' ', 1) 66 | 67 | if fname.endswith('/') or not has_prefix(fname) or os.path.islink(fname): 68 | continue 69 | 70 | if not pkgs.get(pkg): 71 | pkgs[pkg] = [] 72 | pkgs[pkg].append(fname) 73 | 74 | with open(CACHEFILE, "w") as cachefile: 75 | for k, v in sorted(pkgs.items()): 76 | write_solinks(cachefile, k, v) 77 | 78 | def main(): 79 | parser = argparse.ArgumentParser(description='find soname revdeps') 80 | 81 | parser.add_argument('-c', '--create', action='store_true', dest='create', default=False, 82 | help='rebuild soname cache') 83 | 84 | parser.add_argument('soname', nargs='?', help='soname to search') 85 | 86 | opts = parser.parse_args() 87 | 88 | if opts.create: 89 | createlinks() 90 | else: 91 | if not opts.soname: 92 | print("error: no soname specified", file=sys.stderr) 93 | sys.exit(1) 94 | sogrep(opts.soname) 95 | 96 | if __name__ == '__main__': 97 | main() 98 | 99 | -------------------------------------------------------------------------------- /git-all: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Usage: git-all [-Sfhv] [command...]" 3 | # 4 | # -S Show repos which have uncommitted changes. If this" 5 | # option is specified, all other options are ignored." 6 | # 7 | # -v print error output in job summary for failed jobs" 8 | # 9 | # Actions will be performed on $REPOHOME, which defaults to $HOME" 10 | # if not specified." 11 | # 12 | 13 | REPOHOME=${REPOHOME:-$HOME} 14 | VERBOSE=0 15 | 16 | count=0 17 | declare -a fail 18 | declare -a pass 19 | declare -A output 20 | 21 | die() { 22 | local mesg=$1; shift 23 | printf "\033[1;31m::\033[0m ${mesg}\n" "$@" >&2 24 | } 25 | 26 | msg() { 27 | local mesg=$1; shift 28 | printf " \033[1;32m==>\033[1;0m\033[1;1m ${mesg}\033[1;0m\n" "$@" >&2 29 | } 30 | 31 | msg2() { 32 | local mesg=$1; shift 33 | printf " \033[1;34m ->\033[1;0m\033[1;1m ${mesg}\033[1;0m\n" "$@" >&2 34 | } 35 | 36 | repopass() { 37 | printf " \033[1;0m\033[0;34m[\033[1;37mPASS\033[0;34m] \033[0;36m %s\033[0m\n" "$1" 38 | } 39 | 40 | repofail() { 41 | printf " \033[1;0m\033[0;34m[\033[1;31mFAIL\033[0;34m] \033[0;36m %s\033[0m\n" "$1" >&2 42 | } 43 | 44 | breadlink() { 45 | local path="$1"; 46 | 47 | if [[ -d $path ]]; then 48 | ( 49 | cd "$path" 50 | pwd -P 51 | ) 52 | else 53 | printf "%s\n" "$path" 54 | fi 55 | } 56 | do_all_action() { 57 | IFS=$'\n' read -r -d $'\0' -a repos < <(find "$REPOHOME" -type d -name '.git' 2>/dev/null) 58 | 59 | for repo in "${repos[@]}"; do 60 | (( ++count )) 61 | local repo=$(breadlink ${repo%.git}) 62 | 63 | cd "$repo" 64 | output[$repo]=$(git "$@" 2>&1) && pass=(${pass[@]} "$repo") || fail=(${fail[@]} "$repo") 65 | done 66 | } 67 | 68 | stat_repos() { 69 | IFS=$'\n' read -r -d $'\0' -a repos < <(find "$REPOHOME" -type d -name '.git' 2>/dev/null) 70 | 71 | for repo in "${repos[@]}"; do 72 | local repo=$(breadlink ${repo%.git}) 73 | 74 | cd "$repo" 75 | [[ -n $(git status -s | grep -v "^??") ]] && printf "%s\n" "$repo" 76 | done 77 | } 78 | 79 | job_summary() { 80 | printf "\n" 81 | msg "Job Summary For $count Repos: git $*" 82 | 83 | if [[ ${#fail[@]} -eq 0 ]]; then 84 | msg2 "No errors were reported" 85 | else 86 | for repo in "${fail[@]}"; do 87 | repofail "$repo" 88 | (( VERBOSE )) && { sed 's/^/ /' <<< "${output[$repo]}"; printf "\n"; } >&2 89 | done 90 | fi 91 | printf "\n" 92 | 93 | for repo in "${pass[@]}"; do 94 | repopass "$repo" 95 | done 96 | printf "\n" 97 | } 98 | 99 | # sanity check 100 | [[ ! -r "$REPOHOME" ]] && die "Invalid \$REPOHOME: $REPOHOME" exit 1 101 | 102 | #while getopts :Sfv flag; do 103 | while getopts :Sv flag; do 104 | case $flag in 105 | S) stat_repos; exit 0 ;; 106 | v) VERBOSE=1 ;; 107 | \?) die "invalid option -- '$OPTARG'" ;; 108 | esac >&2 109 | done 110 | 111 | shift $(( OPTIND-1 )) 112 | 113 | # check command line usage 114 | [[ $1 ]] || { sed -n '2,/^$/s/^# \?//p' "$0"; exit 1; } 115 | 116 | # main loop 117 | do_all_action "$@" 118 | job_summary "$@" 119 | 120 | -------------------------------------------------------------------------------- /latest: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse, os, glob, sys, operator, fnmatch 4 | 5 | def pathwalk(path, filter, order, recurse=False): 6 | """walk along a path and stat the matching files""" 7 | entries = dict() 8 | 9 | for root, dirs, files in os.walk(path): 10 | for f in files: 11 | if filter and not fnmatch.fnmatch(f, filter): 12 | continue 13 | 14 | fullpath = os.path.join(root, f) 15 | entries[fullpath] = os.lstat(fullpath).st_mtime 16 | 17 | if not recurse: break 18 | 19 | return sorted(entries.items(), key=operator.itemgetter(1), reverse=order) 20 | 21 | def print_each(*args): 22 | """print a filename plus the delimiter""" 23 | sys.stdout.write("%s%c" % args) 24 | return 0 25 | 26 | def unlink_each(*args): 27 | """attempt to unlink a file""" 28 | try: 29 | os.unlink(args[0]) 30 | return 0 31 | except OSError(errno, strerror): 32 | sys.stderr.write("%s: cannot unlink `%s': %s\n" % (sys.argv[0], args[0], strerror)) 33 | return 1 34 | 35 | def main(): 36 | parser = argparse.ArgumentParser(description='find the latest files in a directory', 37 | epilog='path defaults to the current directory if unspecified') 38 | 39 | parser.add_argument('-0', '--null', action='store_const', dest='delim', default='\n', 40 | const='\0', help='null delimit output') 41 | 42 | parser.add_argument('-r', '--recurse', action='store_true', dest='recurse', default=False, 43 | help='recurse into directories') 44 | 45 | parser.add_argument('-i', '--inverse', action='store_true', dest='inverse', default=False, 46 | help='invert selection (all except latest count)') 47 | 48 | parser.add_argument('-d', '--delete', action='store_const', dest='action', default=print_each, 49 | const=unlink_each, help='delete each candidate instead') 50 | 51 | parser.add_argument('-n', '--count', action='store', dest='count', default=1, type=int, 52 | metavar='N', help='number of files to select') 53 | 54 | parser.add_argument('-o', '--oldest', action='store_true', dest='reverse', default=False, 55 | help='examine oldest files') 56 | 57 | parser.add_argument('-f', '--filter', action='store', dest='filter', default=None, 58 | metavar='PAT', help='filter files on glob pattern') 59 | 60 | parser.add_argument('path', default='.', nargs='?', help='path to search') 61 | 62 | opts = parser.parse_args() 63 | 64 | if not os.path.exists(opts.path): 65 | sys.stderr.write("error: path not found: %s\n" % opts.path) 66 | sys.exit(os.EX_OSFILE) 67 | 68 | files = pathwalk(opts.path, opts.filter, opts.reverse, opts.recurse) 69 | if not files: 70 | sys.exit(os.EX_DATAERR) 71 | 72 | if opts.inverse: 73 | candidates = files[0:len(files) - opts.count] 74 | else: 75 | candidates = files[-(opts.count):] 76 | 77 | error = 0 78 | for cand in candidates: 79 | error += opts.action(cand[0], opts.delim) 80 | 81 | sys.exit(bool(error)) 82 | 83 | main() 84 | -------------------------------------------------------------------------------- /git-retrotag: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # written for alex to retroactively tag his repos based on a VERSION file which 5 | # was conveniently bumped for each of his "releases". 6 | # 7 | 8 | declare -a commits 9 | declare -a existing_tags 10 | 11 | die() { 12 | local mesg=$1; shift 13 | printf "\033[1;31m::\033[0m ${mesg}\n" "$@" >&2 14 | } 15 | 16 | warn() { 17 | local mesg=$1; shift 18 | printf "\033[1;33m::\033[0m ${mesg}\n" "$@" >&2 19 | } 20 | 21 | info() { 22 | local mesg=$1; shift 23 | printf " \033[1;34m::\033[1;0m\033[1;1m ${mesg}\033[1;0m\n" "$@" 24 | } 25 | 26 | msg() { 27 | local mesg=$1; shift 28 | printf " \033[1;32m==>\033[1;0m\033[1;1m ${mesg}\033[1;0m\n" "$@" 29 | } 30 | 31 | in_array() { 32 | local item needle=$1; shift 33 | [[ -z "$1" ]] && return 1 # Not Found 34 | for item; do 35 | [[ $item = "$needle" ]] && return 0 # Found 36 | done 37 | return 1 # Not Found 38 | } 39 | 40 | check_repo_sanity() { 41 | if ! git rev-parse --quiet --verify HEAD &>/dev/null; then 42 | die "error: Not in a git repo" 43 | exit 1 44 | fi 45 | if ! git diff-index --quiet HEAD; then 46 | die "error: repo is dirty. Please commit or stash your changes before running this script." 47 | exit 1 48 | fi 49 | } 50 | 51 | find_taggable_commits() { 52 | local commit action file 53 | 54 | while read -r line; do 55 | [[ $line =~ ^$ ]] && continue 56 | if [[ ! $line =~ ^: ]]; then 57 | # current commit being examined 58 | read commit <<< $line 59 | continue 60 | fi 61 | 62 | # parse file change information 63 | read -r _ _ _ _ action file <<< $line 64 | if [[ $action =~ [AM] && $file == "VERSION" ]]; then 65 | commits+=("$commit") 66 | fi 67 | done < <(git whatchanged --format="%H %ai") 68 | 69 | if (( ${#commits[@]} == 0 )); then 70 | die "error: No candidate commits were found for tagging" >&2 71 | exit 1 72 | fi 73 | } 74 | 75 | get_existing_tags() { 76 | IFS=$'\n' read -r -d'\0' -a existing_tags < <(git tag) 77 | } 78 | 79 | review_candidates() { 80 | msg "Found ${#commits[@]} commits worthy of tagging. Press enter to review the commits." 81 | read 82 | for commit in "${commits[@]}"; do 83 | read hash date <<< $commit 84 | git show --stat $hash 85 | done 86 | } 87 | 88 | ask_abort() { 89 | read -N1 -p "Satisfied? Continue with tagging? (y/N) " reply 90 | printf "\n" 91 | [[ ! "$reply" =~ [Yy] ]] && { printf "Aborting...\n"; exit 1; } 92 | } 93 | 94 | tag_commits() { 95 | local count=0 96 | 97 | for commit in "${commits[@]}"; do 98 | read hash date <<< $commit 99 | [[ -z $hash || -z $date ]] && { die "Fatal error! Something is horribly wrong!"; exit 42; } 100 | 101 | # make a new branch, rewound to the commit 102 | git checkout $hash -b $hash &>/dev/null 103 | 104 | read version < VERSION 105 | 106 | # if the tag exists, skip tagging 107 | if in_array $version "${existing_tags[@]}"; then 108 | warn "Tag \"$version\" already exists. Skipping..." 109 | else 110 | GIT_COMMITTER_DATE="$date" git tag -a "$version" -m "tag as $version" &>/dev/null 111 | info "${hash:0:8} => $version" 112 | (( ++count )) 113 | fi 114 | 115 | # reset for next iteration 116 | { git checkout master; git branch -d $hash; } &>/dev/null 117 | unset hash date 118 | done 119 | 120 | if (( count > 0 )); then 121 | printf "\n" 122 | msg "Finished: Added $count new tags. Use \`git push --tags\` to update the remote." 123 | else 124 | printf "\n" 125 | msg "Finished: No new tags were added." 126 | fi 127 | } 128 | 129 | # main loop 130 | check_repo_sanity 131 | find_taggable_commits 132 | get_existing_tags 133 | review_candidates 134 | ask_abort 135 | tag_commits 136 | 137 | -------------------------------------------------------------------------------- /flac2mp3: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # flac2mp3 - a mass conversion utility 5 | # requires: `flac' and `lame' 6 | # 7 | 8 | ARG0=${0##*/} 9 | TAGVARS=(album artist date genre title tracknumber) 10 | 11 | # runtime options 12 | FORCE=0 13 | LAME_OPTS="-h" 14 | FLAC_OPTS="" 15 | PRESERVE=0 16 | RECURSE=0 17 | VERBOSE=1 18 | QUITONWARN=0 19 | 20 | warn() { 21 | local mesg=$1; shift 22 | printf '\e[1;33m:: \e[0m%s\n' "$mesg" 23 | (( QUITONWARN )) && exit 1 24 | } >&2 25 | 26 | die() { 27 | local mesg=$1; shift 28 | printf '\e[1;31m:: \e[0m%s\n' "$mesg" 29 | exit 1 30 | } >&2 31 | 32 | mkdir_vp() { 33 | mkdir -p "$1" && printf "%s: created directory \`%s'" "$ARG0" "$!" 34 | } 35 | 36 | usage() { 37 | printf "usage: $ARG0 [options] SOURCE... DESTINATION\n" 38 | printf " $ARG0 [options] -t DESTINATION SOURCE...\n\n" 39 | printf " -f overwrite existing files\n" 40 | printf " -h display this help message\n" 41 | printf " -l ARGS additional parameters to pass to lame\n" 42 | printf " -q don't print to stdout\n" 43 | printf " -r convert directories recursively (implies -p)\n" 44 | printf " -t PATH use PATH as destination for converted SOURCEs\n" 45 | printf " -w quit on warnings\n\n" 46 | } >&2 47 | 48 | # find flac files in a given dir, recursing unless told otherwise 49 | expand_dir() { 50 | for arg in "$@"; do 51 | find "$arg" -type f -name '*.flac' 52 | done 53 | } 54 | 55 | # convert with tags (only setting tags if they exist) 56 | convert() { 57 | local input output artist title album date tracknumber genre 58 | input=$1 59 | output=$2 60 | 61 | eval $(metaflac --export-tags-to - "$1" | awk -F'=' '!/[ ].*=/{ printf "%s=\"%s\"\n", tolower($1), $2 }') 62 | 63 | (( VERBOSE )) || { LAME_OPTS+=" --quiet"; FLAC_OPTS+=" --silent"; } 64 | 65 | flac -cd $FLAC_OPTS "$input" | lame --add-id3v2 $LAME_OPTS \ 66 | ${artist:+--ta "$artist"} \ 67 | ${title:+--tt "$title"} \ 68 | ${tracknumber:+--tn "$tracknumber"} \ 69 | ${genre:+--tg "$genre"} \ 70 | ${comment:+--tc "$comment"} \ 71 | ${album:+--tl "$album"} \ 72 | - "${output}" 73 | } 74 | 75 | while getopts ":fhl:qrt:w" opt; do 76 | case $opt in 77 | f) FORCE=1 ;; 78 | h) usage; exit 1 ;; 79 | l) LAME_OPTS+=" $OPTARG" ;; 80 | q) VERBOSE=0 ;; 81 | r) RECURSE=1 PRESERVE=1 ;; 82 | t) DEST=$OPTARG ;; 83 | w) QUITONWARN=1 ;; 84 | \?) die "$ARG0: invalid option -- '$OPTARG'" ;; 85 | \:) die "$ARG0: option '$OPTARG' requires an argument" ;; 86 | esac 87 | done 88 | shift $(( OPTIND - 1 )) 89 | 90 | # basic arg check 91 | (( $# )) || { usage; exit; } 92 | 93 | # deal with args in an array rather than as positional params. this will make 94 | # life easier if we need to strip the last param for the destination. 95 | args=("$@") 96 | 97 | # if -t wasn't given, strip the last arg for the destination 98 | if [[ -z $DEST ]]; then 99 | DEST=${!#} 100 | args=("${@:1:(( $# - 1 ))}") 101 | fi 102 | 103 | # all but the last part of the dest needs to exist 104 | fulldest=$(readlink -f "$DEST") 105 | if [[ -z $fulldest ]]; then 106 | die "error: cannot create directory \`$DEST': No such file or directory" 107 | fi 108 | 109 | # main loop 110 | for arg in "${args[@]}"; do 111 | if [[ -d $arg ]]; then 112 | # only handle directories if -r was passed 113 | (( ! RECURSE )) && { warn "warning: omitting directory \`$arg'"; continue; } 114 | IFS=$'\n' read -d'\0' -r -a flacs < <(expand_dir "$arg") 115 | else 116 | flacs=$arg 117 | fi 118 | 119 | for flac in "${flacs[@]}"; do 120 | if [[ ! $(file -bi --mime-type "$flac") = audio/x-flac ]]; then 121 | warn "warning: '$flac' is not a valid flac file" 122 | fi 123 | 124 | if (( PRESERVE )); then 125 | # use basename(1) instead of a bash PE so we don't need to fight with the 126 | # possibility of a superfluous trailing slash 127 | outfile=$fulldest/$(basename "$arg")/${flac#$arg} 128 | if [[ ! -d ${outfile%/*} ]]; then 129 | mkdir_vp "${outfile%/*}" || die "error: failed to create directory \`${outfile%/*}'" 130 | fi 131 | else 132 | outfile=$DEST/${flac##*/} 133 | fi 134 | 135 | # a proper extension please... 136 | outfile=${outfile%.flac}.mp3 137 | 138 | # does the outfile exist? 139 | if [[ -f $outfile ]] && (( ! FORCE )); then 140 | warn "warning: file \`$outfile' already exists -- skipping" 141 | continue 142 | fi 143 | 144 | # finally, do the conversion 145 | convert "$flac" "$outfile" || warn "warning: failed to convert to mp3" 146 | done 147 | done 148 | 149 | -------------------------------------------------------------------------------- /kload: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # kload - a syslinux.cfg parser for autoloading a kernel for kexec 4 | # 5 | 6 | bootloadercfg=/boot/syslinux/syslinux.cfg 7 | 8 | error() { 9 | local mesg=$1; shift 10 | printf "==> ERROR: $mesg\n" "$@" 11 | } >&2 12 | 13 | die() { 14 | error "$@" 15 | exit 1 16 | } 17 | 18 | usage() { 19 | cat < [rootoptions] 21 | 22 | ${0##*/} parses syslinux's bootloader configuration and automates the process 23 | of loading a new kernel for kexec. If specified, any root options will override 24 | the APPEND line of the config for a given kernel. 25 | 26 | Options: 27 | -a add additional parameters to new kernel's cmdline 28 | -f use another config file (default: /boot/syslinux/syslinux.cfg) 29 | -h display this help message. 30 | -l display available kernels by label 31 | 32 | EOF 33 | exit 1 34 | } 35 | 36 | parse_config() { 37 | awk -v action="$1" -v arg="$2" ' 38 | function longest(list, listlen,i, len, maxlen) { 39 | maxlen = 0 40 | listlen = length(list) 41 | for(i in list) { 42 | len = length(i) 43 | if (len > maxlen) { 44 | maxlen = len 45 | } 46 | } 47 | return maxlen 48 | } 49 | 50 | function add_label(config, label, kernel, append, initrd) { 51 | if (label && kernel && append) { 52 | config[label] = kernel SUBSEP append SUBSEP initrd 53 | } 54 | } 55 | 56 | function print_human(config, opts, len, label) { 57 | if (length(config) == 0) { 58 | print "==> ERROR: No kernel entries found in", FILENAME > "/dev/stderr" 59 | return 60 | } 61 | 62 | len = longest(config) 63 | 64 | printf "%-*s\t%s\n", len, "label", "kernel image [initrd]" 65 | printf "%-*s\t%s\n", len, "", "options" 66 | print "-------------------------------------------------------------" 67 | for (label in config) { 68 | if (config[label]) { 69 | # opts[label] = [initrd] 70 | split(config[label], opts, SUBSEP) 71 | printf "%-*s\t%s", len, label, opts[1] 72 | if(opts[3]) { 73 | printf " [%s]", opts[3] 74 | } 75 | printf "\n%*s\t%s\n", len, "", opts[2] 76 | } 77 | } 78 | } 79 | 80 | function print_nullsep(config, label, opts) { 81 | if (config[label]) { 82 | split(config[label], opts, SUBSEP) 83 | printf "%s\0%s\0%s\0", opts[1], opts[2], opts[3] 84 | } 85 | } 86 | 87 | $1 == "LABEL" { 88 | add_label(config, label, kernel, append, initrd) 89 | label = $2 90 | kernel = append = initrd = "" 91 | } 92 | 93 | label && $1 ~ /^(LINUX|KERNEL)$/ { 94 | sub(/^[[:blank:]]*(LINUX|KERNEL) /, "") 95 | kernel = $0 96 | } 97 | 98 | label && $1 == "APPEND" { 99 | sub(/^[[:blank:]]*APPEND /, "") 100 | gsub(/[[:blank:]]+/, " ") 101 | append = append ? append " " $0 : $0 102 | } 103 | 104 | label && $1 == "INITRD" { 105 | sub(/^[[:blank:]]*[^[:blank:]]+ /, "") 106 | initrd = $0 107 | } 108 | 109 | END { 110 | # if theres no line after the last section, we wont add the label 111 | add_label(config, label, kernel, append, initrd) 112 | 113 | if (action == "list") { 114 | print_human(config) 115 | } else if (action == "parse_one") { 116 | print_nullsep(config, arg) 117 | } 118 | } 119 | ' "$bootloadercfg" 120 | exit 0 121 | } 122 | 123 | runcmd() { 124 | printf 'executing: %s\n' "$*" 125 | if (( needsroot )); then 126 | if sudo -v &>/dev/null && sudo -l &>/dev/null; then 127 | sudo "$@" 128 | else 129 | printf '%s ' 'root' 130 | su -c "$(printf '%q ' "$@")" 131 | fi 132 | else 133 | "$@" 134 | fi 135 | } 136 | 137 | # main() 138 | while getopts ":a:f:hl" flag; do 139 | case $flag in 140 | a) optappend=$OPTARG ;; 141 | f) bootloadercfg=$OPTARG ;; 142 | h) usage ;; 143 | l) list=1 ;; 144 | :) die "option '-%s' requires an argument" "$OPTARG" ;; 145 | ?) die "invalid option -- '%s'" "$OPTARG" ;; 146 | esac 147 | done 148 | shift $(( OPTIND - 1 )) 149 | 150 | [[ -f $bootloadercfg ]] || die "unable to find bootloader config: %s" "$bootloadercfg" 151 | 152 | (( list )) && { parse_config 'list'; exit 0; } 153 | 154 | label=$1; shift 155 | [[ $label ]] || die "no label specified (use -h for help)" 156 | 157 | (( UID == 0 )) || needsroot=1 158 | 159 | { 160 | IFS= read -rd '' kernel 161 | IFS= read -rd '' append 162 | IFS= read -rd '' initrd 163 | } < <(parse_config 'parse_one' "$label") 164 | [[ -z $kernel || -z $append ]] && die "failed to find label \`%s'" "$label" 165 | 166 | kexec_cmd=(kexec -l "/boot/${kernel##*/}" --append="${*:-$append} $optappend") 167 | [[ $initrd ]] && kexec_cmd+=(--initrd="/boot/${initrd##*/}") 168 | 169 | runcmd "${kexec_cmd[@]}" 170 | 171 | -------------------------------------------------------------------------------- /boxes: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | DGRAY="\e[0;30m" 3 | PINK="\e[0;31m" 4 | TEAL="\e[01;32m" 5 | WHITE="\e[0;37m" 6 | GREEN="\e[1;32m" 7 | PISS="\e[0;33m" 8 | GRAY="\e[0;37m" 9 | YELLOW="\e[0;33m" 10 | COL5="\e[0;32m"; 11 | COLOFF="\x1b[0;37;00m"; 12 | RESET="\e[0m" 13 | 14 | ask_yesno() { 15 | read -p "$1 [N/y] " yesno 16 | [[ ${yesno:0:1} == [Yy] ]] 17 | return # implicitly returns value of above compare 18 | } 19 | 20 | SERVERS="$HOME/.servers.list" 21 | if [[ ! -f "$SERVERS" ]]; then 22 | if ask_yesno "No server list found, would you like to create one now?"; then 23 | touch $SERVERS || { echo "Unable to write to '$SERVERS'"; exit 1; } 24 | else 25 | echo "Canceling..." 26 | exit 1 27 | fi 28 | fi 29 | 30 | # sweep server list 31 | sed -i '/^[\t\ ]*$/d' "$SERVERS" 32 | 33 | NUM_SERVERS=$(wc -l < "$SERVERS") 34 | 35 | # utility dispatcher 36 | iterate_and() { 37 | [[ $(type -t $1) = function ]] || return 1 38 | while IFS='|' read user host port group nl; do 39 | $1 "$user" "$host" "$port" "${group/#\$\$/}" "$nl" # nl might not exist, so its last 40 | done < "${2:-$SERVERS}" 41 | } 42 | 43 | export_to_ssh() { 44 | local user=$1 host=$2 port=$3 45 | printf "Host %s\n\tUser %s\n\tPort %s\n\n" "$host" "$user" "$port" 46 | } 47 | 48 | list() { 49 | local user=$1 host=$2 port=$3 group=$4 nl=$5 50 | printf "${nl:+%-8s}${YELLOW}%-15s${COLOFF} ${WHITE}%-35s${COLOFF} ${TEAL}%6s${COLOFF} %-12s\n" $nl "$user" "$host" "$port" "$group" 51 | } 52 | 53 | keyup() { 54 | local user=$1 host=$2 port=$3 55 | [[ -z $user$host$port ]] && return 56 | echo -ne "Sending key to ${YELLOW}$host...${COLOFF} " 57 | ssh-copy-id "-p $port $user@$host" &>/dev/null && 58 | echo -e "${GREEN}Success${COLOFF}!" || 59 | echo -e "${PINK}Error${COLOFF}!" 60 | } 61 | 62 | action=$1; shift 63 | case $action in 64 | connect) 65 | [[ -z $1 ]] 66 | (( $1 > NUM_SERVERS || $1 <= 0 )) && { echo "Invalid server entry." >&2; exit 1; } 67 | IFS='|' read user host port < <(sed -n "${1}{p;q;}" "$SERVERS") 68 | ssh -t -p $port $user@$host 69 | ;; 70 | 71 | add) 72 | (( $# < 2 || $# > 4 )) && { echo "Usage: ${0##*/} add [port] [group]" >&2; exit 1; } 73 | echo "$1|$2|${3:-22}|${4:-\$\$}" >> "$SERVERS" 74 | ;; 75 | 76 | keyup) 77 | if [[ $1 ]]; then 78 | if [[ -z ${1//[[:digit:]]} ]]; then # strictly numerical 79 | (( $1 > NUM_SERVERS || $1 <= 0 )) && { echo "Invalid server entry." >&2; exit 1; } 80 | keyup $(sed "${1}s/|/ /g" "$SERVERS") 81 | else # must be a group 82 | iterate_and keyup < <(sed -n "/|$1\$/p" "$SERVERS") 83 | fi 84 | else 85 | iterate_and keyup 86 | fi 87 | ;; 88 | 89 | edit) 90 | [[ $2 =~ user|host|port|group ]] || (( $# == 2 )) || { echo "Usage: ${0##*/} edit " >&2; exit 1; } 91 | (( $1 > NUM_SERVERS || $1 <= 0 )) && { echo "Invalid server entry." >&2; exit 1; } 92 | 93 | echo "Altering entry: $(sed -n "${1}{p;q;}" "$SERVERS")" 94 | read -p "Enter new $2: " entry 95 | [[ -z $entry ]] && { echo "New entry is empty. Aborting..." >&2; exit 1; } 96 | case $2 in 97 | user) sed -i "$1s,^\([^|]\+\)|,$entry|," "$SERVERS" ;; 98 | host) sed -i "$1s,|\([-[:alnum:]\.\_]\+\)|,|$entry|," "$SERVERS" ;; 99 | port) sed -i "$1s,|[[:digit:]]\+|,|$entry|," "$SERVERS" ;; 100 | group) sed -i "$1s,|[^|]\+\$,|$entry," "$SERVERS" ;; 101 | esac 102 | ;; 103 | 104 | remove) 105 | (( $1 > NUM_SERVERS || $1 <= 0 )) && { echo "Usage: ${0##*/} remove " >&2; exit 1; } 106 | echo "Deleting entry: $(sed -n "${1}{p;q;}" "$SERVERS")" 107 | prompt='\e[0;31m Are you sure? \e[0;37m' 108 | if ask_yesno "$prompt"; then 109 | sed -i "${1}d" "$SERVERS" 110 | echo -e "\e[0;31m Server removed from database.\e[0;37m" 111 | else 112 | echo "Canceling.." 113 | exit 1 114 | fi 115 | ;; 116 | 117 | list) 118 | echo -e "\n ${PINK}######## ####### ## ## ######## ###### \n" \ 119 | " ## ## ## ## ## ## ## ## ##\n" \ 120 | " ## ## ## ## ## ## ## ## \n" \ 121 | " ######## ## ## ### ###### ###### \n" \ 122 | " ## ## ## ## ## ## ## ##\n" \ 123 | " ## ## ## ## ## ## ## ## ##\n" \ 124 | " ######## ####### ## ## ######## ######${COLOFF}\n" 125 | printf "%-7s ${YELLOW}%-15s${COLOFF} ${WHITE}%-35s${COLOFF} ${TEAL}%6s${COLOFF} %-12s\n" "No." "Username" "Hostname" "Port" "Group" 126 | echo "-------------------------------------------------------------------------------" 127 | 128 | if [[ $1 ]]; then 129 | if [[ -z ${1//[[:digit:]]} ]]; then # strictly numerical 130 | (( $1 > NUM_SERVERS || $1 <= 0 )) && { echo "Invalid server entry." >&2; exit 1; } 131 | list $(awk -F'|' 'NR == '$1'{print $1,$2,$3,$4,NR }' "$SERVERS") 132 | else # must be a group 133 | iterate_and list <(awk '/\|'$1'$/{ print $0,"|",NR }' "$SERVERS") 134 | fi 135 | else 136 | iterate_and list | nl --number-format=ln 137 | fi 138 | ;; 139 | 140 | export) iterate_and export_to_ssh ;; 141 | search) iterate_and list <(awk -F'|' "/$1/"'{ printf "%s|%s|%s|%s|%d\n",$1,$2,$3,$4,NR }' "$SERVERS") ;; 142 | 143 | *) { 144 | [[ $action ]] && echo "Invalid action: '$action'" 145 | echo "Usage: " 146 | echo " connect " 147 | echo " list [servernum]" 148 | echo " add [port]" 149 | echo " keyup " 150 | echo " edit " 151 | echo " remove " 152 | echo " search " 153 | echo " export " 154 | exit 1 155 | } >&2 156 | ;; 157 | esac 158 | 159 | -------------------------------------------------------------------------------- /sprunge: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # uploader for sprunge.us 5 | # 6 | 7 | shopt -s extglob 8 | 9 | declare -a respheaders 10 | 11 | declare content_encoding=cat # NOOP decompressor 12 | 13 | declare -r uastring="bashium 0.2 (bash ${BASH_VERSINFO[0]}.${BASH_VERSINFO[1]})" 14 | declare -r hostname='sprunge.us' 15 | declare -r port='80' bufsiz=4096 16 | 17 | usage() { 18 | cat <&2 31 | 32 | die() { 33 | error "$@" 34 | exit 1 35 | } 36 | 37 | error() { 38 | local mesg=$1; shift 39 | printf "\033[1;31m::\033[0m $mesg\n" "$@" 40 | } 41 | 42 | warn() { 43 | local mesg=$1; shift 44 | printf "\033[1;33m::\033[0m $mesg\n" "$@" 45 | } 46 | 47 | info() { 48 | local mesg=$1; shift 49 | printf "\033[1;34m::\033[0m \033[1m$mesg\033[0m\n" "$@" 50 | } 51 | 52 | create_boundary_string() { 53 | # borrowed from curl's lib/formdata.c 54 | local -r hextab='1234567890abcdef' 55 | 56 | printf -- '-%.s' {1..28} 57 | for x in {1..12}; do 58 | printf '%c' "${hextab:RANDOM%16}" 59 | done 60 | } 61 | 62 | # content handlers 63 | plain() { 64 | cat 65 | } 66 | 67 | 68 | # connect handler 69 | connect() { 70 | { exec {sock}<>/dev/tcp/$hostname/$port; } 2>/dev/null || 71 | die 'error: failed to connect to %s:%s\n' "$hostname" "$port" 72 | } 73 | 74 | 75 | # request handlers 76 | send_http_request() { 77 | local request_uri=$1 content_len=$2 bstring=$3 78 | local -a headers=("POST $request_uri HTTP/1.1" 79 | "User-Agent: $uastring" 80 | "Host: $hostname" 81 | "Accept: */*" 82 | "Content-Length: $content_len" 83 | "Content-Type: multipart/form-data; boundary=$bstring" 84 | '') 85 | 86 | (( verbose )) && printf '> %s\n' "${headers[@]}" # "http://$hostname$1" 87 | printf -- '%s\r\n' "${headers[@]}" >&$sock 88 | } 89 | 90 | send_form_body() { 91 | local formdata=$1 bstring=$2 92 | local -a body=("--$bstring" 93 | 'Content-Disposition: form-data; name="sprunge"' 94 | '' 95 | "$formdata" 96 | "--$bstring--") 97 | 98 | (( verbose )) && printf '> %s\n' "${body[@]}" 99 | printf -- '%s\r\n' "${body[@]}" >&$sock 100 | } 101 | 102 | 103 | # response handlers 104 | assert_response_code() { 105 | read -r -u $sock header resp status 106 | (( verbose )) && printf '< %s %s %s\n' "$header" "$resp" "$status" 107 | 108 | if (( resp != $1 )); then 109 | die 'response code asserted failed! expected %s, got %s %s' "$1" "$resp" "$status" 110 | fi 111 | } 112 | 113 | read_response_headers() { 114 | local header= value= 115 | 116 | # read response until the end of the headers 117 | while IFS=': ' read -r -u $sock header value; do 118 | # end of headers 119 | [[ $header = $'\r' ]] && break 120 | 121 | (( verbose )) && printf '< %s: %s\n' "$header" "$value" 122 | 123 | header=${header,,} 124 | header=${header//-/_} 125 | read -r -d $'\r' "$header" <<< "$value" 126 | 127 | respheaders+=("$header") 128 | done 129 | (( verbose )) && printf '\n' 130 | 131 | content_type=${content_type##*/} # trim 'application/' 132 | 133 | # sanitize 134 | content_type=${content_type//[.-]/_} 135 | content_encoding=${content_encoding//[.-]/_} 136 | 137 | if ! type -p "$content_type" &>/dev/null; then 138 | die 'unknown/unhandled content type: %s\n' "$content_type" 139 | fi 140 | 141 | if ! type -p "$content_encoding" &>/dev/null; then 142 | die 'unknown/unhandled encoding type: %s\n' "$content_encoding" 143 | fi 144 | } 145 | 146 | read_buffered() { 147 | local readlen= actual= bs= fd=$1 len=$2 148 | 149 | while (( len > 0 )); do 150 | (( len > bufsiz )) && readlen=$bufsiz || readlen=$len 151 | actual=$(<&$fd dd bs=1 count=$readlen 2>/dev/null | tee >(wc -c) >&5) 152 | (( len -= actual )) 153 | done 5>&1 154 | } 155 | 156 | read_response_body() { 157 | local len= 158 | 159 | if [[ $transfer_encoding = chunked ]]; then 160 | while true; do 161 | read -r -d $'\r\n' -u $sock len # read length, consume \r 162 | read -r -n 1 -u $sock _ # consume \n 163 | 164 | len=$(( 0x$len )) # convert hex2dec 165 | if (( len == 0 )); then # exit condition 166 | read -r -n 4 -u $sock _ # consume final \r\n\r\n 167 | break 168 | fi 169 | 170 | read_buffered $sock $len # read chunk 171 | 172 | read -r -n 2 -u $sock _ # consume \r\n 173 | done 174 | else 175 | # hrmmm, unexpected. don't give up, just dump what we have out of the socket 176 | cat <&$sock 177 | fi 178 | } 179 | 180 | # main() 181 | while getopts ':hv' opt; do 182 | case $opt in 183 | h) usage 0 ;; 184 | v) verbose=1 ;; 185 | ?) die '%s: invalid option -- '\''%s'\' "${0##*/}" "$OPTARG" 186 | exit 1 ;; 187 | esac 188 | done 189 | shift $(( OPTIND - 1 )) 190 | 191 | # shutdown socket on exit 192 | trap '[[ $sock && -e /dev/fd/$sock ]] && exec {sock}<&-' EXIT 193 | 194 | if [[ $1 && ! -e $1 ]]; then 195 | die 'file not found: %s\n' "$1" 196 | fi 197 | 198 | boundary_string=$(create_boundary_string) 199 | 200 | formdata=$(<"${1:-/dev/stdin}") 201 | 202 | # boundary string + c-d header + data + boundary string (w/ tail) + line endings 203 | content_len=$(( 42 + 46 + ${#formdata} + 44 + 10 )) 204 | 205 | connect 206 | send_http_request "/" "$content_len" "$boundary_string" 207 | send_form_body "$formdata" "$boundary_string" 208 | assert_response_code 200 209 | read_response_headers 210 | read_response_body 211 | 212 | -------------------------------------------------------------------------------- /orb: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # stupid proof of concept bash based AUR agent 5 | # 6 | 7 | shopt -s extglob 8 | 9 | declare -a respheaders uri_list 10 | 11 | declare -i actrpc=0 actpb=0 acttb=0 12 | 13 | declare content_encoding=cat # NOOP decompressor 14 | 15 | declare -r uastring="bashium 0.2 (bash ${BASH_VERSINFO[0]}.${BASH_VERSINFO[1]})" 16 | declare -r hostname='aur.archlinux.org' 17 | declare -r port='80' bufsiz=4096 18 | declare -r fmt_pkgpath='/packages/%s/%s/%s.tar.gz' 19 | declare -r fmt_rpcpath='/rpc.php?type=%s%s' 20 | declare -r fmt_pbpath='/packages/%s/%s/PKGBUILD' 21 | 22 | usage() { 23 | cat < 30 | -I show package info for target(s) 31 | -m search for target(s) by maintainer 32 | -s search for target(s) 33 | 34 | Options: 35 | -h display this help message and exit 36 | -r raw output 37 | -v show http response headers 38 | 39 | EOF 40 | exit 1 41 | } >&2 42 | 43 | die() { 44 | error "$@" 45 | exit 1 46 | } 47 | 48 | error() { 49 | local mesg=$1; shift 50 | printf "\033[1;31m::\033[0m $mesg\n" "$@" 51 | } 52 | 53 | warn() { 54 | local mesg=$1; shift 55 | printf "\033[1;33m::\033[0m $mesg\n" "$@" 56 | } 57 | 58 | info() { 59 | local mesg=$1; shift 60 | printf "\033[1;34m::\033[0m \033[1m$mesg\033[0m\n" "$@" 61 | } 62 | 63 | urlencode() { 64 | local i= char= url=$* 65 | declare -i len=${#url} 66 | 67 | for (( i = 0; i < len; i++ )); do 68 | char=${url:i:1} 69 | case $char in 70 | [a-zA-Z0-9.~_-]) printf "$char" ;; 71 | ' ') printf + ;; 72 | *) printf '%%%X' "'$char" ;; 73 | esac 74 | done 75 | } 76 | 77 | build_uri_list() { 78 | local target= encoded= qtype=$1; shift 79 | 80 | if (( actrpc )); then 81 | if [[ $qtype = multiinfo ]]; then 82 | for arg; do 83 | encoded+="&arg[]=$(urlencode "$arg")" 84 | done 85 | printf "$fmt_rpcpath\n" 'multiinfo' "$encoded" 86 | else 87 | for target; do 88 | printf "$fmt_rpcpath\n" "$qtype" "&arg=$(urlencode "$target")" 89 | done 90 | fi 91 | elif (( actpb )); then 92 | for target; do 93 | printf "$fmt_pbpath\n" "$(urlencode "${target:0:2}")" "$(urlencode "$target")" 94 | done 95 | elif (( acttb )); then 96 | for target; do 97 | encoded=$(urlencode "$target") 98 | printf "$fmt_pkgpath\n" "$(urlencode "${target:0:2}")" "$encoded" "$encoded" 99 | done 100 | fi 101 | } 102 | 103 | # content handlers 104 | json() { 105 | type -P json_reformat >/dev/null && json_reformat || cat 106 | } 107 | 108 | x_tgz() { 109 | if tar xz; then 110 | info '%s downloaded to %s' "$target" "$PWD" 111 | else 112 | error 'error downloading %s' "$target" 113 | fi 114 | } 115 | 116 | plain() { 117 | less 118 | } 119 | 120 | 121 | # decompress handlers 122 | gzip() { 123 | command gzip -d 124 | } 125 | 126 | 127 | # connect handler 128 | connect() { 129 | { exec {sock}<>/dev/tcp/$hostname/$port; } 2>/dev/null || 130 | die 'error: failed to connect to %s:%s\n' "$hostname" "$port" 131 | } 132 | 133 | 134 | # request handler 135 | send_http_request() { 136 | local -a headers=( 137 | "GET $1 HTTP/1.1" 138 | "User-Agent: $uastring" 139 | "Host: $hostname" 140 | "Accept-Encoding: gzip" 141 | "Accept: */*" 142 | "Connection: Keep-Alive" 143 | "" 144 | ) 145 | 146 | (( verbose )) && printf '> %s\n' "http://$hostname$1" "${headers[@]}" 147 | printf '%s\r\n' "${headers[@]}" >&$sock 148 | } 149 | 150 | 151 | # response handlers 152 | read_response_code() { 153 | read -r -u $sock header resp status 154 | (( verbose )) && printf '< %s: %s %s\n' "$header" "$resp" "$status" 155 | 156 | if [[ $resp ]]; then 157 | if (( resp == 404 )); then 158 | die "package \`%s' not found" "$target" 159 | elif (( resp >= 300 )); then 160 | die "server responded with HTTP %d" "$resp" 161 | fi 162 | # assume HTTP 200 (eww) 163 | fi 164 | } 165 | 166 | read_response_headers() { 167 | local header= value= 168 | 169 | # read response until the end of the headers 170 | while IFS=': ' read -r -u $sock header value; do 171 | # end of headers 172 | [[ $header = $'\r' ]] && break 173 | 174 | (( verbose )) && printf '< %s: %s\n' "$header" "$value" 175 | 176 | header=${header,,} 177 | header=${header//-/_} 178 | read -r -d $'\r' "$header" <<< "$value" 179 | 180 | respheaders+=("$header") 181 | done 182 | (( verbose )) && printf '\n' 183 | 184 | content_type=${content_type##*/} # trim 'application/' 185 | 186 | # sanitize 187 | content_type=${content_type//[.-]/_} 188 | content_encoding=${content_encoding//[.-]/_} 189 | 190 | if ! type -p "$content_type" &>/dev/null; then 191 | die 'unknown/unhandled content type: %s\n' "$content_type" 192 | fi 193 | 194 | if ! type -p "$content_encoding" &>/dev/null; then 195 | die 'unknown/unhandled encoding type: %s\n' "$content_encoding" 196 | fi 197 | } 198 | 199 | read_buffered() { 200 | local readlen= actual= bs= fd=$1 len=$2 201 | 202 | while (( len > 0 )); do 203 | (( len > bufsiz )) && readlen=$bufsiz || readlen=$len 204 | actual=$(<&$fd dd bs=1 count=$readlen 2>/dev/null | tee >(wc -c) >&5) 205 | (( len -= actual )) 206 | done 5>&1 207 | } 208 | 209 | read_response_body() { 210 | local len= 211 | 212 | if (( content_length )); then 213 | read_buffered $sock $content_length | $content_encoding | ${raw:-$content_type} 214 | elif [[ $transfer_encoding = chunked ]]; then 215 | while true; do 216 | read -r -d $'\r\n' -u $sock len # read length, consume \r 217 | read -r -n 1 -u $sock _ # consume \n 218 | 219 | len=$(( 0x$len )) # convert hex2dec 220 | if (( len == 0 )); then # exit condition 221 | read -r -n 4 -u $sock _ # consume final \r\n\r\n 222 | break 223 | fi 224 | 225 | read_buffered $sock $len # read chunk 226 | 227 | read -r -n 2 -u $sock _ # consume \r\n 228 | done | $content_encoding | ${raw:-$content_type} 229 | else 230 | $content_encoding <&$sock | ${raw:-$content_type} 231 | fi 232 | } 233 | 234 | # shutdown socket on exit 235 | trap '[[ $sock && -e /dev/fd/$sock ]] && exec {sock}<&-' EXIT 236 | 237 | # main() 238 | while getopts ':dghIimrsv' opt; do 239 | case $opt in 240 | d) acttb=1 ;; 241 | g) actpb=1 ;; 242 | h) usage ;; 243 | I) (( ++actrpc )); qtype=info ;; 244 | i) (( ++actrpc )); qtype=multiinfo ;; 245 | m) (( ++actrpc )); qtype=msearch ;; 246 | r) raw=cat ;; 247 | s) (( ++actrpc )); qtype=search ;; 248 | v) verbose=1 ;; 249 | ?) die '%s: invalid option -- '\''%s'\' "${0##*/}" "$OPTARG" 250 | exit 1 ;; 251 | esac 252 | done 253 | shift $(( OPTIND - 1 )) 254 | 255 | case $(( acttb + actrpc + actpb )) in 256 | 0) die 'no operation specified (use -h for help)' ;; 257 | [^1]) die 'cannot specify multiple operations' ;; 258 | esac 259 | 260 | (( $# )) || die 'no targets specified (use -h for help)' 261 | 262 | IFS=$'\n' read -r -d '' -a uri_list < <(build_uri_list "$qtype" "$@") 263 | 264 | connect 265 | for uri in "${uri_list[@]}"; do 266 | target=$1; shift 267 | [[ $connection = close ]] && connect; # keep-alive expired 268 | unset "${respheaders[@]}" 269 | send_http_request "$uri" 270 | read_response_code 271 | read_response_headers 272 | read_response_body 273 | done 274 | 275 | --------------------------------------------------------------------------------