Skip site navigation (1)Skip section navigation (2)
Date:      Thu, 02 Jul 2009 00:07:53 +0300
From:      Giorgos Keramidas <keramida@ceid.upatras.gr>
To:        Wojciech Puchar <wojtek@wojtek.tensor.gdynia.pl>
Cc:        Alexandre Vieira <nullpt@gmail.com>, freebsd-questions@freebsd.org
Subject:   Re: scripting tip needed
Message-ID:  <87k52saz86.fsf@kobe.laptop>
In-Reply-To: <alpine.BSF.2.00.0907012202070.1817@wojtek.tensor.gdynia.pl> (Wojciech Puchar's message of "Wed, 1 Jul 2009 22:02:48 %2B0200 (CEST)")
References:  <755cb9fc0907011040o28b82cdbjd5760b139f797050@mail.gmail.com> <87tz1wqkmu.fsf@kobe.laptop> <alpine.BSF.2.00.0907012202070.1817@wojtek.tensor.gdynia.pl>

next in thread | previous in thread | raw e-mail | index | archive | help
On Wed, 1 Jul 2009 22:02:48 +0200 (CEST), Wojciech Puchar <wojtek@wojtek.tensor.gdynia.pl> wrote:
>> Using an interactive language like Python you can actually *test* the
>> code as you are writing it.  This is a major win most of the time.
>
> could you explain what you mean? You can and you have to test a code on
> any language be it bash, ksh python or C

Yes.  I mean that one can directly interact with the interpret in a REPL
prompt, doing stuff like:

    >>> import re
    >>> devre = re.compile(r'(/dev/\S+)\s+(\S+)\s.*$')
    >>> devre
    <_sre.SRE_Pattern object at 0x28462780>
    >>> devre.match('/dev/ad0s1d 1012974 390512 541426 42% /var')
    <_sre.SRE_Match object at 0x28432e78>
    >>> devre.match('/dev/ad0s1d 1012974 390512 541426 42% /var').groups()
    ('/dev/ad0s1d', '1012974')
    >>> devre =
    >>> re.compile(r'(/dev/\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+).*$')
    >>> devre.match('/dev/ad0s1d 1012974 390512 541426 42% /var').groups()
    ('/dev/ad0s1d', '1012974', '390512', '541426', '42%', '/var')

See how I am 'refining' the initial regular expression without ever
leaving the Python prompt?  That sort of interactivity is entirely lost
when you have to edit a file, save it, switch screen(1) windows or type
^Z to background the editor, run a script, watch it fail and repeat.

Then I can keep testing bits and pieces of code:

    >>> from subprocess import Popen, PIPE
    >>> pipe = Popen(['df', '-k'], shell=False, stdout=PIPE).stdout
    >>> for l in pipe:
    ...     m = devre.match(l)
    ...     if m:
    ...         print "device %s, size %ld KB" % (m.group(1), long(m.group(2)))
    ...
    device /dev/ad0s1a, size 1012974 KB
    device /dev/ad0s1d, size 1012974 KB
    device /dev/ad0s1e, size 2026030 KB
    device /dev/ad0s1f, size 10154158 KB
    device /dev/ad0s1g, size 284455590 KB
    device /dev/md0, size 19566 KB
    >>>

So piping df output to a Python bit of code works!  That's nice.  Then
once I have a 'rough idea' of how I want the script to work, I can
refactor a bit the repetitive bits:

    >>> def devsize(line):
    ...     m = devre.match(line)
    ...     if m:
    ...         return (m.group(1), m.group(2))
    ...
    >>> devsize('/dev/ad0s1d 1012974 390512 541426 42% /var')
    ('/dev/ad0s1d', '1012974')

So here's a short function to return a nice 2-item tuple with two values
(device name, number of 1 KB blocks).  Can we pipe df output through it?

    >>> pipe = Popen(['df', '-k'], shell=False, stdout=PIPE).stdout
    >>> pipe = Popen(['df', '-k'], shell=False, stdout=PIPE).stdout
    >>> map(devsize, pipe.readlines())
    [ None, ('/dev/ad0s1a', '1012974'), None, ('/dev/ad0s1d', '1012974'),
      ('/dev/ad0s1e', '2026030'), ('/dev/ad0s1f', '10154158'),
      ('/dev/ad0s1g', '284455590'), None, None, None, None, None, None,
      None, None, None, None, None, None, None, None,
      ('/dev/md0', '19566'), None]
    >>>

It looks we can do that too, but the tuple list may be more useful if we
trim the null items in the process:

    >>> pipe = Popen(['df', '-k'], shell=False, stdout=PIPE).stdout
    >>> [t for t in map(devsize, pipe.readlines()) if t]
    [ ('/dev/ad0s1a', '1012974'), ('/dev/ad0s1d', '1012974'),
      ('/dev/ad0s1e', '2026030'), ('/dev/ad0s1f', '10154158'),
      ('/dev/ad0s1g', '284455590'), ('/dev/md0', '19566') ]

So there it is.  A nice structure, supported by the core of the
language, using a readable, easy syntax, and listing all the /dev nodes
of my laptop along with their sizes in KBytes.

The entire thing was built 'piece by piece', in the same Python session,
and I now have not only a 'rough idea' of how the code should work, but
also a working copy of the code in my history.

Note the complete *lack* of care about how to append to a list, how to
create dynamic pairs of devicename-size tuples, how to map all elements
of a list through a function, and more importantly the complete and
utter lack of any sort of '"${[]}"' quoting for variable names, values,
nested expansions, and so on.

That's what I am talking about.  Shell scripts are nice, but if we are
not constrained for some reason to use only /bin/sh or ksh, there's no
excuse for wasting hours upon hours to decipher cryptic quoting rules
and exceptional edge-cases of "black quoting magic", just to get a short
job done.  Being able to _easily_ use higher level structures than a
plain 'stream of bytes' is nice :)





Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?87k52saz86.fsf>