[clug] Bash questions...

Kevin Pulo kev at pulo.com.au
Tue May 3 02:48:04 MDT 2011


On Mon, May 02, 2011 at 04:10:26PM +1000, David Deaves wrote:

> There are some very cool things you can do with strings in modern bash, you 
> almost never need to fork to some other more capable program (sed, expr, awk ...)

This is true, and usually great fun, too.

> of interest the old standards basename and dirname are easily re-implemented 
> with the  "${path##*/}" (the single arg usage)  and  "${path%/*}".

And with shopt -s extglob, you can do all sorts of stuff with these
operators.

> To do the more complete 2 arg basename you would need:
> 
> bname()
> {
>   file="${1##*/}"
>   echo "${file%$2}"
> }
> 
> all fork free.

Unfortunately, that will generally cost you 1 fork, because to use it
you typically need to do something like:

    foo="$(bname "$full" "$ext")"

And the shell needs to fork to capture the stdout from bname (since
you're using echo to return the result).

You can see this by checking the final column of /proc/loadavg, which
is the most recent pid handed out by the kernel.  But you need to be
careful that you don't spawn any processes in you attempt to read it.

    $ exec 3< /proc/loadavg ; read -u 3 ; echo $REPLY
    0.01 0.05 0.05 1/463 6246
    $ exec 3< /proc/loadavg ; read -u 3 ; echo $REPLY
    0.01 0.05 0.05 1/463 6246
    $ bname() { file="${1##*/}" ; echo "${file%$2}" ; }
    $ exec 3< /proc/loadavg ; read -u 3 ; echo $REPLY ; bname ; exec 3< /proc/loadavg ; read -u 3 ; echo $REPLY
    0.01 0.05 0.05 1/463 6284
    
    0.01 0.05 0.05 2/463 6284
    $ exec 3< /proc/loadavg ; read -u 3 ; echo $REPLY ; foo=$(bname) ; exec 3< /proc/loadavg ; read -u 3 ; echo $REPLY
    0.01 0.04 0.05 1/463 6373
    0.01 0.04 0.05 2/463 6374
    $ exec 3< /proc/loadavg ; read -u 3 ; echo $REPLY ; foo=$(bname) ; exec 3< /proc/loadavg ; read -u 3 ; echo $REPLY
    0.01 0.04 0.05 1/463 6385
    0.01 0.04 0.05 2/463 6386
    $ exec 3< /proc/loadavg ; read -u 3 ; echo $REPLY ; foo=$(bname) ; exec 3< /proc/loadavg ; read -u 3 ; echo $REPLY
    0.01 0.04 0.05 1/463 6386
    0.01 0.04 0.05 2/463 6387
    $

Thankfully 1 fork() isn't a very big deal these days.  But this can be
very annoying when the function in question has side-effects, eg. it
sets the value of some other (global) variable, because it'll happen
in the sub-process, and you'll never see it in the "main" program.

To get around this (when it does matter), I tend to do things in a
tcl-ish kind of way, passing in the name of the variable to put the
result into, eg:

    $ function bname { local file="${2##*/}" ; eval "$1="'${file%$3}' ; }
    $ exec 3< /proc/loadavg ; read -u 3 ; echo $REPLY ; bname result /foo/bar/baz.txt .txt ; echo $result ; exec 3< /proc/loadavg ; read -u 3 ; echo $REPLY
    0.00 0.04 0.05 1/462 11553
    baz
    0.00 0.04 0.05 1/462 11553
    $ 

Which is more annoying to code, and less readable.

And, a point of nitpicking, $file should be declared local.  :)

Kev

-- 
Kevin Pulo
kev at pulo.com.au
http://www.kev.pulo.com.au/
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 189 bytes
Desc: not available
URL: <http://lists.samba.org/pipermail/linux/attachments/20110503/782ad391/attachment.pgp>


More information about the linux mailing list