MCWong
Blah! MCWong

20040714 Wednesday July 14, 2004

ksh scripting #4 : list elements v7 and v8

Alan offered cool advise on ksh scripting #3 with pl.sh using "IFS" and "set --".

... and I dig!

So I adapted pl.sh into a shell function. My initial concerns that IFS is not taken in local context of a function was valid afterall. IFS is global and most certainly not just another variable. It got confusing when echo $IFS doesn't show anything and I thought it's spaces. set | grep IFS shows differently. It got even more mysterious when the function was invoked again but produce different result. Only after a bunch of almighty debug printf (ok I used echo) that it became clear IFS was causing my confusion. Just to make matter more difficult for myself I tried to be fancy and do set -- $@ directly. That certainly doesn't work. The moment IFS is set, substitution for $@ no longer shows the char that's IFS but instead, space is substituted!

Here's pl7() which adapted the set -- trick that works:


#
#  pl7 - print list elements delimited by '|'
#
function pl7 {
  l=$@
  IFS='|'
  set -- $l
  for e
  do
    echo $e
  done
  unset IFS
}

And to add some flexibility, I can cater to user specified delimiters which would have been otherwise messy to do in pl version 1:


#
#  pl8 - print list elements, $1 is delimiter
#
function pl8
{
  d=$1
  shift
  l=$@
  IFS=$d
  set -- $l
  for e
  do
    echo $e
  done
  unset IFS
}


Give it a go:


$ pl7 "lp|x|71|8|Line Printer Admin|/usr/spool/lp|"
lp
x
71
8
Line Printer Admin
/usr/spool/lp
xxx


$ pl8 ":" lp:x:71:8:Line Printer Admin:/usr/spool/lp:
lp
x
71
8
Line Printer Admin
/usr/spool/lp
xxx
$ 

I guess my intention was clear, in that I'm trying to let ksh do all the work whenever I can avoid a process invocation. Otherwise, it's really much easier to just awk it.


$ head -1 /etc/passwd | nawk -F':' '{while (++i<=NF) print $i}'

 or a bit more readable

$ head -1 /etc/passwd | nawk -F':' '{for (i=1;i<=NF;i++) print $i}' 

PS: Okay why version 7 and 8? it took me that no. of iteration to figure out IFS. ;)

ksh scripting flashback: #1 #2 #3

(2004-07-14 00:00:04.0) Permalink Comments [3]

Comments:

What about:
$ head -1 /etc/passwd | tr ":" "\n" 
or 

$ head -1 /etc/passwd | awk 'BEGIN{RS=":"}{print}'

interestingly, it seems that /usr/bin/tr is not that slow:

$wc -l my.passwd
    5000 my.passwd

$ time cat my.passwd | tr ":" "\n" > /dev/null                                     

real    0m0.02s
user    0m0.02s
sys     0m0.01s
$ time awk 'BEGIN { RS =  ":"}{print}' my.passwd > /dev/null                       

real    0m0.16s
user    0m0.16s
sys     0m0.00s
$ time nawk -F':' '{for (i=1;i<=NF;i++) print $i}' my.passwd > /dev/null           

real    0m0.07s
user    0m0.06s
sys     0m0.00s

Posted by antoon on July 22, 2004 at 06:11 AM PDT #

for your IFS troubles: it's plain evil to unset IFS (at least in in bash and ash -- so ksh will most likely not differ on this). the clean way is to save IFS and restore it later:
  OIFS=$IFS
  [ do your special IFS-stuff here ]
  IFS=$OIFS
set -- $l: should be unnecessary as
  for e in $l; do echo $e; done
does exactly the same ;)

Posted by tom on August 27, 2004 at 05:04 PM PDT #

I never like messing with IFS so this one will do it:

function pl_cjg
{
        typeset line=$2
        while [[ ${line%%${1:-:}*} != ${line} ]]
        do
                echo ${line%%${1:-:}*}
                line=${line#*${1:-:}}
        done
        echo ${line}
}

Though for parsing whole file this is better

function pl_file
{
        typeset line
        while read line
        do
                while [[ ${line%%${1:-:}*} != ${line} ]]
                do
                        echo ${line%%${1:-:}*}
                        line=${line#*${1:-:}}
                done
                echo ${line}
        done

Posted by Chris Gerhard on October 21, 2004 at 07:33 AM PDT #

Post a Comment:

Comments are closed for this entry.

archives
links
referers