Heiner's SHELLdorado
Shell Tips & Tricks (Programmer)
SHELLdorado - your UNIX shell scripting resource
HomeGood Shell Coding PracticesExample Shell ScriptsShell Scripting LinksShell Scripting Tips+TricksShell Scripting Articles

Tips&Tricks Home
  Beginner
  Intermediate
  Script Programmer
  Advanced

Submit a new Tip

  Previous Tip Top Next Tip
"comment out" code blocks
Level: Script Programmer Submitted by: ??? URL: none
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.
      

Previous Tip Top Next Tip
"comment out" code blocks (2)
Level: Script Programmer Submitted by: ??? URL: none
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)
      

Previous Tip Top Next Tip
"comment out" code blocks (3)
Level: Script Programmer Submitted by: ??? URL: none
(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--
      

Previous Tip Top Next Tip
cleaning up tmp files
Level: Script Programmer Submitted by: ??? URL: none
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.

      

Previous Tip Top Next Tip
cleaning up tmp files (2)
Level: Script Programmer Submitted by: ??? URL: none
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
#...


      

Previous Tip Top Next Tip
Convert "relative" in "absolute" path name
Level: Script Programmer Submitted by: ??? URL: none
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"
      

Previous Tip Top Next Tip
Positioning the cursor from within shell scripts
Level: Script Programmer Submitted by: ??? URL: none
[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

      

Previous Tip Top Next Tip
Setting default values for variables
Level: Script Programmer Submitted by: ??? URL: none
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.
      

Previous Tip Top Next Tip
Getting a file into "memory"
Level: Script Programmer Submitted by: glong@openwave.com URL: none
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:

${<arrayname>[<subscript>]}

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.
      

Previous Tip Top Next Tip
Find user's name
Level: Script Programmer Submitted by: ??? URL: none
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 " "`



      

Previous Tip Top Next Tip
Find user's name (2)
Level: Script Programmer Submitted by: ??? URL: none
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).

      

Previous Tip Top Next Tip
Using "here-documents" instead of multiple "echo"
Level: Script Programmer Submitted by: ??? URL: none
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 <<!
Please enter your choice
1 - list current directory
2 - list current users
3 - log off
!
      

Previous Tip Top Next Tip
Using "here-documents" instead of multiple "echo" (2)
Level: Script Programmer Submitted by: bijoytg@usa.net URL: http://www.geocities.com/bijoytg_
# you can also turn multi-echos into a single echo

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

Previous Tip Top Next Tip
To find idle users
Level: Script Programmer Submitted by: ??? URL: none
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

}'
      

Previous Tip Top Next Tip
Using ksh builtins instead of external commands
Level: Script Programmer Submitted by: unix-guy@pacbell.net URL: http://www.community.net/~atomik

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
      

Previous Tip Top Next Tip
KSH build-in networking functions
Level: Script Programmer Submitted by: ??? URL: none
[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.

      

Previous Tip Top Next Tip
"Normalize" input field separators
Level: Script Programmer Submitted by: ??? URL: none
Script programmers sometimes have to process input that consists
of fields separated by whitespace, i.e.

    field1    field 2 <TAB>   <TAB> 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
<TAB> character:

    sed 's/ [ <TAB>][ <TAB>]*/<TAB>/g'

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

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


      

Previous Tip Top Next Tip
To Reverse a File
Level: Script Programmer Submitted by: ??? URL: none
########  TO PRINT FILE IN REVERSE ORDER BY LINE  #############3

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

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

      

Previous Tip Top Next Tip
Script debugging settings
Level: Script Programmer Submitted by: ??? URL: none
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
      

Previous Tip Top Next Tip
Enable "set -x" for all functions
Level: Script Programmer Submitted by: ??? URL: none
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 <dmercer@mn.rr.com>,
news group comp.unix.shell]

      

Previous Tip Top Next Tip
Swapping stdout and stderr
Level: Script Programmer Submitted by: ??? URL: none
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.

      

Previous Tip Top Next Tip
Quickly comment/uncomment whole scripts
Level: Script Programmer Submitted by: ??? URL: none
#!/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
      

Previous Tip Top Next Tip
Find yesterday's date
Level: Script Programmer Submitted by: ??? URL: none
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
      

   
Copyright © 1998-2022 Heiner Steven (heiner.steven@shelldorado.com)