New shell gimmicks

So, I decided to streamline my shell config the other day. The very first I did, was to write an awk replacement in perl. Sounds a little senceless, but sometimes I need some more power inside an awk command and sometimes I just want to save typing. This is how it looks like, when used:

# awk version:
ps | grep sleep | grep -v grep | awk '{print $1}' | xargs kill
# pwk version:
ps | grep sleep | grep -v grep | pwk 1 | xargs kill

This is the simple variant, which just saves typing, pretty handy. The other variant is more perlish and at first looks like the original awk syntax. Hover, you can add almost any kind of perl code to it:

ps | pwk 'if($5 =~ /^python$/) { $t=`fetch -o - "http://$8/"`; if($t =~ /<title>(.+?)<\/title/) { print "$8: $1"} }'

Here's the shell function, just put it into your .bashrc:

pwk () {
    if test -z "$*"; then
        echo "Perl awk. Usage:"
        echo "Perlish: pawk [-F/regex/] [-Mmodule] <perl code>"
        echo " Simple: pawk <1,2,n | 1..n>"
        echo "Perlish helpers:"
        echo "  p() - print field[s], eg: p(\$1,\$4,\$7)"
        echo "  d() - dump variables, without param, dump all"
        echo "  e() - exec code on all fields, eg: e('s/foo/bar/')" 
        echo
        echo "Default loaded modules: Data::Dumper, IO::All"
        echo "Enable \$PWKDEBUG for debugging"
        echo "Simple mode has no helpers or anything else"
    else
        # determin pwk mode
        if echo "$*" | egrep '^[0-9,\.]*$' > /dev/null 2>&1; then
            # simple mode
            code=`echo "$*" | perl -pe 's/([0-9]+?)/\$x=\$1-1;\$x/ge'`      
            perl -lane "print join(' ', @F[$code]);"
        else
            # perl mode
            # prepare some handy subs
            uselib="use lib qw(.);"
            subprint="sub p{print \"@_\";};"
            subsed='sub e{$r=shift; foreach (@F) { eval $r; }};'
            subdump='sub d {$x=shift||{_=>$_,S=>\@F}; print Dumper($x);};'
            begin=";  BEGIN { $uselib $stdsplit $subprint $subdump $subsed}; "
            
            # extract the code and eventual perl parameters, if any
            code=""
            args=""
            last=""
            for arg in "$@"; do
                args="$args $last"
                last="$arg"
            done
            code=$last
            
            # fix perl -F /reg/ bug, complains about file /reg/ not found,
            # so remove the space after -F
            args=`echo "$args" | sed -e 's/-F /-F/' -e 's/-M /-M/'`
            
            # convert $1..n to $F[0..n]
            code=`echo "$code" | perl -pe 's/\\\$([0-9]+?)/\$x=\$1-1;"\\\$F[\$x]"/ge'`
            
            # rumble
            defaultmodules="-MData::Dumper"
            if perl -MIO::All -e0 > /dev/null 2>&1; then
                defaultmodules="$defaultmodules -MIO::All"
            fi
            
            if test -n "$PWKDEBUG"; then
                set -x
            fi
            perl $defaultmodules $args -lane "$code$begin"
            if test -n "$PWKDEBUG"; then
                set +x
            fi
        fi
    fi
}

Another new shell function is extr, which unpacks any kind of archive. In contrast to its sisters out there (there are a couple of generic unpack shell funcs to be found on the net), it takes much more care about what it does. Error checking, you know. And it looks inside the archive to check if it extracts into its own directory, which is not always the case and very annoying. In such instances it generates a directoryname from the archivename and extracts it to there. Usage is simple: extr archivefile. Here's the function:

