|
Softpanorama |
May the source be with you, but remember the KISS principle ;-)
|
| Basic navigation | Command Completion | ||||
| Bash as an Enterprize Shell | Shell Programming | Paul Jarc's software lintsh | Humor | Etc |
Note: You might be better off forgetting all this silly noise about compatibility and using bash. Bash is now standard de-facto and is available of almost all enterprise flavors of Unix and commercial linuxes.
It is still not available on HP-UX 11 but that's the problem of HP. Many people including me do not consider HP-UX to a legitimate Unix ;-). Anyway even they provide bash with binaries available for all HP-UX version you can find. It is easier to install it on all systems that struggle with compatibility questions. Solaris, AIX and linuxes have bash installed out of the box and now bash in the natural least command denominator of shells. But bash 3.xx is pretty close to ksh93 and if you tested you scripts with it, in most cases the script should work OK. It's a pity that most Linux distributions ignore ksh93 which is another candidate but we can do nothing with mass stupidity in this area :-)
Originally sh was Born shell that has a separate implementation. Those days sh is often implemented as a special compilation of existing ksh version (often ksh93) and in most cases it does not make sense to use Born shell. You will be better off using bash
The second problem in writing portable script is that there are also many subtle differences in utilities provided and their location for various flavors of Unix. There are several ways to deal with this problem. One is to provide symbolic link to "most reasonable location", the second is to provide wrappers and the third that probably the most popular is to encode path and the name of the utility in shell variables.
Both the Bourne shell, and the Korn shell, can use the semicolon and the carriage return interchangeably in their syntax of the if, for, and while built-in commands. When using the brackets ([ ]) within if commands, you must separate both inside ends of the brackets from the inside characters with a space.
Shell Script Porting Guidelines
Here is a semi-useless table from the FAQ that compares shells. It is old and from purely academic point of view is incomplete as it does not include ksh93 which is in many respect the pinnacle of traditional Unix shells. But again you need to forget about academic discussion here: it is simpler to switch to bash then struggle with all this stupid complexity. Bash is not perfect but it works well enough to suit your needs. Sometime the best way to conquer obstacle is to go around it :-)
sh csh ksh bash tcsh zsh rc es
Job control N Y Y Y Y Y N N
Aliases N Y Y Y Y Y N N
Shell functions Y(1) N Y Y N Y Y Y
"Sensible" Input/Output redirection Y N Y Y N Y Y Y
Directory stack N Y Y Y Y Y F F
Command history N Y Y Y Y Y L L
Command line editing N N Y Y Y Y L L
Vi Command line editing N N Y Y Y(3) Y L L
Emacs Command line editing N N Y Y Y Y L L
Rebindable Command line editing N N N Y Y Y L L
User name look up N Y Y Y Y Y L L
Login/Logout watching N N N N Y Y F F
Filename completion N Y(1) Y Y Y Y L L
Username completion N Y(2) Y Y Y Y L L
Hostname completion N Y(2) Y Y Y Y L L
History completion N N N Y Y Y L L
Fully programmable Completion N N N N Y Y N N
Mh Mailbox completion N N N N(4) N(6) N(6) N N
Co Processes N N Y N N Y N N
Builtin artithmetic evaluation N Y Y Y Y Y N N
Can follow symbolic links invisibly N N Y Y Y Y N N
Periodic command execution N N N N Y Y N N
Custom Prompt (easily) N N Y Y Y Y Y Y
Sun Keyboard Hack N N N N N Y N N
Spelling Correction N N N N Y Y N N
Process Substitution N N N Y(2) N Y Y Y
Underlying Syntax sh csh sh sh csh sh rc rc
Freely Available N N N(5) Y Y Y Y Y
Checks Mailbox N Y Y Y Y Y F F
Tty Sanity Checking N N N N Y Y N N
Can cope with large argument lists Y N Y Y Y Y Y Y
Has non-interactive startup file N Y Y(7) Y(7) Y Y N N
Has non-login startup file N Y Y(7) Y Y Y N N
Can avoid user startup files N Y N Y N Y Y Y
Can specify startup file N N Y Y N N N N
Low level command redefinition N N N N N N N Y
Has anonymous functions N N N N N N Y Y
List Variables N Y Y N Y Y Y Y
Full signal trap handling Y N Y Y N Y Y Y
File no clobber ability N Y Y Y Y Y N F
Local variables N N Y Y N Y Y Y
Lexically scoped variables N N N N N N N Y
Exceptions N N N N N N N Y
Key to the table above.
Y Feature can be done using this shell.
N Feature is not present in the shell.
F Feature can only be done by using the shells function
mechanism.
L The readline library must be linked into the shell to enable
this Feature.
|
|||||||
16.3 Moving from ksh to bashMigrating your sh and ksh scripts to bash is typically a straightforward process. However, there is a minor difference to be aware of. If you are using the read statement in your script, as shown in Example 16-1, the second line echo results in a blank line. This is a known issue in bash and applies to both single and multiple variable reads.
Example 16-1 Sample read statement
> echo "A B C" | read x y z> echo $x $y $z> _Here are some suggested workarounds for the read command:
> read x y z < <(echo "A B C")> echo $x $y $zA B C> _Using awk
> x=`echo "A B C" | awk `{ print $2 }'`> echo $xB> _For more information about using bash, refer to the following Web site:
http://www.gnu.org/software/bash/
Paul Jarc's software
lintshis a Bourne shell that optionally warns about suspicious or nonportable constructs. It is intended to help script authors write correct, portable scripts; it will also be suitable for use as/bin/sh. It does not exist yet, and it might never, or perhaps the warning functionality could be added to an existing shell. Regardless, this list of gotchas will be maintained, and you can get into the habit of coding to avoid them.The following programs are known to be in use as
/bin/sh, in addition to the/bin/shprograms maintained by OS vendors. Please let me know about any not listed here.
bash(most Linux systems)pdksh(OpenBSD)zsh(Darwin, Mac OS X)ash(NetBSD)- Solaris
ksh- maybe?ksh88- maybe?ksh-93- maybe?You can also contribute by emailing me suggestions for constructs to warn about. My experience is mostly with
bashandpdksh, so information about issues with other shells is especially appreciated. I am not interested in how shells behave in interactive mode; scripts are only affected by differences in behavior in noninteractive mode. Here's the list of issues so far. (Some are bugs in particular versions; these will still be warned about as long as buggy installations are thought to be common, or as long as I forget to update this page.)Related Links
- Sven Mascheck has a page about the history of the Bourne shell.
- The GNU autoconf manual has some useful tips for portable shell programming.
Constructs likely to indicate a programming error
- "
IFS=whatever set $variable"; this setting ofIFSwill not be used to split$variable. Use "IFS=whatever; set $variable" instead.Constructs meaningful to all shells, but with different meanings to different shells
- If an inherited environment variable is set to a new value but never explicitly exported within a script, external commands may see either the original inherited value (Solaris
sh) or the new value (bash).bashautomatically sets the following variables (clobbering any inherited value):$BASH,$BASH_VERSINFO,$BASH_VERSION,$DIRSTACK,$EUID,$GROUPS,$HISTCMD,$HOSTNAME,$HOSTTYPE,$IFS,$LINENO,$MACHTYPE,$OLDPWD,$OPTERR,$OPTIND,$OSTYPE,$PPID,$PWD,$RANDOM,$SECONDS,$SHELLOPTS,$SHLVL,$UID,$_.bashautomatically sets the following variables if they are not already set:$COLUMNS,$HISTFILE,$HISTFILESIZE,$HISTSIZE,$LINES,$MAILCHECK,$PATH,$PS4,$SHELL,$TERM.bashautomatically unsets$PS1and$PS2.bashautomatically exports the following variables:$HOSTNAME,$HOSTTYPE,$MACHTYPE,$OSTYPE,$PATH,$PWD,$SHELL,$SHLVL,$TERM,$_.bashtreats$BASH_VERSINFO,$EUID,$PPID,$SHELLOPTS, and$UIDas read-only.- Solaris
kshautomatically sets$SHELL(to/bin/sh) if it is not already set.- Solaris
shdoes not allow$PATHor$MAILCHECKto be unset.- The builtin "
echo" command may treat backslashes in its arguments as literal backslashes (bash), or as escape characters (pdksh).- The builtin "
echo" command may accept options such as "-n" (bash), or may treat all arguments as data to print (Solarissh).- Solaris
kshdoes not fork for the last command in the last pipeline in a script or subshell.- Solaris
shdoes not fork for the last command in the last pipeline in a subshell.- "
$*" may expand to the positional parameters separated by the first character of$IFS, or by the empty string if$IFSis empty (bash,pdksh, Solarisksh), or it may expand to the positional parameters separated by spaces (Solarissh).bashandpdkshuse$IFSfor word splitting only for the results of unquoted expansions (variable expansions, command substitution, etc.). Solarisshuses it for all unquoted strings. Solariskshuses it for the unquoted parts of any word which includes a (quoted or unquoted) expansion.- When "
\"" appears in a here-document,pdkshinterprets the backslash as an escape character and removes it.bashand Solarisshpreserve the backslash. Workaround: backslash-escape the backslash.- "
var=value builtin-command" may leave the variable set after the builtin command completes (pdksh, Solarissh), or may revert it to its previous state for subsequent commands (bash), depending on which builtin command is used.- "
var=value exec cmd" may set$varin the environment forcmd(bash,pdksh) or not (Solarissh). Workaround: "exec env var=value cmd".- "
var= shell_function" may make$varunset during the call to the function (bashbefore version 2.05b), may set it to the empty string during the call (bash2.05b and later,pdksh) or may not affect$varat all during the call (Solarissh).- If
varis already unset, "unset var" may give exit status 0 (bash2.05b and later, Solarissh) or nonzero (bashbefore version 2.05b,pdksh, Solarisksh).- "
. directory" may fail with an error message (bash) or may be a successful no-op (pdksh).bashallows arbitrary file descriptors to be redirected.pdkshdoesn't redirect file descriptors larger than 9; any digit strings longer than a single character are treated as a command or argument, even if followed immediately by ">" or "<".- Solaris
shwill lose its script fd if a redirection in the script happens to use that fd.bashdups the script fd before performing the redirection.pdkshinitially moves the script to an fd larger than 9, so it cannot be redirected.- In
bash, "command &> file" runscommandwith standard output and error redirected tofile. Inpdksh, it runscommandin the background and then truncatesfile, as if it were written "command & > file".bashaccepts both "!" and "^" to negate character classes in pattern matching;pdkshaccepts only "!".pdkshandbashtreat an unquoted "^" as a non-special character; Solarisshtreats it as a metacharacter.- Solaris
shforks for compound commands with redirections such as "while read var; do something; done < file", so variable assignments andexitbehave differently (although errors still cause the top-level shell to exit ifset -eis in effect).bashandpdkshdo not fork. As a workaround, the compound command can be wrapped in a shell function, and the function can be invoked with redirections; this will not cause Solarisshto fork.- For an
if...elif...fichain, any redirections appplied to the finalfimay take effect only for the finalthenclause (and the accompanyingelifcondition, if there is one) (Solarissh), or for the entire compound command (bash). Workaround: put the whole compound command inside{...}, and move the redirections outside the braces.- Even when
set -eis in effect, "false && true" does not cause Solarisshto exit.- Even when
set -eis in effect, "foo() { return 1; }; foo" does not cause Solarisshto exit.- When
set -eis in effect, a shell function that returns with a nonzero status always causes BSD/OSshto exit, even if the call is the condition of anifstatement, etc. Workaround: call the shell function in a subshell.- When
set -eis in effect, "foo() { true; false; }; foo || :" causes Solarisshto exit.- When
set -eis in effect,eval falsemay cause the shell to exit unconditionally, (pdksh, FreeBSDsh), or may not, depending on the context of theevalcommand (bash). Workaround: wrap theevalin a subshell.- Even when
set -eis not in effect, a builtin command with failing redirections, called fromeval, may cause the shell to exit (FreeBSDsh, NetBSDsh) or not (bash,pdksh, Solarissh). It may even depend on which builtin command is used -bashexits for:, but not fortrue.- When
set -eis in effect and a subshell command exits nonzero, Solarisshandkshand FreeBSDshexit;bash, FreeBSDksh, and NetBSDshandkshdo not exit.pdkshexits in some circumstances but not others.- If the
testcommand is a builtin and returns false, the shell may exit whenset -eis in effect (pdksh,bash) or not (Solarissh).- The "
test" builtin command may treat the following as operators (bash) or as plain strings (Solarissh): ">", "<", "-G", "-O", "-S", "-e", "-ef", "-nt", "-o" "-ot", "==".- "
test '!' = '!'" may treat "=" as a binary operator (bash,pdksh), or may treat the first "!" as negation.- Variable names and shell function names may share a single namespace (Solaris
sh) or have separate namespaces (bash).$?may be reset upon the appearance of redirections (Free/NetBSDsh), or only after whole commands.- Failed redirections (e.g., reading from a nonexistent file) may cause the shell to exit (
pdksh), or to set$?to nonzero for the command (bash).- A failing
cdcommand may cause the shell to exit even whenset -eis not in effect (Solarissh), or may return a nonzero exit status (bash,pdksh).- When
set -eis in effect, a failing command in backticks appearing as an argument of another command, as in "echo `false`", may cause the shell to exit (Solarissh), or not (bash,pdksh, Solarisksh). Workaround: use backticks only in variable assignments. All shells exit if such a command exits nonzero: "var=`false`; echo "$var"".- "
!", "[[", "function" and "time" may be shell keywords, having special effects on parsing (bash), or may be treated as ordinary external command names (Solarissh).- The following commands may be shell builtins or may be treated as ordinary external command names: "
alias", "bind", "builtin", "command", "compgen", "complete", "declare", "dirs", "disown", "enable", "fc", "getconf", "help", "hist", "history", "let", "local", "logout", "newgrp", "popd", "printf", "pushd", "shopt", "sleep", "stop", "typeset", "unalias", "whence".- "
$'foo\nbar'" may be expanded as "foo<newline>bar" (bash), or plainly quoted as "$foo\nbar" (Solarissh).- "
a{b,c}d" may expand to "abd acd" (bash), or may be treated literally (Solarissh).- "
`< file`" may expand to the contents offile(bash), or to an empty string (Solarissh).- A second invocation argument beginning with "
-" may be treated as an option/flag (bash), or as a script filename (Solarissh).- A match pattern like "
[[:alnum:]]", "[[=c=]]", or "[[.symbol.]]" may be treated as an extension pattern (bash) or as a simple character-class pattern followed by a literal "]" (pdksh).- An unquoted "
~" in certain contexts may be subject to tilde expansion (bash), or may be treated literally (Solarissh).- "
set --" may be a no-op (Solarissh), or may unset all positional parameters (bash,pdksh). To unset all positional parameters portably, use "set x && shift".- If there are no arguments, "
"$@"" may expand to a single empty argument (some oldershimplementations) or to no arguments (bash,pdksh, Solarissh,zsh). This is commonly worked around by using "${1+"$@"}". However, this workaround fails forzsh: each argument will be subjected to word splitting. To avoid this, define this global alias before using the workaround:case $ZSH_VERSION in ?*) alias -g '${1+"$@"}="$@"';; esacAlternatively, explicitly check for arguments and use separate command lines:case $# in 0) foo;; *) foo "$@";; esacBut this kind of code is harder to maintain.- If a double-quoted positional parameter is used in a
casepattern, then any wildcard characters in its value may be interpreted either as literal characters (bash,pdksh, Solarissh, OpenBSDsh) or as wildcards (FreeBSDsh, NetBSDshbefore NetBSD 2.0). To force the literal interpretation, assign the value of the positional parameter to a named parameter, and use the named parameter in thecasepattern:var=$1; case foo in "$var") ...;; esac- A
casestatement in which none of the patterns match may return successfully (bash,pdksh) or may preserve$?as the exit code of the previous command (Solarissh). Workaround: include a final pattern clause like "*) :;;" to ensure there is a match.- A
forloop with an empty list of iteration values (such as "for var in $empty ...") may return successfully (bash,pdksh) or may preserve$?as the exit code of the previous command (Solarissh). Workaround: insert a ":" command before theforloop to ensure that the status of the loop will be 0 if it never iterates.- A wildcard pattern (used as an argument, not as a
casepattern) ending in "*/" may expand to a list of directories (bash,pdksh) or may be preserved as if it were quoted (Solarissh). Workaround: use "...*/.".- When invoked as "
sh script-basename" the script may be searched for only in the current directory (pdksh; Solaris, NetBSD, FreeBSD, OpenBSDsh), or in the current directory followed by$PATH(bash), or in $PATH followed by the current directory (Solarisksh).Constructs not meaningful to some shells
These will also be erroneous for
lintsh; no separate warning will be necessary. These include:
- "
for i; do ...; done" is a syntax error for Solarissh. However, "for i do ...; done" works portably.- "
$(command)" is a syntax error for Solarissh. However, "`command`" works portably.- A missing final quote, as in "
echo 'foo", is tolerated by Solarissh, but not bypdkshorbash.- "
${var-multiple words}" (also with+instead of-) is a syntax error for Solarissh. Workaround: "tmp='multiple words'; echo ${var-$tmp}"- In
bash, "command >& file" runscommandwith standard output and standard error redirected tofile. Inpdksh, it is a syntax error. Portable syntax: "command > file 2>&1"
Etc
Solaris Test command
The test utility evaluates the condition and indicates the result of the evaluation by its exit status. An exit status of zero indicates that the condition evaluated as true and an exit status of 1 indicates that the condition evaluated as false.
In the second form of the utility, which uses [ ] rather than test, the square brackets must be separate arguments and condition is optional.
See largefile(5) for the description of the behavior of test when encountering files greater than or equal to 2 Gbyte ( 231 bytes).
The condition following if is executed and, if it returns a 0 exit status, the action following the first then is executed. Otherwise, the condition2 following elif is executed and, if its value is 0, the action2 following the next then is executed. Failing the if and elif conditions, the else action3 is executed. If no else action or then action is executed, the if command returns a 0 exit status. Any number of elif . . . then . . . branching pairs are allowed, but only one else.
test evaluates the condition condition and, if its value is true, sets exit status to 0; otherwise, a non-zero (false) exit status is set; test also sets a non-zero exit status if there are no arguments. When permissions are tested, the effective user ID of the process is used.
All operators, flags, and brackets (brackets used as shown in the second SYNOPSIS line) must be separate arguments to the test command; normally these items are separated by spaces.
The following primitives are used to construct condition:
These primaries may be combined with the following operators:
The not-a-directory alternative to the -f option is a transition aid for BSD applications and may not be supported in future releases.
The -L option is a migration aid for users of other shells which have similar options and may not be supported in future releases.
If you test a file you own (the -r -w or -x tests), but the permission tested does not have the owner bit set, a non-zero (false) exit status will be returned even though the file may have the group or other bit set for that permission. The correct exit status will be set if you are super-user.
The = and != operators have a higher precedence than the -r through -n operators, and = and != always expect arguments; therefore, = and != cannot be used with the -r through -n operators.
If more than one argument follows the -r through -n operators, only the first argument is examined; the others are ignored, unless a -a or a -o is the second argument.
The if must appear alone on its input line or after an else. Only one endif is needed, but it is required. The words else and endif must be the first nonwhite characters on a line. Any number of else if . . . then . . . branching pairs are allowed, but only one else.
The condition following if is executed and, if it returns an exit status of 0, the action following the first then is executed. Otherwise, the condition2 following elif is executed and, if its value is 0, the action2 following the next then is executed. Failing that, the else action3 is executed. If no else action or then action is executed, then the if command returns an exit status of 0. Any number of elif . . . then . . . branching pairs are allowed, but only one else.
For a description of the test built-in, see the ksh(1) sections Conditional Expressions and Arithmetic Evaluation as well as the (sh) Bourne shell's test built-in above.
[ condition ] evaluates file attributes, string comparisons, and compound "and" or "or" conditions.
All operators and elements of primaries must be presented as separate arguments to the test utility.
The following primaries can be used to construct condition:
These primaries can be combined with the following operator:
The primaries with two elements of the form:
-primary_operator primary_operand
primary_operand -primary_operator primary_operand primary_operand primary_operator primary_operand
The algorithm for determining the precedence of the operators and the return value that will be generated is based on the number of arguments presented to test. (However, when using the [. . .] form, the right-bracket final argument will not be counted in this algorithm.)
In the following list, $1, $2, $3 and $4 represent the arguments presented to test.
Scripts should be careful when dealing with user-supplied input that could be confused with primaries and operators. Unless the application writer knows all the cases that produce input to the script, invocations like:
test "$1" -a "$2"should be written as:
test "$1" && test "$2"to avoid problems if a user supplied values such as $1 set to ! and $2 set to the null string. That is, in cases where maximal portability is of concern, replace:
test expr1 -a expr2with:
test expr1 && test expr2and replace:
test expr1 -o expr2with:
test expr1 | | test expr2but note that, in test, -a has higher precedence than -o while && and | | have equal precedence in the shell.
Parentheses or braces can be used in the shell command language to effect grouping.
Parentheses must be escaped when using sh; for example:
test \( expr1 -a expr2 \) -o expr3This command is not always portable outside XSI-conformant systems. The following form can be used instead:
( test expr1 && test expr2 ) | | test expr3The two commands:
test "$1" test ! "$1"could not be used reliably on some historical systems. Unexpected results would occur if such a string condition were used and $1 expanded to !, ( or a known unary primary. Better constructs are:
test -n "$1" test -z "$1"respectively.
Historical systems have also been unreliable given the common construct:
test "$response" = "expected string"One of the following is a more reliable form:
test "X$response" = "Xexpected string" test "expected string" = "$response"
Note that the second form assumes that expected string could not be confused with any unary primary. If expected string starts with -, (, ! or even =, the first form should be used instead. Using the preceding rules without the marked extensions, any of the three comparison forms is reliable, given any input. (However, note that the strings are quoted in all cases.)
Because the string comparison binary primaries, = and !=, have a higher precedence than any unary primary in the >4 argument case, unexpected results can occur if arguments are not properly prepared. For example, in
test -d $1 -o -d $2
If $1 evaluates to a possible directory name of =, the first three arguments are considered a string comparison, which causes a syntax error when the second -d is encountered. is encountered. One of the following forms prevents this; the second is preferred:
test \( -d "$1" \) -o \( -d "$2" \) test -d "$1" | | test -d "$2"Also in the >4 argument case,
test "$1" = "bat" -a "$2" = "ball"
Syntax errors will occur if $1 evaluates to ( or !. One of the following forms prevents this; the third is preferred:
test "X$1" = "Xbat" -a "X$2" = "Xball" test "$1" = "bat" && test "$2" = "ball" test "X$1" = "Xbat" && test "X$2" = "Xball"
In the if command examples, three conditions are tested, and if all three evaluate as true or successful, then their validities are written to the screen. The 3 tests are:
Perform a mkdir if a directory does not exist:
test ! -d tempdir && mkdir tempdir
Wait for a file to become non-readable:
while test -r thefile do sleep 30 done echo'"thefile" is no longer readable'
Perform a command if the argument is one of three strings (two variations):
if [ "$1" = "pear" ] | | [ "$1" = "grape" ] | | [ "$1" = "apple" ]
then
command
fi
case "$1" in
pear|grape|apple) command;;
esac
The two forms of the test built-in follow the Bourne shell's if example.
ZERO=0 ONE=1 TWO=2 ROOT=root
if [ $ONE -gt $ZERO ]
[ $TWO -eq 2 ]
grep $ROOT /etc/passwd >&1 > /dev/null # discard output
then
echo "$ONE is greater than 0, $TWO equals 2, and $ROOT is a user-name
in the password file"
else
echo "At least one of the three test conditions is false"
fi
test `grep $ROOT /etc/passwd >&1 /dev/null` # discard output echo $? # test for success [ `grep nosuchname /etc/passwd >&1 /dev/null` ] echo $? # test for failure
ZERO=0 ONE=1 TWO=$((ONE+ONE)) ROOT=root
if ((ONE > ZERO)) # arithmetical comparison
[[ $TWO = 2 ]] # string comparison
[ `grep $ROOT /etc/passwd >&1 /dev/null` ] # discard output
then
echo "$ONE is greater than 0, $TWO equals 2, and $ROOT is a user-name
in the password file"
else
echo "At least one of the three test conditions is false"
fi
The Korn shell will also accept the syntax of both the if command and the test command of the Bourne shell.
When using the brackets ([ ]) within if commands, you must separate both inside ends of the brackets from the inside characters with a space.
See environ(5) for descriptions of the following environment variables that affect the execution of test: LC_CTYPE, LC_MESSAGES, and NLSPATH.
The following exit values are returned:
Copyright © 1996-2008 by Dr. Nikolai Bezroukov. www.softpanorama.org was created as a service to the UN Sustainable Development Networking Programme (SDNP) in the author free time. Submit comments This document is an industrial compilation designed and created exclusively for educational use and is placed under the copyright of the Open Content License(OPL). Original materials copyright belong to respective owners. Quotes are made for educational purposes only in compliance with the fair use doctrine.
Standard disclaimer: The statements, views and opinions presented on this web page are those of the author and are not endorsed by, nor do they necessarily reflect, the opinions of the author present and former employers, SDNP or any other organization the author may be associated with. We do not warrant the correctness of the information provided or its fitness for any purpose.
Last modified: June 02, 2008