Softpanorama

Home Switchboard Unix Administration Red Hat TCP/IP Networks Neoliberalism Toxic Managers
May the source be with you, but remember the KISS principle ;-)
Skepticism and critical thinking is not panacea, but can help to understand the world better

Case statement in shell

News

bash

Recommended  Links

Arithmetic expressions Comparison operators String Operations in Shell
if statements Loops in Shell   select   Pipes in Loops
  Advanced navigation BASH Debugging Shell history   Etc

Shell case statement is similar to those in Pascal and switch statement in C. It  can be used to test a variable against set of patterns.  Often case statement lets you express a series of if-then-else statements that check single variable for various conditions or ranges in a more concise way.

The syntax of case is as follows:

case expression in
    pattern1 )
        statements ;;
    pattern2 )
        statements ;;
    ...
esac

Any of the patterns can actually be several patterns separated by pipe character "|". If expression matches one of the patterns, its corresponding statements of a case branch are executed. If there are several patterns separated by pipe characters, the expression can match any of them in order for the associated statements to be run. The patterns are checked in order until a match is found; if none is found, nothing happens.

For example:

for filename in $*; do
    case $filename in
        *.c )
            objname=${filename%.c}.o
            ccom $filename $objname
            ;;
        *.s )
            objname=${filename%.s}.o
            as $filename $objname 
            ;;
        *.o ) ;;
        *   )
            print "error: $filename is not a source or object file."
            return 1 ;;
    esac
done

The final case is *, which is a catchall for whatever didn't match the other cases. (In fact, a * case is analogous to a default case in C and an otherwise case in some Pascal-derived languages.)

Another example:

 
extension="${file##*.}" 
case $extension in
     "gz" )
           gunzip $file
           ;;
     "bz2" )
           bz2unpack $file
           ;;
     * )
           echo "Archive format for extention '$extension' is not recognized."
           ;;
esac

Above, bash first expands "${file##*.}". In the code, "$file" is the name of a file, and "${file##.*}" has the effect of stripping all text except that following the last period in the filename. Then, bash compares the resultant string against the values listed cases of case statement. In this case, file extension will be  compared against "gz" and "bz2" If one of those matches, then appropriate command will be executed for the value of $file .

Please note that  $file can contain fully qualified filename . For example:

file="/somewhere/sample.gz"

Extension will still be extracted correctly. Of course you can split fully qualified name into directory and path and operate with them separately, for example:

file=$(basename $fully_qualified_name)
dir=$(dirname $fully_qualified_name)

So far selectors in case statement were simple strings. They can be pretty complex patterns. For example here is how you can implement the functions that trims spaces both from left and right of its argument:

function trim
{ 
   target=$1
   while : # this is an infinite loop
   do
   case $target in
      ' '*) target=${target#?} ;; ## if $target begins with a space remove it
      *' ') target=${target%?} ;; ## if $target ends with a space remove it
      *) break ;; # no more leading or trailing spaces, so exit the loop
   esac
   done
   return target
}

You can use "Or" operation in case to tie several cases to one branch of case statement. Here is an example from Wikipedia:

case $n in
    0)      echo 'You typed 0.';;
    1|9)    echo "$n is a perfect square.";;
    3|5|7)  echo "$n is a prime number.";;
    4)    echo "$n is a perfect square.
$n is an even number";;
    2|6|8)    echo "$n is an even number.";;
    *)      echo 'Only single-digit numbers are allowed.';;
esac
As patterns can have character classes that sometimes simplify logic or options processing and similar cases (Using case statements):
cat disktest.sh
#!/bin/bash

# This script does a very simple test for checking disk space.

space=`df -h | awk '{print $5}' | grep % | grep -v Use | sort -n | tail -1 | cut -d "%" -f1 -`

case $space in
[1-6]*)
  Message="All is quiet."
  ;;
[7-8]*)
  Message="Start thinking about cleaning out some stuff.  There's a partition that is $space % full."
  ;;
9[1-8])
  Message="Better hurry with that new disk...  One partition is $space % full."
  ;;
99)
  Message="I'm drowning here!  There's a partition at $space %!"
  ;;
*)
  Message="I seem to be running with an nonexistent amount of disk space..."
  ;;
esac

Additional examples from Advanced Bash-Scripting Guide

Here are few additional examples from Advanced Bash-Scripting Guide: Chapter 11. Loops and Branches