extr () {
    act() {
        echo "$@"
        "$@"
    }
    n2dir() {
        tarball="$1"
        suffix="$2"
        dir=`echo "$tarball" | perl -pne "s/.$suffix//i"`
        dir=`basename "$dir"`
        echo "$dir"
    }
    tarball="$1"
    if test -n "$tarball"; then
        if test -e "$tarball"; then
            if echo "$tarball" | grep -Ei '(.tar|.jar|.tgz|.tar.gz|.tar.Z|.tar.bz2|tbz)$' > /dev/null 2>&1; then
                # tarball
                if echo "$tarball" | grep -E '.(tar|jar)$' > /dev/null 2>&1; then
                    # plain old tarball
                    extr=""
                elif echo "$tarball" | grep -E '(bz2|tbz)$' > /dev/null 2>&1; then
                    extr="j"
                elif echo "$tarball" | grep -E 'Z$' > /dev/null 2>&1; then
                    extr="Z"
                else
                    extr="z"
                fi

                if ! tar ${extr}tf "$tarball" | cut -d/ -f1 | sort -u | wc -l
                    | egrep ' 1$' > /dev/null 2>&1; then
                    # does not extract into own directory
                    dir=`n2dir "$tarball" "(tar.gz|tgz|tar.bz2|tbz|tar|jar|tar.z)"`
                    mkdir -p $dir
                    extr="-C $dir -${extr}"
                fi
                act tar ${extr}vxf $tarball
            elif echo $tarball | grep -Ei '.zip$' > /dev/null 2>&1; then
                # zip file
                if unzip -l "$tarball" | grep [0-9] | awk '{print $4}' | cut -d/ -f1 | sort -u \
                    | wc -l | egrep ' 1$' /dev/null 2>&1; then
                    # does not extract into own directory
                    dir=`n2dir "$tarball" zip`
                    act mkdir -p $dir
                    opt="-d $dir"
                fi
                act unzip ${opt} $tarball
            elif echo "$tarball" | grep -Ei '.rar$' > /dev/null 2>&1; then
                if ! unrar vt "$tarball" | tail -5 | grep '.D...' > /dev/null 2>&1; then
                    # does not extract into own directory
                    dir=`n2dir "$tarball" rar`
                    act mkdir -p "$dir"
                    (cd "$dir"; act unrar x -e $tarball)
                else
                    act unrar x $tarball
                fi
            elif echo "$tarball" | grep -Ei '.gz$' > /dev/null 2>&1; then
                # pure gzip file
                act gunzip "$tarball"
            else
                :
            fi
        else
            echo "$tarball does not exist!"
        fi
    else
        echo "Usage: untar <tarball>"
    fi
}

And finally an updated version of my h function, which can be used for dns resolving. Usage is pretty simple:

% h theoatmeal.com
; dig +nocmd +noall +answer theoatmeal.com
theoatmeal.com.         346     IN      A       208.70.160.53

% h 208.70.160.53
; dig -x 208.70.160.53 +short
oatvip.gpdatacenter.com.

% h theoatmeal.com mx
; dig +nocmd +noall +answer theoatmeal.com mx
theoatmeal.com.         1800    IN      MX      5 eforwardct2.name-services.com.
theoatmeal.com.         1800    IN      MX      5 eforwardct3.name-services.com.
theoatmeal.com.         1800    IN      MX      5 eforwardct.name-services.com.

It uses dig to do the work, or host if dig cannot be found. The source:

h () {
    if type dig > /dev/null 2>&1; then
        args="$*"
        opt="+nocmd +noall +answer"
        rev=""
        if echo "$args" | egrep '^[0-9\.:]*$' > /dev/null 2>&1; then
            # ip address
            cmd="dig -x $* +short"
        else
            # hostname
            cmd="dig +nocmd +noall +answer $*"
        fi
        echo "; $cmd"
        $cmd
    else
        # no dig installed, use host instead
        host="$1"
        type="a"
        debug=""
        cmd="host $debug"
        if test -z "$host"; then
            echo "Usage: h <host> [<querytype>]"
            return
        else
            if test -n "$2"; then
                type=$2
            fi
            if test -n "$debug"; then
                set -x
            fi
            case $type in
                ls)
                    $cmd -l $host
                    ;;
                any)
                    cmd=`echo $cmd | sed 's/\-d//'`
                    $cmd -d -t any $host | grep -v ^\; | grep -v "^rcode ="
                    ;;
                mx|a|ns|soa|cname|ptr)
                    $cmd -t $type $host
                    ;;
                *)
                    echo "*** unsupported query type: $type!"
                    echo "*** allowed: mx, a, ns, any, *, soa, cname, ptr"
                    continue
                    ;;
            esac
            if test -n "$debug"; then
                set +x
            fi
        fi
    fi
}

