# vim: et sw=4 ts=4 filetype=sh # .bashrc with functions for interactive shells ## doc: err message: send message to stderr err() { local f="${FUNCNAME[1]}" echo "${f:+$f: }$*" 1>&2 } ## doc: remove a repetition of a character from the beginning of a string ltrim() { local c s r c="$1" shift s="$*" r="${s#$c}" while [ "$r" != "$s" ]; do s="$r" r="${s#$c}" done echo "$r" } ## doc: remove a repetition of a character from the end of a string rtrim() { local c s r c="$1" shift s="$*" r="${s%$c}" while [ "$r" != "$s" ]; do s="$r" r="${s%$c}" done echo "$r" } ## doc: remove a repetition of a character from the beginning and end of a string trim() { local c="$1" shift ltrim "$c" $(rtrim "$c" $*) } ## doc: start screen s() { if [ -z "$STY" ]; then export SCREENTERM=$TERM screen -RR -D else err already running under screen fi } ## doc: simple http cat wcat() { local url host port path file header while [ "$1" ]; do url="${1#http://}" shift host="${url%%/*}" port="${host#*:}" [ "$port" = "$host" ] && port=80 host="${host%%:*}" path="${url#*/}" if [ "$path" = "$url" ]; then path="/" else path="/$path" fi file="${path##*/}" [ -z "$file" ] && file="index.html" echo -n "http://$host:$port$path -> $file" ( echo -en 'GET '$path' HTTP/1.0\r\n' echo -en 'Host: '$host:$port'\r\n\r\n' while read header ; do [ "${#header}" -lt 3 ] && break done cat > "$file" ) <> /dev/tcp/$host/$port 1>&0 echo done } ## equals passwordcomposer ## http://www.xs4all.nl/~jlpoutre/BoT/Javascript/PasswordComposer/ ## doc: passwordcomposer function pass() { local host pass host="$1" pass="$2" if [ -z "$host" ]; then echo -n "Enter domain name: " read host fi ( if [ -z "$pass" ]; then trap "stty echo" EXIT echo -n "Enter master password (won't echo): " stty -echo read pass stty echo echo fi echo -n "$pass:$host" | (md5sum || md5 || openssl md5 -hex) 2>/dev/null \ | cut -b 1-8 ) } ## encode username:password for http basic auth http_basic_auth() { local user pass user="$1" pass="$2" if [ -z "$user" ]; then echo -n "Enter username: " read user fi ( if [ -z "$pass" ]; then trap "stty echo" EXIT echo -n "Enter password (won't echo): " stty -echo read pass stty echo echo fi echo "Authorization: Basic" $(perl -MMIME::Base64 -e 'print encode_base64(join(":", @ARGV))' "$user" "$pass") ) } ## extract archives ## doc: extract archives extract() { local arg t dir tmp abs dest for arg; do if ! [ -e "$arg" ]; then err "$arg: doesn't exist, skipped" continue fi if [ -z "${arg%%/*}" ]; then abs="$arg" else abs="$PWD/$arg" fi if [ "$(uname -s)" = Darwin ]; then t=$(file -I "$abs") else t=$(file -i "$abs") fi # remove ; charset=.. t="${t%%;*}" if [ -z "$t" ] || [ -z "${t##*application/octet-stream*}" ]; then t="${arg##*.}" fi if [ -z "$t" ]; then err "$arg: couldn't detect type" continue fi if [ -z "${t##*text/*}" ]; then err "$arg: not an archive" continue fi dest="${arg##*/}" dest="${dest%.*}" if [ -z "$dest" ]; then err "$arg: no useful name extracted" continue fi dir=".__${dest}__"; if [ -e "$dir" ]; then err "$arg: temporary directory ($dir) already exists, remove it" continue fi mkdir -p "$dir" case "$t" in *rpm) tmp=$(rpm -qp --nosignature "$abs" --qf '%{name}-%{version}-%{release}.%{arch}') mkdir -p "$dir/$tmp" rpm2cpio "$abs" | (cd "$dir/$tmp" && cpio -idu --no-absolute-filenames --quiet) ;; *tar) tar -xpf "$abs" -C "$dir" ;; *gzip) if [ -z "${arg##*.tgz}" ] || [ -z "${arg##*tar*}" ]; then tar -xzpf "$abs" -C "$dir" else zcat < "$abs" > "$dir/$dest" fi ;; *xz | *lzma) if [ -z "${arg##*.txz}" ] || [ -z "${arg##*tar*}" ]; then xzcat < "$abs" | tar -xpf - -C "$dir" else xzcat < "$abs" > "$dir/$dest" fi ;; *bzip2) if [ -z "${arg##*.tbz*}" ] || [ -z "${arg##*tar*}" ]; then tar -xjpf "$abs" -C "$dir" else bzcat < "$abs" > "$dir/$dest" fi ;; *zip) unzip -q -d "$dir" "$abs" ;; *rar) (cd "$dir" && unrar -inul x "$abs") ;; *iso*) tmp="" if isoinfo -f -R -i "$abs" &> /dev/null; then tmp="-R" elif isoinfo -f -J -i "$abs" &> /dev/null; then tmp="-J" fi isoinfo $tmp -f -i "$abs" | while read; do REPLY="${REPLY%;[0-9]*}" REPLY="${REPLY%.}" t="$dir/${REPLY%/*}" if [ -f "$t" ]; then rm -f "$t" fi mkdir -p "$t" isoinfo $tmp -x "$REPLY" -i "$abs" > "$dir/$REPLY" done ;; *) err "$arg: couldn't detect type" rmdir "$dir" continue ;; esac tmp=("$dir/"*) if ! [ -e "$tmp" ]; then err "$arg: nothing extracted" rmdir "$dir" elif [ "${#tmp[*]}" -gt 1 ]; then tmp="$dest" t=1 while [ -e "$tmp" ]; do tmp="$dest.$((t++))" done mv -f "$dir" "$tmp" echo "$arg: extracted to $tmp" else dest="${tmp##*/}" t=1 tmp="$dest" while [ -e "$tmp" ]; do tmp="$dest.$((t++))" done mv -f "$dir/"* "$tmp" echo "$arg: extracted to $tmp" rmdir "$dir" fi done } ## doc: show KB size in number of CDs or DVDs size_cds() { local size="$1" dvds cds ((cds=size/683593)) if [ $((size%683593)) -gt 0 ]; then ((cds++)) fi ((dvds=size/4589843)) if [ $((size%4589843)) -gt 0 ]; then ((dvds++)) fi echo "$cds CDs, $dvds DVDs" } ## doc: compute expected iso size size_iso() { local calc size arg tmp do_total calc="mkisofs -J -joliet-long -r -pad -V LABEL -print-size -q" if [ $# -ge 1 ]; then if [ "$1" = "-c" ]; then do_total=1 shift fi for arg; do tmp=$(($($calc "$arg")*2)) echo "$arg: ${tmp}KB $((tmp/1024))MB: $(size_cds $tmp)" ((size += tmp)) done if [ -n "$do_total" ]; then echo "total: ${size}KB $((size/1024))MB: $(size_cds $size)" fi else tmp=$(($($calc .)*2)) echo "${tmp}KB $((tmp/1024))MB: $(size_cds $tmp)" fi } ## doc: compute expected playlist size, for DVDs size_playlist() { perl -n -e ' if (/file:/) { chomp; s/.*file:..//; s/%(..)/chr(hex($1))/ge; my $size = (stat($_))[7]; $total += int($size / 2048); $total++ if $size % 2048; } END { $size = $total * 2048; $size_gb = $size / 1024.0 / 1024.0 /1024.0; $dvd = $total*100.0/2294921.0; $free = ((100.0 - $dvd) / 100.0) * 2294921.0 / 512.0; $free_t = $free * 1024.0 * 1024.0 / 16000.0; $free_h = int($free_t / 3600.0); $free_m = int(($free_t - $free_h * 3600.0) / 60.0); $time = $free_h ? $free_h . "h" : ""; $time .= $free_m ? $free_m . "m" : ""; printf "sectors: \%s\n". "size: \%.0f (\%.2f GB)\n". "dvd: \%.2f\%\%\n". "free: %.2f MB ~ %s\n", $total, $size, $size_gb, $dvd, $free, $time; } ' "$@" } ## w/ colordiff installed if type -p cdiff &> /dev/null; then _use_cdiff() { if [ -t 1 ]; then "$@" | cdiff else "$@" fi } else _use_cdiff() { if [ -t 1 ]; then "$@" | ${PAGER:-more} else "$@" fi } fi ## doc: make svn more user-friendly svn() { _dvcs svn "$@"; } ## doc: make hg more user-friendly hg() { _dvcs hg "$@"; } ## doc: make cvs more user-friendly cvs() { _dvcs cvs "$@"; } ## make hg/svn/alikes more user-friendly _dvcs() { local cmd="$(type -P $1)" shift if [ "$1" = "diff" ]; then _use_cdiff "$cmd" "$@" elif [ -t 1 ] && [ "$1" = "help" -o "$1" = "blame" -o "$1" = "log" ]; then "$cmd" "$@" | ${PAGER:-more} else "$cmd" "$@" fi } ## doc: colorize output of diff diff() { _use_cdiff "$(type -P diff)" -u "$@" } ## doc: crypt(3) crypt() { local pass salt crypt pass="$1" salt="$2" [ -z "$pass" ] && return if [ -z "$salt" ]; then salt="\$1\$$(perl -e 'print map { $_ < 10 ? chr $_ + 48 : $_ < 36 ? chr $_ + 55 : chr $_ + 61 } map { int rand 62 } 1..8')\$" fi crypt=$(perl -e "print crypt('$pass', '$salt')") echo crypt: $crypt echo base64: $(perl -MMIME::Base64 -e "print encode_base64('{crypt}'.'$crypt')") } ## run vim svim() { _vim vim "$@" } ## run gvim, open multiple windows for multiple files mvim() { local a args opt nonopt fg declare -a args declare -a opt declare -a nonopt fg= for a; do if [ -z "${a##-*}" ]; then [ "$a" = "-f" ] && fg=1 opt=("${opt[@]}" "$a") else nonopt=("${nonopt[@]}" "$a") fi done if [ -n "$fg" -o "${#nonopt[@]}" -gt 7 ]; then _vim gvim "${opt[@]}" "${nonopt[@]}" return fi if [ "${#nonopt[@]}" -eq 0 ]; then gvim "${opt[@]}" return fi for a in "${nonopt[@]}"; do if [ -z "${a##+*}" ]; then args=("${args[@]}" "$a") elif [ ${#args[*]} -gt 0 ]; then _vim gvim "${opt[@]}" "${args[@]}" args=("$a") else args=("$a") fi done if [ ${#args[*]} -gt 0 ]; then _vim gvim "${opt[@]}" "${args[@]}" fi } ## smart vim: tranform file:line -> +line, file:func() -> file +/func/ _vim() { local vi args a n t aa declare -a args vi="$1" shift for a; do if [ -e "$a" ] || [ -z "${a##*[0-9]:[0-9]}" ]; then ## file exists, or probably an interface alias ## use as is args=("${args[@]}" "$a") continue fi if [ -z "${a##*::*}" ]; then # perl module, find source with the help of perldoc t=$(perldoc -l "$a" 2>/dev/null) if [ -n "$t" ]; then args=("${args[@]}" "$t") continue fi fi ## remove trailing ':', output by grep -n, for instance aa="${a/%:}" if [ "$aa" != "${aa%:[0-9]*}" ]; then ## with line information n="${aa##*:}" aa="${aa%:*}" ## must exist if [ -e "$aa" ]; then args=("${args[@]}" "$aa" "+$n") else args=("${args[@]}" "$a") fi elif [ "$aa" != "${aa%::*}" ]; then ## with function aa="${aa/%()}" n="${aa##*::}" aa="${aa%::*}" aa="${aa//\\/\\\\}" ## must exist if [ -e "$aa" ]; then args=("${args[@]}" "$aa" \ '+/\v((sub|function|def)\s+<\V'"$n"'\v>)|(<'"$n"'>\s*\()') else args=("${args[@]}" "$a") fi else ## use as is args=("${args[@]}" "$a") fi done command $vi "${args[@]}" } ## doc: list directories in arguments ldir() { local arg if [ -z "${1##-*}" ]; then arg="$1" shift fi find "$@" -maxdepth 1 -type d -print0 | xargs -0r ls --color=auto -d $arg } ## doc: list symbolic links in arguments lln() { local arg if [ -z "${1##-*}" ]; then arg="$1" shift fi find "$@" -maxdepth 1 -type l -print0 | xargs -0r ls --color=auto -dF $arg } ## doc: test a perl program perl_test() { ( ulimit -c 0 perl -Ilib -I. "$@" ) 2>&1 | ${PAGER:-more} } ## doc: run the perl debugger with usual modules perldbg() { ( ulimit -c 0 mod=(YAML::XS=Load,Dump,LoadFile,DumpFile Data::Dumper MIME::Base64) aval=() for m in "${mod[@]}"; do if perl "-M$m" -e 1 &> /dev/null; then aval[ ${#aval[@]} ]="$m" fi done e=() for m; do if [[ "$m" =~ '^([[:alnum:]_)+(::[[:alnum:]_]+)*([ =].+)?$' ]]; then if perl "-M$m" -e 1 &> /dev/null; then aval[ ${#aval[@]} ]="$m" else echo "skipping $m, unable to load it" >&2 fi elif [ -z "${m##*.pm}" ]; then if [ -e "$m" ]; then e[ ${#e[@]} ]="require '$m';" else echo "skipping $m, unable to find it" >&2 fi else e[ ${#e[@]} ]="$m;" fi done echo "will import modules: ${aval[*]}" >&2 echo "will execute: ${e[*]} 1" >&2 perl -Ilib -I. -d "${aval[@]/#/-M}" "${e[@]/#/-e}" -e1 ) } ## doc: create a bare git clone git-bare() { ( set +m; _git-bare-bg "$@" ) } _git-bare-bg() { local url tmpd name tmpd=`mktemp -d` || exit 1 trap "rm -fr $tmpd" EXIT for url; do ## remove /.git name=$(rtrim / "$url") name="${name%.git}" name=$(rtrim / "$name") ## remove url, add .git name="${name##*/}.git" if [ "$name" = ".git" ]; then echo "coudln't get name for $url" continue fi if [ -d "$name" ]; then echo "skipping $name, already exists" continue fi ( if { git clone --bare "$url" "$name" && cd "$name" && git remote add -f --mirror origin "$url" || { git config remote.origin.url "$url" && git config remote.origin.fetch "+refs/heads/*:refs/heads/*" } } &> $tmpd/$$; then echo "finished $name" else echo "failed to create $name:" tail -n 3 $tmpd/$$ fi ) & done wait } ## doc: remove a line from a file: rmline or rmline() { local file line file="$1" line="$2" if [ -z "$file" ]; then err "missing file to remove" return 1 fi if [ -z "$line" ]; then line="${file##*:}" file="${file%:*}" fi if ! [ -e "$file" ]; then err "file to remove doesn't exist" return 1 fi if [ -z "$line" ]; then err "missing line to remove" return 1 fi ed -s "$file" <<-EOF ${line}d wq EOF } ## doc: show local IP addresses ips() { ( PATH=$PATH:/usr/sbin:/sbin if _exists ip; then ip addr show to 0/0 else ifconfig | awk '/^[^ \t]/{i=$1} /inet/{ print i $0}' fi ) } ## doc: list interfaces belonging to a bridge brif() { local br="$1" shift brctl show | awk -v br=$br ' BEGIN { inbr=0 } /^[^ \t]/ { inbr=0 } $1 == br { if ($4) { print $4 } inbr=1 } /^[ \t]/ && inbr==1 { print $1 } ' } ## doc: list content differences between two directories dirdiff() { local src="$1" dst dst="${2:-.}" if [ -z "$src" ]; then err "missing original directory" return 1 fi if ! [ -d "$src" ]; then err "$src: not a directory" return 1 fi if ! [ -d "$dst" ]; then err "$dst: not a directory" return 1 fi diff -u <(cd "$src" && find . | LC_ALL=C sort | sed -e 's/^..//') \ <(cd "$dst" && find . | LC_ALL=C sort | sed -e 's/^..//') } ## doc: execute command with path to a binary; path added to the end or where '%' is bin() { local p a cmd spec p=$(type -P "$1" 2>/dev/null) if [ -z "$p" ]; then err "couldn't find $1" return 1 fi shift if [ $# -eq 0 ]; then echo $p return 0 fi declare -a cmd spec= for a; do if [ "$a" = "%" ]; then cmd[ ${#cmd[@]} ]="$p" spec=1 else cmd[ ${#cmd[@]} ]="$a" fi done [ -z "$spec" ] && cmd[ ${#cmd[@]} ]="$p" eval "${cmd[@]}" } ## doc: show nfo nfo() { local f="$1" a if [ -d "$f" ]; then declare -a a a=("$f"/*.[nN][fF][oO]) f="$a" fi if ! [ -f "$f" ]; then err "couldn't find nfo in $f" return 1 fi iconv -f cp437 "$f" | tr -d '\r' | sed -e 's/^ *$//' | LESS="${LESS:--}rsS" ${PAGER:-more} } ## doc: swap the name of two files fswap() { local t if [ $# != 2 ]; then err "need two files: fswap file_1 file_2" return 1 fi for t; do if ! [ -e "$t" ]; then err "file '$t' doesn't exist" return 1 fi done t="$1.$$.tmp" if [ -e "$t" ]; then err "file with same name of temporary one exists ($t)" return 1 fi if ! mv -f "$1" "$t"; then err "error renaming first file to temporary name, nothing changed" return 1 fi # $1 is now $t # todo: $2 -> $1; $t -> $2 if ! mv -f "$2" "$1"; then if mv -f "$t" "$1" 2>/dev/null; then echo "error renaming second file to first, nothing changed" else echo "error renaming second file to first, and undoing first file" echo "file '$1' is now '$t'" fi return 1 fi # $1 is $t # $2 is $1 # todo: $t -> $2 if ! mv -f "$t" "$2"; then if mv -f "$1" "$2" 2>/dev/null; then # $2 restored, try $1 if mv -f "$t" "$1" 2>/dev/null; then echo "error renaming first file to second, nothing changed" else echo "error renaming first file to second, and undoing first file" echo "file '$1' is now '$t'" fi else echo "error renaming first file to second, and undoing second file" echo "file '$1' is now '$t'" echo "file '$2' is now '$1'" fi return 1 fi }