Example 11-24. Using case
#!/bin/bash
# Testing ranges of characters.

echo; echo "Hit a key, then hit return."
read Keypress

case "$Keypress" in
  [[:lower:]]   ) echo "Lowercase letter";;
  [[:upper:]]   ) echo "Uppercase letter";;
  [0-9]         ) echo "Digit";;
  *             ) echo "Punctuation, whitespace, or other";;
esac      #  Allows ranges of characters in [square brackets],
          #+ or POSIX ranges in [[double square brackets.

#  In the first version of this example,
#+ the tests for lowercase and uppercase characters were
#+ [a-z] and [A-Z].
#  This no longer works in certain locales and/or Linux distros.
#  POSIX is more portable.
#  Thanks to Frank Wang for pointing this out.

#  Exercise:
#  --------
#  As the script stands, it accepts a single keystroke, then terminates.
#  Change the script so it accepts repeated input,
#+ reports on each keystroke, and terminates only when "X" is hit.
#  Hint: enclose everything in a "while" loop.

exit 0
Example 11-25. Creating menus using case
#!/bin/bash

# Crude address database

clear # Clear the screen.

echo "          Contact List"
echo "          ------- ----"
echo "Choose one of the following persons:" 
echo
echo "[E]vans, Roland"
echo "[J]ones, Mildred"
echo "[S]mith, Julie"
echo "[Z]ane, Morris"
echo

read person

case "$person" in
# Note variable is quoted.

  "E" | "e" )
  # Accept upper or lowercase input.
  echo
  echo "Roland Evans"
  echo "4321 Flash Dr."
  echo "Hardscrabble, CO 80753"
  echo "(303) 734-9874"
  echo "(303) 734-9892 fax"
  echo "revans@zzy.net"
  echo "Business partner & old friend"
  ;;
# Note double semicolon to terminate each option.

  "J" | "j" )
  echo
  echo "Mildred Jones"
  echo "249 E. 7th St., Apt. 19"
  echo "New York, NY 10009"
  echo "(212) 533-2814"
  echo "(212) 533-9972 fax"
  echo "milliej@loisaida.com"
  echo "Ex-girlfriend"
  echo "Birthday: Feb. 11"
  ;;

# Add info for Smith & Zane later.

          * )
   # Default option.	  
   # Empty input (hitting RETURN) fits here, too.
   echo
   echo "Not yet in database."
  ;;

esac

echo

#  Exercise:
#  --------
#  Change the script so it accepts multiple inputs,
#+ instead of terminating after displaying just one address.

exit 0

An  clever use of case involves testing for command-line parameters.

#! /bin/bash

case "$1" in
  "") echo "Usage: ${0##*/} <filename>"; exit $E_PARAM;;
                      # No command-line parameters,
                      # or first parameter empty.
# Note that ${0##*/} is ${var##pattern} param substitution.
                      # Net result is $0.

  -*) FILENAME=./$1;;   #  If filename passed as argument ($1)
                      #+ starts with a dash,
                      #+ replace it with ./$1
                      #+ so further commands don't interpret it
                      #+ as an option.

  * ) FILENAME=$1;;     # Otherwise, $1.
esac

Here is an more straightforward example of command-line parameter handling:

#! /bin/bash


while [ $# -gt 0 ]; do    # Until you run out of parameters . . .
  case "$1" in
    -d|--debug)
              # "-d" or "--debug" parameter?
              DEBUG=1
              ;;
    -c|--conf)
              CONFFILE="$2"
              shift
              if [ ! -f $CONFFILE ]; then
                echo "Error: Supplied file doesn't exist!"
                exit $E_CONFFILE     # File not found error.
              fi
              ;;
  esac
  shift       # Check next set of parameters.
done

#  From Stefano Falsetto's "Log2Rot" script,
#+ part of his "rottlog" package.
#  Used with permission.
Example 11-26. Using command substitution to generate the case variable
#!/bin/bash
# case-cmd.sh: Using command substitution to generate a "case" variable.

case $( arch ) in   # "arch" returns machine architecture.
                    # Equivalent to 'uname -m' ...
  i386 ) echo "80386-based machine";;
  i486 ) echo "80486-based machine";;
  i586 ) echo "Pentium-based machine";;
  i686 ) echo "Pentium2+-based machine";;
  *    ) echo "Other type of machine";;
esac

exit 0

