Tuesday, December 7, 2010

Solaris AWK still broken after all these years


It's amazing how time flies. I've got the above book sitting on my desk at work. It's the only book on my desk at work. Its probably my favorite computer book for it's terse clear style and simple power. I've keep it for about 20 years, and 20 years later, I'd expect any bugs in basic code examples to be worked out. For the most part on most platforms this is true, but not Solaris.

AWK is an awesome utility always available on UNIX and makes a perfect tool to for scripts that will run anywhere with no need for dependencies nor if's, and's or but's. For that reason it's all the more dissapointing that the default awk on Solaris is broken for syntaxes that were valid over 20 years ago. On the default awk on Solaris the following are broken
  1. awk -vVAR=value
  2. if ( $0 ~ "string" )
  3. gsub , sub
Probably the most useful thing to know is for point 1, passing in a shell variable, you can do it by setting a variable in the shell such as "VAR=10" and accessing it in the awk script with:
 VAR="'"$VAR"'"
Point 2, the if construct can be gotten around a bit less elegantly  with
 /string/   { ...
Point 3, gsub and sub are broken is just plain annoying and it takes writing your own routines with index and substr.
Which was too bad for me. I wanted to write a script to eliminate more than 2 redo logs from database creation for "scratch" databases. I don't know if you can imagine a create script like:

STARTUP NOMOUNT pfile='/mnt/provision/redo2a/datafile/initredo2a.ora.provision'
CREATE CONTROLFILE REUSE SET DATABASE "redo2a" RESETLOGS ARCHIVELOG
MAXLOGFILES $MAXLOGFILES
MAXLOGMEMBERS $MAXLOGMEMBERS
MAXDATAFILES $MAXDATAFILES
MAXINSTANCES $MAXINSTANCES
MAXLOGHISTORY $MAXLOGHISTORY
LOGFILE
GROUP 1(
'/mnt/provision/redo2a/datafile/opt/oracle/oradata/homer/redo01.log',
'/mnt/provision/redo2a/datafile/opt/oracle/oradata/homer/redo01a.log'
) SIZE 52428800,
GROUP 2(
'/mnt/provision/redo2a/datafile/opt/oracle/oradata/homer/redo02.log',
'/mnt/provision/redo2a/datafile/opt/oracle/oradata/homer/redo02a.log'
) SIZE 52428800,
GROUP 3(
'/mnt/provision/redo2a/datafile/opt/oracle/oradata/homer/redo03.log'
) SIZE 52428800
DATAFILE
'/mnt/provision/redo2a/datafile/opt/oracle/oradata/homer/system01.dbf',
'/mnt/provision/redo2a/datafile/opt/oracle/oradata/homer/undotbs01.dbf',
'/mnt/provision/redo2a/datafile/opt/oracle/oradata/homer/sysaux01.dbf',
'/mnt/provision/redo2a/datafile/opt/oracle/oradata/homer/users01.dbf',
'/mnt/provision/redo2a/datafile/opt/oracle/oradata/homer/t1.dbf'
$CHARACTER_SET
;

I just wanted 2 redo logs and one member for group, so I wanted to pull out all that's highlighted in orange. Simple with AWK:

cat ${MNTDIR}/doCreateControlFile.sh | \
awk -vNREDO="$NREDO" '
BEGIN {
output="on"
members="off"
nlogs=0
last="no"
}
{
# members
if ( $0 ~ "GROUP" ) { members="on" ; nlogs=0}
# output
if ( $0 ~ NREDO ) { last="yes" }
if ( $0 ~ "DATAFILE" ) { output="on" }
if ( $0 ~ "SIZE" ) { members="off" ; nlogs=0}
if ( output == "on" ) {
nlogs++
gsub(/,/,"")
}
if ( nlogs < last ="=" last = "no" output="off"> /tmp/redo2.$$

The script works on AIX, HPUX, Redhat but not on Solaris. For Solaris I had a few choices. I could try and use nawk or gawk which would require me using some sort of "ifdef" and even then using nawk would require some changes. AFAIK gawk would have to be installed on the machines, thus ruling it out. For me, I wanted the safest option. The safest option for me was just to re-write the code in a way that worked around the bugs on Solaris default awk.

cat ${MNTDIR}/doCreateControlFile.sh | \
awk '
BEGIN {
output="on"
members="off"
nlogs=0
last="no"
size="no"
NREDO="'"$NREDO"'"
}
/DATAFILE/ { output="on" } # turn output back on
/SIZE/ { members="off" ; nlogs=0; size="yes"} # end of group members
/GROUP/ { members="on" ; nlogs=-1 } # start counting group members
{ if ( index($0,NREDO) ) last="yes" } # reach the maximum of redo logs to recreate
{
if ( output == "on" ) {
str=$0
if ( members == "on" ) { # we are in the log member section
nlogs++ # count members
i=index(str,","); # get rid of comma at end of line
if ( i > 0 ) {
str=substr(str,0,i-1)
}
}
if ( nlogs < size ="=" last ="=" str="$0" i="index(str,"> 0 ) {
str=substr(str,0,i-1)
}
last = "no"
output="off" # we reach max # of redo logs, turn output off till we see DATAFILE
}
}
print str
}
}
}
{ size = "no" }
' > /tmp/redo2.$$

No comments:

Post a Comment