03.07.2014 20:07 CC0 opensource software Source






subst update (1.1.3)

So, after a couple of idle years I made an update to my subst script. Although I use it everyday there were still some glitches here and there. For one, I just could not rename files with spaces in them. Very annoying. Also it was unflexible in that I could not use additional perlmodules when using /e. STDIN was not supported among other minor stuff.

So, the new version fixes all this, see the link above. Download, rename it (remove the .txt extension) and put it into your bin directory. Usage:

Usage: subst  [-M <perl module>] [-t] -r 's/old/new/<flags>' [ -r '...', ...] [<file> ...     | /regex/]
       subst  [-M <perl module>] [-t] -m 's/old/new/<flags>' [ -m '...', ...] [<file|dir> ... | /regex/]

Options:
 -r        replace contents of file(s)
 -m        rename file(s)
 -M        load additional perl module to enhance /e functionality.
 -t        test mode, do not overwrite file(s)

Samples:
 - replace "tom" with "mac" in all *.txt files:
   subst -r 's/tom/mac/g' *.txt

 - rename all jpg files containing whitespaces:
   subst -m 's/ /_/g' '/.jpg/'

 - decode base64 encoded contents
   subst -M MIME::Base64 -r 's/([a-zA-Z0-9]*)$/decode_base64($1)/gem' somefile

 - turn every uri into a link
   subst -M "Regexp::Common qw /URI/" -r 's#($RE{URI}{HTTP})#<a href="$a">link</a>#g' somefile

If <file> is -, STDIN will be used as input file, results will be printed
to STDOUT. -t does not apply for STDIN input.

Substitution regex must be perlish. See 'perldoc perlre' for details.

Version: 1.1.3. Copyright (c) 2002-2014 - T.v.Dein <tom AT linden DOT at>

So, in order to remove spaces of filenames, I can now just issue:

subst -m 's/ /_/g' '/\.mp3$/'

As you can see, instead of giving a shell wildcard as last argument, I provide a regex, which will be resolved by the script itself from the current directory. Bam!


01.07.2014 19:08 CC0 opensource software Source






Die ersten Wildstiefmütterchen

Frauchen baut ja jedes Jahr massenweise Grünzeug als Futtermittel an. Dieses Jahr ist das hier die erste Blüte ihrer Aussaat:

Bild: Blüten wilder Stiefmütterchen
Blüten wilder Stiefmütterchen (May 18, 2014, 4:17 p.m.)
[Tags: foto ] [Album: Natur ]

18.05.2014 16:16 CC0 foto Fotografie






ipv4.l.google.com

Ohne Worte:

host -t aaaa ipv4.l.google.com
ipv4.l.google.com has IPv6 address 2a00:1450:4019:800::1003

18.05.2014 10:57 CC0 fun google netz Geschwätz






Küche Abzugeben

Kostenlos:


06.05.2014 20:43 CC0 foto Fotografie






Springer. Ohne Worte

Man beachte diese beiden Heisemeldungen:

  1. 05.05.2014 20:47: Springer: Google, Facebook & Co. "wollen uns Verlage vernichten"

  2. 06.05.2014 08:23: Axel Springer erwirtschaftet erstmals mehr als die Hälfte seines Umsatzes digital

Offensichtlich setzen die zu viele unbezahlte Praktikanten in ihrer PR-Abteilung ein. Demagogengesindel.


06.05.2014 20:15 CC0 idioten kritik politik Gesellschaft






Der Vorteil von Fuzzytests