A case construct can filter strings for globbing patterns.

Example 11-27. Simple string matching
#!/bin/bash
# match-string.sh: Simple string matching.

match_string ()
{ # Exact string match.
  MATCH=0
  E_NOMATCH=90
  PARAMS=2     # Function requires 2 arguments.
  E_BAD_PARAMS=91

  [ $# -eq $PARAMS ] || return $E_BAD_PARAMS

  case "$1" in
  "$2") return $MATCH;;
  *   ) return $E_NOMATCH;;
  esac

}  


a=one
b=two
c=three
d=two


match_string $a     # wrong number of parameters
echo $?             # 91

match_string $a $b  # no match
echo $?             # 90

match_string $b $d  # match
echo $?             # 0


exit 0		    
Example 11-28. Checking for alphabetic input
#!/bin/bash
# isalpha.sh: Using a "case" structure to filter a string.

SUCCESS=0
FAILURE=-1

isalpha ()  # Tests whether *first character* of input string is alphabetic.
{
if [ -z "$1" ]                # No argument passed?
then
  return $FAILURE
fi

case "$1" in
  [a-zA-Z]*) return $SUCCESS;;  # Begins with a letter?
  *        ) return $FAILURE;;
esac
}             # Compare this with "isalpha ()" function in C.


isalpha2 ()   # Tests whether *entire string* is alphabetic.
{
  [ $# -eq 1 ] || return $FAILURE

  case $1 in
  *[!a-zA-Z]*|"") return $FAILURE;;
               *) return $SUCCESS;;
  esac
}

isdigit ()    # Tests whether *entire string* is numerical.
{             # In other words, tests for integer variable.
  [ $# -eq 1 ] || return $FAILURE

  case $1 in
    *[!0-9]*|"") return $FAILURE;;
              *) return $SUCCESS;;
  esac
}



check_var ()  # Front-end to isalpha ().
{
if isalpha "$@"
then
  echo "\"$*\" begins with an alpha character."
  if isalpha2 "$@"
  then        # No point in testing if first char is non-alpha.
    echo "\"$*\" contains only alpha characters."
  else
    echo "\"$*\" contains at least one non-alpha character."
  fi  
else
  echo "\"$*\" begins with a non-alpha character."
              # Also "non-alpha" if no argument passed.
fi

echo

}

digit_check ()  # Front-end to isdigit ().
{
if isdigit "$@"
then
  echo "\"$*\" contains only digits [0 - 9]."
else
  echo "\"$*\" has at least one non-digit character."
fi

echo

}

a=23skidoo
b=H3llo
c=-What?
d=What?
e=`echo $b`   # Command substitution.
f=AbcDef
g=27234
h=27a34
i=27.34

check_var $a
check_var $b
check_var $c
check_var $d
check_var $e
check_var $f
check_var     # No argument passed, so what happens?
#
digit_check $g
digit_check $h
digit_check $i


exit 0        # Script improved by S.C.

# Exercise:
# --------
#  Write an 'isfloat ()' function that tests for floating point numbers.
#  Hint: The function duplicates 'isdigit ()',
#+ but adds a test for a mandatory decimal point.

Top Visited
Switchboard
Latest
Past week
Past month

NEWS CONTENTS

Old News ;-)

[Sep 06, 2019] Using Case Insensitive Matches with Bash Case Statements by Steven Vona

Jun 30, 2019 | www.putorius.net

If you want to match the pattern regardless of it's case (Capital letters or lowercase letters) you can set the nocasematch shell option with the shopt builtin. You can do this as the first line of your script. Since the script will run in a subshell it won't effect your normal environment.

#!/bin/bash
 shopt -s nocasematch
 read -p "Name a Star Trek character: " CHAR
 case $CHAR in
   "Seven of Nine" | Neelix | Chokotay | Tuvok | Janeway )
       echo "$CHAR was in Star Trek Voyager"
       ;;&
   Archer | Phlox | Tpol | Tucker )
       echo "$CHAR was in Star Trek Enterprise"
       ;;&
   Odo | Sisko | Dax | Worf | Quark )
       echo "$CHAR was in Star Trek Deep Space Nine"
       ;;&
   Worf | Data | Riker | Picard )
       echo "$CHAR was in Star Trek The Next Generation" &&  echo "/etc/redhat-release"
       ;;
   *) echo "$CHAR is not in this script." 
       ;;
 esac

