Saturday, March 28, 2009

unix scripting

"comment out" code blocks
One line of shell code can be "commented out" using the
"#" character. Sometimes however it would be nice to "comment out"
more than one line of code, like the C "/* */" comments.

One way to comment out multiple lines is this:
: '
,,,,,,
'

After the ":" command (that returns "true") the rest of the code
is a large string constant enclosed within 'single quotes'.

Of course this works only if the code "commented-out" does
not contain single quotes.

"comment out" code blocks (2)
Another way to comment out multiple lines of code is the "here document"
way:

: << --END-COMMENT--
your comment here
--END-COMMENT--

This way, there is no restriction with single quotes. Note that any delimiter
can be used in place of "--END-COMMENT--".

(tested with bash 2.05a)

"comment out" code blocks (3)
(tested in bash 3 only)

: << --END-COMMENT--
your comment here
--END-COMMENT--

could cause problem if `somecommand` or $(somecommand) is used.

: << --END-COMMENT--
`touch /tmp/foo`
foo bar
--END-COMMENT--

instead

: << '--END-COMMENT--'
`touch /tmp/foo`
foo bar
--END-COMMENT--


Additionally : can use also not-interpreted commands, e.g.

: INGORED UP TO << '--END-COMMENT--'
`touch /tmp/foo`
foo bar
--END-COMMENT--

cleaning up tmp files
I've seen too many scripts using massive number of tmp files, which
is wrong in its self.
But not only that, people tend to clean them up one at a time in a fashion
such as

if [ -a ${tmpFile} ]; then
rm ${tmpFile};
fi

This, when you use up  to 10 or even 5 tmp files gets nasty. A quicker way
of cleaning
up such tmp files is to use a simple loop, I even perfer to use array's
which are
availible in Korn shell. Here is an example.