Ich habe am Wochenende angefangen, die PCP Library (libpcp) threadsafe zu machen. Zu diesem Zweck habe ich ein Context-Objekt eingeführt, in dem vormals globale Variablen abgelegt werden (Fehler und Schlüssellisten). Jetzt habe ich nach der Änderung (die tiefgreifend ist) das erste Mal die Unittests durchlaufen lassen und einer der Tests ist fehlgeschlagen:

  executing prepare command: while :; do cp testfuzzP.orig testfuzzP.pub; ./mangle testfuzzP.pub; 
                             if ! diff testfuzzP.* > /dev/null 2>&1; then break; fi; done

echo no | ../src/pcp1 -V vf -K -I testfuzzP.pub -x a
      ok 81 - check-fuzz-binary-pubkey-loop-0
# Abnormal program termination
Core was generated by `pcp1'.
Program terminated with signal 6, Aborted.
#0  0x0000000800db9d6c in kill () from /lib/libc.so.7
#0  0x0000000800db9d6c in kill () from /lib/libc.so.7
#1  0x0000000800db899b in abort () from /lib/libc.so.7
#2  0x0000000000407f8c in final (fmt=Variable "fmt" is not available.
) at context.c:102
#3  0x00000000004100f2 in buffer_get_chunk (b=0x80149b100, buf=0x80149c080, len=3932164) at buffer.c:132
#4  0x00000000004122d3 in _check_sigsubs (blob=0x80149b100, p=0x801407c00, subheader=0x8014a7060) at mgmt.c:103
#5  0x00000000004126bf in pcp_import_pub_rfc (ptx=0x801417040, blob=0x80149b100) at mgmt.c:265
#6  0x0000000000403e34 in pcp_import (vault=0x80141a060, in=0x8010099d0, passwd=0x801419104 "a") at keymgmt.c:577
#7  0x0000000000402df2 in main (argc=0, argv=0x7fffffffd170) at pcp.c:440
Last failed check: check-fuzz
1..81
To run a single test only, type: 'make test CHECK=testname'

Ich hab das Problem jetzt noch nicht näher untersucht. Spannend ist aber, dass es der Fuzzytest war, der hier fehlgeschlagen ist (hat wahrscheinlich nichts mit der Contextänderung zu tun). Bei einem Fuzzytest wird randomisiertes Input erzeugt und das zu testende Programm damit gefüttert. Idealerweise sollte es mit solchem Zeug klarkommen. In diesem Fall ist pcp1 aber gestorben.

Noch wesentlich interessanter ist warum. Und zwar habe ich - gleichzeitig - in der Bufferklasse eine Änderung gemacht, bei der potentielle Bufferoverflows, die dort abgefangen werden, nicht via fatal() über den normalen Programmfluß reportet werden, sondern ich habe an der Stelle ein abort() eingebaut. Und an der Stelle ist dieser Fuzzytest hier gescheitert. Man kann das besser erkennen, wenn man den Test manuell ausführt:

echo no | ../src/pcp1 -V vf -K -I testfuzzP.pub -x a
ABORT: [buffer importblob] attempt to read 3932164 bytes data from buffer with 204 bytes left at offset 113
Abort trap: 6 (Speicherabzug geschrieben)

Die Fehlermeldung wird in libpcp/buffer.c erzeugt. Woher das kommt, ist mir im Moment zwar noch nicht ganz klar. Es sieht verdächtig nach Integer Overflow aus: in der zu importierenden Schlüsseldatei gibt es normalerweise Felder für Email, Name und dergleichen (Notations, siehe RFC 4880). In der Notation ist die Länge des Strings angegeben und durch das kaputte Inputfile (das wird mit mangle.c, oben verlinkt, erzeugt) steht an der Stelle wahrscheinlich eine ziemlich grosse Zahl. Und offensichtlich prüft meine Importroutine nicht, ob diese Zahlenangabe Sinn macht. Aber das spielt an der Stelle erstmal keine Rolle. Der Fehler zeigt jedenfalls eindrucksvoll, wie wertvoll und nützlich solche Fuzzytests sind. Zum Glück hab ich die eingebaut! Wer weiss, ob ich den Fehler andernfalls je gefunden hätte. Uff.

Fies, aber schön :)

Der Vollständigkeit halber: in diesem Commit kann man ab Zeile 73 die Änderungen sehen, mit denen ich das Problem behoben habe.


05.05.2014 19:57 CC0 opensource pcp security Source






Luftfeuchtigkeit Australien - Bartagamen

Da wir hier einige Diskussionen über die richtige Luftfeuchtigkeit bei der Bartagamenhaltung hatten, habe ich mal das Verbreitungsgebiet der Tiere mit tatsächlichen Meßwerten korreliert. Das Ergebnis ist klar: ausserhalb der Winterzeit (wo die Tiere sowieso in Winterruhe sind) sind tagsüber 20-30% relative Luftfeuchte in Ordnung.

Quelle Klimadaten: Australian Government, Bureau of Meteorology.

Quelle Pogona Vitticeps Verbreitungsgebiet: Wikipedia.

Bild: Luftfeuchtigkeit Australien im Winter, vormittags
Luftfeuchtigkeit Australien im Winter, vormittags (May 1, 2014, 7:37 p.m.)
[Tags: barties ] [Album: Terrarium ]
Bild: Luftfeuchtigkeit Australien im Winter, nachmittags
Luftfeuchtigkeit Australien im Winter, nachmittags (May 1, 2014, 7:37 p.m.)
[Tags: barties ] [Album: Terrarium ]
Bild: Luftfeuchtigkeit Australien im Sommer, vormittags
Luftfeuchtigkeit Australien im Sommer, vormittags (May 1, 2014, 7:38 p.m.)
[Tags: barties ] [Album: Terrarium ]
Bild: Luftfeuchtigkeit Australien im Sommer, nachmittags
Luftfeuchtigkeit Australien im Sommer, nachmittags (May 1, 2014, 7:38 p.m.)
[Tags: barties ] [Album: Terrarium ]

01.05.2014 19:35 CC0 barties science terrarium Terrarium






Besser spät als nie :)

Hach, das ist ja mal eine kuriose Nummer, dieser Bug. Aber von vorn: gestern habe ich (in der Arbeit) wegen einem Perlproblem herumgegoogelt und eines der Suchergebnisse war ein Posting bei Perlmonks wegen einem Config::General Problem. In dem Posting hatte jemand Schwierigkeiten, ein Makefile mit meinem Modul zu parsen, in dem sich "line continuations" befanden. Die entsprechende Stelle in dem Makefile sah so aus:

THIS_BREAKS     = \

MULTILINE       = \
                Foo     \
                Bar     \
                Baz

Config::General hat dann nach dem Parsen das hier ausgespuckt:

THIS_BREAKS  => "MULTILINE\t= Foo\tBar\tBaz",

was der Fragesteller als Fehler betrachtete. Peter Jaquiery hat dann für mein Modul einen Bug eröffnet: 39814. In der Fehlerbeschreibung hat er aber von dem oben beschriebenen Problem nichts erwähnt, sondern nur gemeint, ein chop() Aufruf sei falsch. Das war aber Absicht, also hab ich den Bug geschlossen (rejected).

Das war vor 6 Jahren!

Nun bin ich also gestern über dieses Perlmonks Posting gestolpert, bei dem ganz unten zu jenem Bug verlinkt war. Natürlich erschien das Problem nun in einem völlig anderen Licht. Der Bug war sogar unerwarteterweise recht einfach zu beheben, was ich soeben gemacht habe.

Odd ist wirklich die passende Beschreibung für den Vorgang. 6 Jahre! Meine Güte :)

Nun ist also die Version 2.54 aktuell.








Erstes Beckenfoto mit neuen Bewohnern

Wie bereits neulich berichtet, habe ich neue Bewohner: Kardinalfische. Die mag ich richtig gerne. Springen nicht, erschrecken sich nicht, sie sind sogar recht zutraulich. Herrlich. Hier also das übliche Beckenupdatefoto, diesmal mit Kardinalfischen.

Bild: Becken vom 27.04.2014
Becken vom 27.04.2014 (April 27, 2014, 6:39 p.m.)
[Tags: aquarium2013 ] [Album: Aquarium ]

27.04.2014 18:35 CC0 aquarium2013 Aquarium