[Sep 02, 2019] Switch statement for bash script

Sep 02, 2019 | www.linuxquestions.org
Switch statement for bash script
<a rel='nofollow' target='_blank' href='//rev.linuxquestions.org/www/delivery/ck.php?n=a054b75'><img border='0' alt='' src='//rev.linuxquestions.org/www/delivery/avw.php?zoneid=10&amp;n=a054b75' /></a>
[ Log in to get rid of this advertisement] Hello, i am currently trying out the switch statement using bash script.

CODE:
showmenu () {
echo "1. Number1"
echo "2. Number2"
echo "3. Number3"
echo "4. All"
echo "5. Quit"
}

while true
do
showmenu
read choice
echo "Enter a choice:"
case "$choice" in
"1")
echo "Number One"
;;
"2")
echo "Number Two"
;;
"3")
echo "Number Three"
;;
"4")
echo "Number One, Two, Three"
;;
"5")
echo "Program Exited"
exit 0
;;
*)
echo "Please enter number ONLY ranging from 1-5!"
;;
esac
done

OUTPUT:
1. Number1
2. Number2
3. Number3
4. All
5. Quit
Enter a choice:

So, when the code is run, a menu with option 1-5 will be shown, then the user will be asked to enter a choice and finally an output is shown. But it is possible if the user want to enter multiple choices. For example, user enter choice "1" and "3", so the output will be "Number One" and "Number Three". Any idea?

Just something to get you started.

Code:

#! /bin/bash
showmenu ()
{
    typeset ii
    typeset -i jj=1
    typeset -i kk
    typeset -i valid=0  # valid=1 if input is good

    while (( ! valid ))
    do
        for ii in "${options[@]}"
        do
            echo "$jj) $ii"
            let jj++
        done
        read -e -p 'Select a list of actions : ' -a answer
        jj=0
        valid=1
        for kk in "${answer[@]}"
        do
            if (( kk < 1 || kk > "${#options[@]}" ))
            then
                echo "Error Item $jj is out of bounds" 1>&2
                valid=0
                break
            fi
            let jj++
        done
    done
}

typeset -r c1=Number1
typeset -r c2=Number2
typeset -r c3=Number3
typeset -r c4=All
typeset -r c5=Quit
typeset -ra options=($c1 $c2 $c3 $c4 $c5)
typeset -a answer
typeset -i kk
while true
do
    showmenu
    for kk in "${answer[@]}"
    do
        case $kk in
        1)
            echo 'Number One'
            ;;
        2)
            echo 'Number Two'
            ;;
        3)
            echo 'Number Three'
            ;;
        4)
            echo 'Number One, Two, Three'
            ;;
        5)
            echo 'Program Exit'
            exit 0
            ;;
        esac
    done 
done
stevenworr
View Public Profile
View LQ Blog
View Review Entries
View HCL Entries
Find More Posts by stevenworr
Old 11-16-2009, 10:10 PM # 4
wjs1990 Member
Registered: Nov 2009 Posts: 30
Original Poster
Rep: Reputation: 15
Ok will try it out first. Thanks.
Last edited by wjs1990; 11-16-2009 at 10:13 PM .
wjs1990
View Public Profile
View LQ Blog
View Review Entries
View HCL Entries
Find More Posts by wjs1990
Old 11-16-2009, 10:16 PM # 5
evo2 LQ Guru
Registered: Jan 2009 Location: Japan Distribution: Mostly Debian and CentOS Posts: 5,945
Rep: Reputation: 1376 Reputation: 1376 Reputation: 1376 Reputation: 1376 Reputation: 1376 Reputation: 1376 Reputation: 1376 Reputation: 1376 Reputation: 1376 Reputation: 1376
This can be done just by wrapping your case block in a for loop and changing one line.

Code:

#!/bin/bash
showmenu () {
    echo "1. Number1"
    echo "2. Number2"
    echo "3. Number3"
    echo "4. All"
    echo "5. Quit"
}

while true ; do
    showmenu
    read choices
    for choice in $choices ; do
        case "$choice" in
            1)
                echo "Number One" ;;
            2)
                echo "Number Two" ;;
            3)
                echo "Number Three" ;;
            4)
                echo "Numbers One, two, three" ;;
            5)
                echo "Exit"
                exit 0 ;;
            *)
                echo "Please enter number ONLY ranging from 1-5!"
                ;;
        esac
    done