clean()
{
tmpfiles[0]=${temp1}
tmpfiles[1]=${temp2}

for file in ${tmpfiles[*]}
do
if [ -a ${file} ]; then
rm ${file}
fi
done

This way, as you accumulate more and more tmp files, you need only to add
one line to get it cleaned up.


cleaning up tmp files (2)
Another way to clean up multiple temporary
files is to create them within a subdirectory, i.e.

TmpBase=${TMPDIR:=/tmp}/myscript.$$
mkdir "$TmpBase" || exit 1 # create directory
chmod 700 "$TmpBase" || exit 1 # restrict access

# Remove all temporary files after program termination
# or at receiption of a signal:
trap 'rm -rf "$TmpBase" >/dev/null 2>&1' 0
trap "exit 2" 1 2 3 15

# The following files will be remove automatically:
input=$TmpBase/input
output=$TmpBase/output
#...



Convert "relative" in "absolute" path name
In shell scripts it is often necessary to convert a
relative path name, i.e. "../usr/../lib/somefile" to
an absolute path name starting with a slash "/", i.e.
"/lib/somefile". The following code fragment does exactly this:

D=`dirname "$relpath"`
B=`basename "$relpath"`
abspath="`cd \"$D\" 2>/dev/null && pwd || echo \"$D\"`/$B"

Positioning the cursor from within shell scripts
[This tip was first published within the SHELLdorado Newsletter 1/99]

For some shell scripts it would be desirable, if the script
could position the cursor to arbitrary (row, column) pairs
(i.e. to display a status line, ...)

The following shell function uses the "tput" command to
move the cursor to the specified (row, column) position:

# move cursor to row $1, col $2 (both starting with zero)
# usage: writeyx message rowno colno
writeyx () {
tput cup $2 $3
echo "$1"
}

Example usage:

clear           # clear the screen
writeyx "This is a centered message" 11 26
writeyx "press any key to continue..." 22 0
read dummy

The "tput" comm!
and looks up the escape command sequence for
a feature needed for the current terminal. You can use it
for other terminal related things, too:

tput smso               # "start mode shift out": usually
# reverse
echo "This is printed reverse"
tput rmso               # "reset mode shift out"

All available capability names are listed on the terminfo(5)
manual page.

Portability:
The "tput" command is available with the "terminfo"
terminal information database


Setting default values for variables
In shell scripts it's often useful to
provide default values for script variables, i.e.


if [ -z "$Host" ]
then
Host=`uname -n`
fi


For this kind of assignment the shell
has a shorthand:


: ${Host:=`uname -n`}


This means: if the variable "Host" is
not already set, execute the command
"uname -n" and set the variable to
the returned value.

Getting a file into "memory"
Sometimes its convienient to have a file
read into memory to work on it.  The
form that you take to accomplish this is
an array data structure.  In ksh88 the
maximum is 1024 elements, however, on
some of the more modern versions you can
go much higher.

To do this the following can be done:

#/usr/bin/ksh
typeset -i cnt=0

while read line
do
myarray[$cnt]=$line
((cnt = cnt + 1))
done < myfile
# end of file---------

Now, if I want to access any line of that
file, I simply use:

${[]}

echo ${myarray[4]}

This is useful for parsing, or for interactive
use of of the file's contents.  I have
all !
of the lines of the file available in
the array, and I can move around in them,
select the ones I want.

Look at the following example:

#!/usr/bin/ksh

typeset -i cnt=0

while read line
do
myarray[$cnt]=$line
((cnt = cnt + 1))
done < myfile

PS3="Select a number: "
select linefromfile in ${myarray[@]}
do
echo $linefromfile
done
# end of file------------

There are many other uses for this techique.
Dynamic menus
numeric error message reference
getting mulitple specific lines of a file
in a single pass

Have fun.

Find user's name
The full name of each user is available in the /etc/passwd file. If you
would like to use the full name in your script instead of $LOGNAME,
which simply returns the user's login name, you can declare the following
variable in your script:

fullname=`grep $LOGNAME /etc/passwd | cut -f 5 -d :`

If you only want the first name, you would declare this variable:

firstname=`grep $LOGNAME /etc/passwd | cut -f 5 -d : | cut -f 1 -d " "`




Find user's name (2)
Since the full name is the 5th column
of the file /etc/passwd, it's easy
to look up the full name for a
login name like "joe":

awk -F: '$1 == name {print $5}' name=joe /etc/passwd

The option "-F" tells awk to use ":" als field
separator (instead of whitespace).


Using "here-documents" instead of multiple "echo"
Multiple "echo" commands may be replaced by a "here-document".
This makes the script faster and easier to read.

Example:


echo "Please enter your choice:"
echo "1 - list current directory"
echo "2 - list current users"
echo "3 - log off"


may be replaced with


cat <

Using "here-documents" instead of multiple "echo" (2)
# you can also turn multi-echos into a single echo

echo "
Welcome to Foo.Bar v0.8
=======================
Press enter to continue...
";

To find idle users
w | gawk '
BEGIN { FIELDWIDTHS = "9 11 13 10 8 7 7 14" }
NR > 2 {
idle = $5
sub(/^  */, "", idle)
if ( idle == "" )
idle = 0
if (idle ~ /:/) {
split(idle, t, ":")
idle = t[1] * 60 + t[2] #Converts idle time into seconds
}
if (idle ~ /days/)
idle *= 24*60*60
print $1, $2, idle

}'

Using ksh builtins instead of external commands
Many times, scripters will use external commands like basename, dirname and
tr  because they don't realize they can instead use ksh builtins.

An added bonus is the builtins are faster and require less system resources
because no sub-process is spawned.

basename replacement:
---------------------

$ fullfile="/some/dir/file.txt"
# replaced: file=$(basename $fullfile)
$ file=${fullfile##*/}
$ echo $file
file.txt

dirname replacement:
--------------------

$ fullfile="/some/dir/file.txt"
# replaced: dir=$(dirname $fullfile)
$ dir=${fullfile%/*}
$ echo $dir
/some/dir

tr replacements:
----------------

$ word="MiXeD"
# replaced: word=$(echo $word | tr [A-Z] [a-z])
$ typeset -l word
$ echo $word
mixed

# replaced: word=$(echo $word | tr [a-z] [A-Z])
$ typeset -u word
$ echo $word
MIXED

KSH build-in networking functions
[Note: the following examples will work only with standard
ksh implementations. They will not work with the Linux Korn
Shell pdksh.]

Most Korn Shells (/bin/ksh) have sparsely documented, build-in
networking functions.

Example:

$ date=
$ read date < /dev/tcp/127.0.0.1/13
$ echo $date
Wed Feb 10 00:45:39 MET 1999

This command opens a TCP connection to the IP address 127.0.0.1
(the local loopback IP address), and connects to the port "13"
(daytime, see /etc/services). The current date and time is
returned, and assigned to the variable "date".

Note that the "/dev/tcp/*" directories do not have to exist;
the file names are special to the Korn Shell and are interpre!
ted
by the shell internally. Only numerical ip addresses and port
numbers are supported; "read date < /dev/tcp/localhost/daytime"
does not work.


"Normalize" input field separators
Script programmers sometimes have to process input that consists
of fields separated by whitespace, i.e.

field1    field 2     field3

This input has the disadvantage that it uses different combinations
of blank and TAB characters as input, and is hard to process using
"cut" and "sort", because these commands expect exactly one
field separator character.

The following "sed" line "normalizes" this input replacing each
sequence of two or more whitespace characters with exactly one
 character:

sed 's/ [ ][ ]*//g'

Substitute the five characters "" with a "real" TAB character
(ASCII 9).

Further processing can be done using this  character
as field separator.



To Reverse a File
########  TO PRINT FILE IN REVERSE ORDER BY LINE  #############3

if [ $# -ne 1 ]
then
echo "Usage reverse_file  "
exit 1;
fi

########  By using for loop #############
awk '{ line[NR] = $0 }  END { for (i=NR; i>0; i=i-1)
print line[i] }' $1


Script debugging settings
Most shell script programmers know the command

set -vx

to print each script command before execution. Sometimes
the following flags are useful, too:


set -e        # terminate the script at first error
set -u        # unset variables are fatal errors

Enable "set -x" for all functions
Tracing can be enabled for a
KornShell script by using "set -x", but
this will have no effect on functions
within the script.
The following line will enable tracing
for all functions in a script:

typeset -ft $(typeset +f)

Note that this will not work for BASH.

[Idea is from Dan Mercer ,
news group comp.unix.shell]


Swapping stdout and stderr
In scripts it's often useful not
to filter standard output (i.e. cmd | filter),
but stderr. This may be done using
the following command:

cmd 3>&1 1>&2 2>&3

It's even possible to filter both
standard output and standard error:



( ( cmd | ... process stdout ) 3>&1 1>&2 2>&3 ) | \
... process stderr 3>&1 1>&2 2>&3

The last file descriptor operations restore
the normal meaning of stdin and stdout.


Quickly comment/uncomment whole scripts
#!/usr/bin/ksh
#set -x

#-----VARIABLES

# You must comment out one of the two VAR lines below depending on whether
you want comments turned on or off in front of all your executable commands.

# The VAR line below, when uncommented, will comment out all commands with
$VAR in front of them
#VAR=": #"; export VAR

# The VAR line below, when uncommented, will allow execution of all commands
with $VAR in fron of them.
VAR=""; export VAR

#-----MAIN
$VAR date

Find yesterday's date
A simple way, to find out yesterdays date:
#!/usr/bin/ksh
############################
### set the TZ env variable
############################
TZ=GMT+24
/usr/bin/date +%m%d%Y

TZ specifies the time zone in which the time and date are written. If you
set the time zone 24 hours forward, date will give yesterdays date back


Print this post

No comments: