dot rc

Sign in or create your account | Project List | Help

dot rc Git Source Tree

Root/rc/bash_functions

1# vim: et sw=4 ts=4 filetype=sh
2# .bashrc with functions for interactive shells
3
4## doc: err message: send message to stderr
5err()
6{
7    local f="${FUNCNAME[1]}"
8    echo "${f:+$f: }$*" 1>&2
9}
10
11## doc: remove a repetition of a character from the beginning of a string
12ltrim()
13{
14    local c s r
15    c="$1"
16    shift
17    s="$*"
18    r="${s#$c}"
19    while [ "$r" != "$s" ]; do
20        s="$r"
21        r="${s#$c}"
22    done
23    echo "$r"
24}
25
26## doc: remove a repetition of a character from the end of a string
27rtrim()
28{
29    local c s r
30    c="$1"
31    shift
32    s="$*"
33    r="${s%$c}"
34    while [ "$r" != "$s" ]; do
35        s="$r"
36        r="${s%$c}"
37    done
38    echo "$r"
39}
40
41## doc: remove a repetition of a character from the beginning and end of a string
42trim()
43{
44    local c="$1"
45    shift
46    ltrim "$c" $(rtrim "$c" $*)
47}
48
49## doc: start screen
50s()
51{
52    if [ -z "$STY" ]; then
53        export SCREENTERM=$TERM
54        screen -RR -D
55    else
56        err already running under screen
57    fi
58}
59
60## doc: simple http cat
61wcat()
62{
63    local url host port path file header
64    while [ "$1" ]; do
65        url="${1#http://}"
66        shift
67
68        host="${url%%/*}"
69        port="${host#*:}"
70        [ "$port" = "$host" ] && port=80
71        host="${host%%:*}"
72        path="${url#*/}"
73        if [ "$path" = "$url" ]; then
74            path="/"
75        else
76            path="/$path"
77        fi
78        file="${path##*/}"
79        [ -z "$file" ] && file="index.html"
80        echo -n "http://$host:$port$path -> $file"
81        (
82            echo -en 'GET '$path' HTTP/1.0\r\n'
83            echo -en 'Host: '$host:$port'\r\n\r\n'
84            while read header ; do
85                [ "${#header}" -lt 3 ] && break
86            done
87            cat > "$file"
88        ) <> /dev/tcp/$host/$port 1>&0
89        echo
90    done
91}
92
93## equals passwordcomposer
94## http://www.xs4all.nl/~jlpoutre/BoT/Javascript/PasswordComposer/
95## doc: passwordcomposer function
96pass()
97{
98    local host pass
99    host="$1"
100    pass="$2"
101
102    if [ -z "$host" ]; then
103        echo -n "Enter domain name: "
104        read host
105    fi
106    (
107    if [ -z "$pass" ]; then
108        trap "stty echo" EXIT
109        echo -n "Enter master password (won't echo): "
110        stty -echo
111        read pass
112        stty echo
113        echo
114    fi
115    echo -n "$pass:$host" | (md5sum || md5 || openssl md5 -hex) 2>/dev/null \
116        | cut -b 1-8
117    )
118}
119
120## encode username:password for http basic auth
121http_basic_auth() {
122    local user pass
123    user="$1"
124    pass="$2"
125
126    if [ -z "$user" ]; then
127        echo -n "Enter username: "
128        read user
129    fi
130    (
131    if [ -z "$pass" ]; then
132        trap "stty echo" EXIT
133        echo -n "Enter password (won't echo): "
134        stty -echo
135        read pass
136        stty echo
137        echo
138    fi
139    echo "Authorization: Basic" $(perl -MMIME::Base64 -e 'print encode_base64(join(":", @ARGV))' "$user" "$pass")
140    )
141}
142
143## extract archives
144## doc: extract archives
145extract()
146{
147    local arg t dir tmp abs dest
148    for arg; do
149        if ! [ -e "$arg" ]; then
150            err "$arg: doesn't exist, skipped"
151            continue
152        fi
153
154        if [ -z "${arg%%/*}" ]; then
155            abs="$arg"
156        else
157            abs="$PWD/$arg"
158        fi
159
160        if [ "$(uname -s)" = Darwin ]; then
161            t=$(file -I "$abs")
162        else
163            t=$(file -i "$abs")
164        fi
165
166        # remove ; charset=..
167        t="${t%%;*}"
168
169        if [ -z "$t" ] || [ -z "${t##*application/octet-stream*}" ]; then
170            t="${arg##*.}"
171        fi
172
173        if [ -z "$t" ]; then
174            err "$arg: couldn't detect type"
175            continue
176        fi
177
178        if [ -z "${t##*text/*}" ]; then
179            err "$arg: not an archive"
180            continue
181        fi
182
183        dest="${arg##*/}"
184        dest="${dest%.*}"
185
186        if [ -z "$dest" ]; then
187            err "$arg: no useful name extracted"
188            continue
189        fi
190
191        dir=".__${dest}__";
192
193        if [ -e "$dir" ]; then
194            err "$arg: temporary directory ($dir) already exists, remove it"
195            continue
196        fi
197
198        mkdir -p "$dir"
199
200        case "$t" in
201            *rpm)
202            tmp=$(rpm -qp --nosignature "$abs" --qf '%{name}-%{version}-%{release}.%{arch}')
203            mkdir -p "$dir/$tmp"
204            rpm2cpio "$abs" | (cd "$dir/$tmp" && cpio -idu --no-absolute-filenames --quiet)
205            ;;
206            *tar)
207                tar -xpf "$abs" -C "$dir"
208            ;;
209            *gzip)
210            if [ -z "${arg##*.tgz}" ] || [ -z "${arg##*tar*}" ]; then
211                tar -xzpf "$abs" -C "$dir"
212            else
213                zcat < "$abs" > "$dir/$dest"
214            fi
215            ;;
216            *xz | *lzma)
217            if [ -z "${arg##*.txz}" ] || [ -z "${arg##*tar*}" ]; then
218                xzcat < "$abs" | tar -xpf - -C "$dir"
219            else
220                xzcat < "$abs" > "$dir/$dest"
221            fi
222            ;;
223            *bzip2)
224            if [ -z "${arg##*.tbz*}" ] || [ -z "${arg##*tar*}" ]; then
225                tar -xjpf "$abs" -C "$dir"
226            else
227                bzcat < "$abs" > "$dir/$dest"
228            fi
229            ;;
230            *zip)
231            unzip -q -d "$dir" "$abs"
232            ;;
233            *rar)
234            (cd "$dir" && unrar -inul x "$abs")
235            ;;
236            *iso*)
237            tmp=""
238            if isoinfo -f -R -i "$abs" &> /dev/null; then
239                tmp="-R"
240            elif isoinfo -f -J -i "$abs" &> /dev/null; then
241                tmp="-J"
242            fi
243            isoinfo $tmp -f -i "$abs" | while read; do
244                REPLY="${REPLY%;[0-9]*}"
245                REPLY="${REPLY%.}"
246                t="$dir/${REPLY%/*}"
247                if [ -f "$t" ]; then
248                    rm -f "$t"
249                fi
250                mkdir -p "$t"
251                isoinfo $tmp -x "$REPLY" -i "$abs" > "$dir/$REPLY"
252            done
253            ;;
254            *)
255            err "$arg: couldn't detect type"
256            rmdir "$dir"
257            continue
258            ;;
259        esac
260
261        tmp=("$dir/"*)
262        if ! [ -e "$tmp" ]; then
263            err "$arg: nothing extracted"
264            rmdir "$dir"
265        elif [ "${#tmp[*]}" -gt 1 ]; then
266            tmp="$dest"
267            t=1
268            while [ -e "$tmp" ]; do
269                tmp="$dest.$((t++))"
270            done
271            mv -f "$dir" "$tmp"
272            echo "$arg: extracted to $tmp"
273        else
274            dest="${tmp##*/}"
275            t=1
276            tmp="$dest"
277            while [ -e "$tmp" ]; do
278                tmp="$dest.$((t++))"
279            done
280            mv -f "$dir/"* "$tmp"
281            echo "$arg: extracted to $tmp"
282            rmdir "$dir"
283        fi
284    done
285}
286
287## doc: show KB size in number of CDs or DVDs
288size_cds()
289{
290    local size="$1" dvds cds
291
292    ((cds=size/683593))
293    if [ $((size%683593)) -gt 0 ]; then
294        ((cds++))
295    fi
296
297    ((dvds=size/4589843))
298    if [ $((size%4589843)) -gt 0 ]; then
299        ((dvds++))
300    fi
301
302    echo "$cds CDs, $dvds DVDs"
303}
304
305## doc: compute expected iso size
306size_iso()
307{
308    local calc size arg tmp do_total
309    calc="mkisofs -J -joliet-long -r -pad -V LABEL -print-size -q"
310    if [ $# -ge 1 ]; then
311        if [ "$1" = "-c" ]; then
312            do_total=1
313            shift
314        fi
315        for arg; do
316            tmp=$(($($calc "$arg")*2))
317            echo "$arg: ${tmp}KB $((tmp/1024))MB: $(size_cds $tmp)"
318            ((size += tmp))
319        done
320        if [ -n "$do_total" ]; then
321            echo "total: ${size}KB $((size/1024))MB: $(size_cds $size)"
322        fi
323    else
324        tmp=$(($($calc .)*2))
325        echo "${tmp}KB $((tmp/1024))MB: $(size_cds $tmp)"
326    fi
327}
328
329## doc: compute expected playlist size, for DVDs
330size_playlist()
331{
332     perl -n -e '
333        if (/file:/) {
334            chomp;
335            s/.*file:..//;
336            s/%(..)/chr(hex($1))/ge;
337            my $size = (stat($_))[7];
338            $total += int($size / 2048);
339            $total++ if $size % 2048;
340        }
341        END {
342            $size = $total * 2048;
343            $size_gb = $size / 1024.0 / 1024.0 /1024.0;
344            $dvd = $total*100.0/2294921.0;
345            $free = ((100.0 - $dvd) / 100.0) * 2294921.0 / 512.0;
346            $free_t = $free * 1024.0 * 1024.0 / 16000.0;
347            $free_h = int($free_t / 3600.0);
348            $free_m = int(($free_t - $free_h * 3600.0) / 60.0);
349            $time = $free_h ? $free_h . "h" : "";
350            $time .= $free_m ? $free_m . "m" : "";
351            printf "sectors: \%s\n".
352                "size: \%.0f (\%.2f GB)\n".
353                "dvd: \%.2f\%\%\n".
354                "free: %.2f MB ~ %s\n",
355                $total, $size, $size_gb, $dvd, $free, $time;
356        }
357        ' "$@"
358}
359
360
361## w/ colordiff installed
362if type -p cdiff &> /dev/null; then
363    _use_cdiff()
364    {
365        if [ -t 1 ]; then
366            "$@" | cdiff
367        else
368            "$@"
369        fi
370    }
371else
372    _use_cdiff()
373    {
374        if [ -t 1 ]; then
375            "$@" | ${PAGER:-more}
376        else
377            "$@"
378        fi
379    }
380fi
381
382## doc: make svn more user-friendly
383svn() { _dvcs svn "$@"; }
384
385## doc: make hg more user-friendly
386hg() { _dvcs hg "$@"; }
387
388## doc: make cvs more user-friendly
389cvs() { _dvcs cvs "$@"; }
390
391## make hg/svn/alikes more user-friendly
392_dvcs()
393{
394    local cmd="$(type -P $1)"
395    shift
396
397    if [ "$1" = "diff" ]; then
398        _use_cdiff "$cmd" "$@"
399    elif [ -t 1 ] && [ "$1" = "help" -o "$1" = "blame" -o "$1" = "log" ]; then
400        "$cmd" "$@" | ${PAGER:-more}
401    else
402        "$cmd" "$@"
403    fi
404}
405
406## doc: colorize output of diff
407diff()
408{
409    _use_cdiff "$(type -P diff)" -u "$@"
410}
411
412## doc: crypt(3)
413crypt()
414{
415    local pass salt crypt
416    pass="$1"
417    salt="$2"
418
419    [ -z "$pass" ] && return
420    if [ -z "$salt" ]; then
421        salt="\$1\$$(perl -e 'print map { $_ < 10 ? chr $_ + 48 : $_ < 36 ? chr $_ + 55 : chr $_ + 61 } map { int rand 62 } 1..8')\$"
422    fi
423    crypt=$(perl -e "print crypt('$pass', '$salt')")
424    echo crypt: $crypt
425    echo base64: $(perl -MMIME::Base64 -e "print encode_base64('{crypt}'.'$crypt')")
426}
427
428## run vim
429svim()
430{
431    _vim vim "$@"
432}
433
434## run gvim, open multiple windows for multiple files
435mvim()
436{
437    local a args opt nonopt fg
438    declare -a args
439    declare -a opt
440    declare -a nonopt
441
442    fg=
443
444    for a; do
445        if [ -z "${a##-*}" ]; then
446            [ "$a" = "-f" ] && fg=1
447            opt=("${opt[@]}" "$a")
448        else
449            nonopt=("${nonopt[@]}" "$a")
450        fi
451    done
452    if [ -n "$fg" -o "${#nonopt[@]}" -gt 7 ]; then
453        _vim gvim "${opt[@]}" "${nonopt[@]}"
454        return
455    fi
456
457    if [ "${#nonopt[@]}" -eq 0 ]; then
458        gvim "${opt[@]}"
459        return
460    fi
461
462    for a in "${nonopt[@]}"; do
463        if [ -z "${a##+*}" ]; then
464            args=("${args[@]}" "$a")
465        elif [ ${#args[*]} -gt 0 ]; then
466            _vim gvim "${opt[@]}" "${args[@]}"
467            args=("$a")
468        else
469            args=("$a")
470        fi
471    done
472    if [ ${#args[*]} -gt 0 ]; then
473        _vim gvim "${opt[@]}" "${args[@]}"
474    fi
475}
476
477## smart vim: tranform file:line -> +line, file:func() -> file +/func/
478_vim()
479{
480    local vi args a n t aa
481    declare -a args
482
483    vi="$1"
484    shift
485    for a; do
486        if [ -e "$a" ] || [ -z "${a##*[0-9]:[0-9]}" ]; then
487            ## file exists, or probably an interface alias
488            ## use as is
489            args=("${args[@]}" "$a")
490            continue
491        fi
492
493        if [ -z "${a##*::*}" ]; then
494            # perl module, find source with the help of perldoc
495            t=$(perldoc -l "$a" 2>/dev/null)
496            if [ -n "$t" ]; then
497                args=("${args[@]}" "$t")
498                continue
499            fi
500        fi
501
502        ## remove trailing ':', output by grep -n, for instance
503        aa="${a/%:}"
504        if [ "$aa" != "${aa%:[0-9]*}" ]; then
505            ## with line information
506            n="${aa##*:}"
507            aa="${aa%:*}"
508            ## must exist
509            if [ -e "$aa" ]; then
510                args=("${args[@]}" "$aa" "+$n")
511            else
512                args=("${args[@]}" "$a")
513            fi
514        elif [ "$aa" != "${aa%::*}" ]; then
515            ## with function
516            aa="${aa/%()}"
517            n="${aa##*::}"
518            aa="${aa%::*}"
519            aa="${aa//\\/\\\\}"
520            ## must exist
521            if [ -e "$aa" ]; then
522                args=("${args[@]}" "$aa" \
523                    '+/\v((sub|function|def)\s+<\V'"$n"'\v>)|(<'"$n"'>\s*\()')
524            else
525                args=("${args[@]}" "$a")
526            fi
527        else
528            ## use as is
529            args=("${args[@]}" "$a")
530        fi
531    done
532    command $vi "${args[@]}"
533}
534
535## doc: list directories in arguments
536ldir()
537{
538    local arg
539    if [ -z "${1##-*}" ]; then
540        arg="$1"
541        shift
542    fi
543    find "$@" -maxdepth 1 -type d -print0 | xargs -0r ls --color=auto -d $arg
544}
545
546## doc: list symbolic links in arguments
547lln()
548{
549    local arg
550    if [ -z "${1##-*}" ]; then
551        arg="$1"
552        shift
553    fi
554    find "$@" -maxdepth 1 -type l -print0 | xargs -0r ls --color=auto -dF $arg
555}
556
557## doc: test a perl program
558perl_test()
559{
560    (
561        ulimit -c 0
562        perl -Ilib -I. "$@"
563    ) 2>&1 | ${PAGER:-more}
564}
565
566## doc: run the perl debugger with usual modules
567perldbg()
568{
569    (
570        ulimit -c 0
571        mod=(YAML::XS=Load,Dump,LoadFile,DumpFile
572            Data::Dumper
573            MIME::Base64)
574
575        aval=()
576        for m in "${mod[@]}"; do
577            if perl "-M$m" -e 1 &> /dev/null; then
578                aval[ ${#aval[@]} ]="$m"
579            fi
580        done
581
582        e=()
583        for m; do
584            if [[ "$m" =~ '^([[:alnum:]_)+(::[[:alnum:]_]+)*([ =].+)?$' ]]; then
585                if perl "-M$m" -e 1 &> /dev/null; then
586                    aval[ ${#aval[@]} ]="$m"
587                else
588                    echo "skipping $m, unable to load it" >&2
589                fi
590            elif [ -z "${m##*.pm}" ]; then
591                if [ -e "$m" ]; then
592                    e[ ${#e[@]} ]="require '$m';"
593                else
594                    echo "skipping $m, unable to find it" >&2
595                fi
596            else
597                e[ ${#e[@]} ]="$m;"
598            fi
599        done
600
601        echo "will import modules: ${aval[*]}" >&2
602        echo "will execute: ${e[*]} 1" >&2
603        perl -Ilib -I. -d "${aval[@]/#/-M}" "${e[@]/#/-e}" -e1
604    )
605}
606
607## doc: create a bare git clone
608git-bare()
609{
610    ( set +m; _git-bare-bg "$@" )
611}
612
613_git-bare-bg()
614{
615    local url tmpd name
616
617    tmpd=`mktemp -d` || exit 1
618    trap "rm -fr $tmpd" EXIT
619
620    for url; do
621        ## remove /.git
622        name=$(rtrim / "$url")
623        name="${name%.git}"
624        name=$(rtrim / "$name")
625
626        ## remove url, add .git
627        name="${name##*/}.git"
628
629        if [ "$name" = ".git" ]; then
630            echo "coudln't get name for $url"
631            continue
632        fi
633
634        if [ -d "$name" ]; then
635            echo "skipping $name, already exists"
636            continue
637        fi
638        (
639            if { git clone --bare "$url" "$name" &&
640                cd "$name" &&
641                git remote add -f --mirror origin "$url" ||
642                {
643                    git config remote.origin.url "$url" &&
644                    git config remote.origin.fetch "+refs/heads/*:refs/heads/*"
645                }
646            } &> $tmpd/$$; then
647                echo "finished $name"
648            else
649                echo "failed to create $name:"
650                tail -n 3 $tmpd/$$
651            fi
652        ) &
653    done
654    wait
655}
656
657## doc: remove a line from a file: rmline <file> <line> or <file:line>
658rmline()
659{
660    local file line
661
662    file="$1"
663    line="$2"
664
665    if [ -z "$file" ]; then
666        err "missing file to remove"
667        return 1
668    fi
669
670    if [ -z "$line" ]; then
671        line="${file##*:}"
672        file="${file%:*}"
673    fi
674
675    if ! [ -e "$file" ]; then
676        err "file to remove doesn't exist"
677        return 1
678    fi
679
680    if [ -z "$line" ]; then
681        err "missing line to remove"
682        return 1
683    fi
684
685    ed -s "$file" <<-EOF
686        ${line}d
687        wq
688EOF
689}
690
691## doc: show local IP addresses
692ips()
693{
694    (
695        PATH=$PATH:/usr/sbin:/sbin
696        if _exists ip; then
697            ip addr show to 0/0
698        else
699            ifconfig | awk '/^[^ \t]/{i=$1} /inet/{ print i $0}'
700        fi
701    )
702}
703
704## doc: list interfaces belonging to a bridge
705brif()
706{
707    local br="$1"
708    shift
709
710    brctl show | awk -v br=$br '
711        BEGIN { inbr=0 }
712        /^[^ \t]/ { inbr=0 }
713        $1 == br {
714            if ($4) { print $4 }
715            inbr=1
716        }
717        /^[ \t]/ && inbr==1 { print $1 }
718    '
719}
720
721## doc: list content differences between two directories
722dirdiff()
723{
724    local src="$1" dst
725    dst="${2:-.}"
726
727    if [ -z "$src" ]; then
728        err "missing original directory"
729        return 1
730    fi
731
732    if ! [ -d "$src" ]; then
733        err "$src: not a directory"
734        return 1
735    fi
736    if ! [ -d "$dst" ]; then
737        err "$dst: not a directory"
738        return 1
739    fi
740
741    diff -u <(cd "$src" && find . | LC_ALL=C sort | sed -e 's/^..//') \
742        <(cd "$dst" && find . | LC_ALL=C sort | sed -e 's/^..//')
743}
744
745## doc: execute command with path to a binary; path added to the end or where '%' is
746bin()
747{
748    local p a cmd spec
749
750    p=$(type -P "$1" 2>/dev/null)
751    if [ -z "$p" ]; then
752        err "couldn't find $1"
753        return 1
754    fi
755    shift
756
757    if [ $# -eq 0 ]; then
758        echo $p
759        return 0
760    fi
761
762    declare -a cmd
763    spec=
764    for a; do
765        if [ "$a" = "%" ]; then
766            cmd[ ${#cmd[@]} ]="$p"
767            spec=1
768        else
769            cmd[ ${#cmd[@]} ]="$a"
770        fi
771    done
772    [ -z "$spec" ] && cmd[ ${#cmd[@]} ]="$p"
773    eval "${cmd[@]}"
774}
775
776## doc: show nfo
777nfo()
778{
779    local f="$1" a
780
781    if [ -d "$f" ]; then
782        declare -a a
783        a=("$f"/*.[nN][fF][oO])
784        f="$a"
785    fi
786
787    if ! [ -f "$f" ]; then
788        err "couldn't find nfo in $f"
789        return 1
790    fi
791
792    iconv -f cp437 "$f" | tr -d '\r' | sed -e 's/^ *$//' |
793        LESS="${LESS:--}rsS" ${PAGER:-more}
794}
795
796
797## doc: swap the name of two files
798fswap()
799{
800    local t
801
802    if [ $# != 2 ]; then
803        err "need two files: fswap file_1 file_2"
804        return 1
805    fi
806
807    for t; do
808        if ! [ -e "$t" ]; then
809            err "file '$t' doesn't exist"
810            return 1
811        fi
812    done
813
814    t="$1.$$.tmp"
815
816    if [ -e "$t" ]; then
817        err "file with same name of temporary one exists ($t)"
818        return 1
819    fi
820
821    if ! mv -f "$1" "$t"; then
822        err "error renaming first file to temporary name, nothing changed"
823        return 1
824    fi
825
826    # $1 is now $t
827    # todo: $2 -> $1; $t -> $2
828
829    if ! mv -f "$2" "$1"; then
830        if mv -f "$t" "$1" 2>/dev/null; then
831            echo "error renaming second file to first, nothing changed"
832        else
833            echo "error renaming second file to first, and undoing first file"
834            echo "file '$1' is now '$t'"
835        fi
836        return 1
837    fi
838
839    # $1 is $t
840    # $2 is $1
841    # todo: $t -> $2
842
843    if ! mv -f "$t" "$2"; then
844        if mv -f "$1" "$2" 2>/dev/null; then
845            # $2 restored, try $1
846            if mv -f "$t" "$1" 2>/dev/null; then
847                echo "error renaming first file to second, nothing changed"
848            else
849                echo "error renaming first file to second, and undoing first file"
850                echo "file '$1' is now '$t'"
851            fi
852        else
853            echo "error renaming first file to second, and undoing second file"
854            echo "file '$1' is now '$t'"
855            echo "file '$2' is now '$1'"
856        fi
857
858        return 1
859    fi
860}
861

Archive Download this file

Branches:
master