done
You can now enter any number of numbers seperated by white space.

Cheers,

EVo2.

[Feb 10, 2011] Regex in if-then-else statement to match strings

Sept 18, 2007 | The UNIX and Linux Forums

Using a case/esac statement makes regular expressions simple and obvious.

Code:

case $name in
    [ab]{2,3} )
         echo do stuff
         ;;
    * ) 
         ;;
esac

drl:

Hi.

Perhaps I am using a too-old version of bash. Version 3.00.16(1) recognizes the [ab] but not the {2,3} part, apparently because the case selectors are expanded in the same fashion as pathnames, not regular expressions:

Code:

#!/bin/bash3

# @(#) s4       Demonstrate case selectors.

set -o nounset
echo

echo "GNU bash $BASH_VERSION" >&2
echo " MUST USE VERSION 3 FOR REGULAR EXPRESSIONS WITH =~ OPERATOR!"
# See: http://www.unix.com/showthread.php?p=302136557&posted=1#post302136557

echo

# if [ $name = [ab]{2,3} ]; then
name="b"
name="bb"
echo " Original string = \"$name\""

case $name in
[ab]{2,3} )
    echo Success
        ;;
* )
    echo Failure
        ;;
esac

echo
name="b{2,3}"
echo " Original string = \"$name\""

case $name in
[ab]{2,3} )
    echo Success
        ;;
* )
    echo Failure
        ;;
esac

exit 0

producing:

Code:

% ./s4

GNU bash 3.00.16(1)-release
 MUST USE VERSION 3 FOR REGULAR EXPRESSIONS WITH =~ OPERATOR!

 Original string = "bb"
Failure

 Original string = "b{2,3}"
Success

cheers, drl

radoulov:

Yes, it's globbing, not re's. For {2,3} with globbing:

Code:

bash 3.2.25(1)$ v=b
bash 3.2.25(1)$ case $v in ([ab][ab]|[ab][ab][ab]) echo OK;;(?) echo KO;;esac
KO
bash 3.2.25(1)$ v=bbb
bash 3.2.25(1)$ case $v in ([ab][ab]|[ab][ab][ab]) echo OK;;(?) echo KO;;esac
OK
bash 3.2.25(1)$ # or:
bash 3.2.25(1)$ shopt -s extglob
bash 3.2.25(1)$ v=b
bash 3.2.25(1)$ case $v in ([ab][ab]?([ab])) echo OK;;(?) echo KO;;esac
KO
bash 3.2.25(1)$ v=bb
bash 3.2.25(1)$ case $v in ([ab][ab]?([ab])) echo OK;;(?) echo KO;;esac
OK

[Feb 10, 2011] Idiosyncratic behavior-case-statement-bash

08-20-2008 | The UNIX and Linux Forums

stryth

Hello all, I'm a new poster with a problem that I'm not finding in the archives.

I'm trying to better my understanding of the behavior of case statements, in particular the word to pattern matching aspects. According to the GNU bash reference (Bash Reference Manual) the syntax is:
case word in [ [(] pattern [| pattern]...) command-list ;;]... esac

Also, "Each pattern undergoes tilde expansion, parameter expansion, command substitution, and arithmetic expansion."

Based on this I would expect both of these expressions to behave identically:

#! /bin/bash
# test program attempting to understand case statements
# predominantly used/tested by author in bash --version
#    GNU bash, version 3.2.39(1)-release (i686-pc-linux-gnu)
#    Copyright (C) 2007 Free Software Foundation, Inc.
# please note that above version is as installed by gentoo
# also tested using completely unmodified/unpatched bash --version 
#    GNU bash, version 3.2.0(1)-release (i686-pc-linux-gnu)
#    Copyright (C) 2005 Free Software Foundation, Inc.

# Usage: $0 [ {1..10} ]


VAR=6

if [ $# -gt 0 ]
then
  VAR=$1
fi

echo VAR=$VAR

case $VAR in
   [1..5] ) echo "one to five" ;;
  $(seq -s'|' 6 10) ) echo "six to ten" ;;
#  6|7|8|8|10 ) echo "six to ten" ;;
  *       ) echo "fall through" ;;
esac

exit 0

fpmurphy

Interesting. I replaced seq with echo "6|7|8|9|10" just to take seq out of the equation. The script works as expected with ksh93 but fails on bash (version 3.2.29)

era

It seems to interpret the expanded string literally.

Code:

bash$ case '6|7|8|9|10' in $(seq -s '|' 6 10) ) echo yes;; esac
yes 

It would surprise me if the spec is unambiguous on this point ...

stryth

Quote:

It seems to interpret the expanded string literally.

Code:

bash$ case '6|7|8|9|10' in $(seq -s '|' 6 10) ) echo yes;; esac
yes 

It would surprise me if the spec is unambiguous on this point ...

You nailed it. Sifting through the source was revealing:

case 3 in 2|3|4 )

The above produces a linked list of strings 2->3->4, which are in turn compared as strings against the 3 in the case. When a variable or command substitution is used the substitution happens but is never parsed for comparison of individual components.

This appears to be a bug and has been submitted as such.

ghostdog74:

Code:

shopt -s extglob
case $var in
    @($(seq -s'|' 6 10)) )   echo "six to ten" ;;
    6|7|8|8|10 ) echo "dfs six to ten" ;;
esac
shopt -u extglob

See here under 3.5.8.1 for explanation

drl

Hi.

My interpretation of this is different from the previous ones. The bash manual does indeed have the words cited. And it is clear that substitution takes place. However, the result is simply a string. The manual does not say that the case statement is then re-scanned to cause the strings to be treated as patterns.

We know how to cause a re-scan: the eval built-in:

Code: #!/bin/bash3 -

# @(#) s2 Demonstrate eval of case to get active selectors.

echo echo "(Versions displayed with local utility \"version\")" version >/dev/null 2>&1 && version "=o" $(_eat $0 $1) set -o nounset

echo VAR=6

if [ $# -gt 0 ] then VAR=$1 fi

echo VAR=$VAR six=$(seq -s'|' 6 10) echo " Generated selector in variable: $six"

echo echo " Results of eval case:" eval " case $VAR in $(seq -s'|' 1 5) ) echo ' 1 - 5 (seq)' ;; $six) echo 'six to ten (variable)' ;; * ) echo 'fall through' ;; esac "

exit 0 Producing:

Code: $ ./s2 5

(Versions displayed with local utility "version") Linux 2.6.11-x1 GNU bash 3.00.16(1)-release

VAR=5 Generated selector in variable: 6|7|8|9|10

Results of eval case: 1 - 5 (seq) $ $ ./s2 7

(Versions displayed with local utility "version") Linux 2.6.11-x1 GNU bash 3.00.16(1)-release

VAR=7 Generated selector in variable: 6|7|8|9|10

Results of eval case: six to ten (variable) This may not be what we want or expect, and the interpretation may not even be correct. We might consider this a method to get around a limitation. In any case (pun intended), it works ... cheers, drl

Shell Programming Introduction

Recommended Links

Google matched content

Softpanorama Recommended

Top articles

Sites



Etc

The Last but not Least Technology is dominated by two types of people: those who understand what they do not manage and those who manage what they do not understand ~Archibald Putt. Ph.D


Copyright 1996-2018 by Dr. Nikolai Bezroukov. www.softpanorama.org was initially created as a service to the (now defunct) UN Sustainable Development Networking Programme (SDNP) in the author free time and without any remuneration. This document is an industrial compilation designed and created exclusively for educational use and is distributed under the Softpanorama Content License. Original materials copyright belong to respective owners. Quotes are made for educational purposes only in compliance with the fair use doctrine.

FAIR USE NOTICE This site contains copyrighted material the use of which has not always been specifically authorized by the copyright owner. We are making such material available to advance understanding of computer science, IT technology, economic, scientific, and social issues. We believe this constitutes a 'fair use' of any such copyrighted material as provided by section 107 of the US Copyright Law according to which such material can be distributed without profit exclusively for research and educational purposes.

This is a Spartan WHYFF (We Help You For Free) site written by people for whom English is not a native language. Grammar and spelling errors should be expected. The site contain some broken links as it develops like a living tree...

You can use PayPal to make a contribution, supporting development of this site and speed up access. In case softpanorama.org is down you can use the at softpanorama.info

Disclaimer:

The statements, views and opinions presented on this web page are those of the author (or referenced source) 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.

The site uses AdSense so you need to be aware of Google privacy policy. You you do not want to be tracked by Google please disable Javascript for this site. This site is perfectly usable without Javascript.

Last modified: September 10, 2019