My-Tiny.Net :: Networking with Virtual Machines



bash Script I/O by Example



This is a simple bash script to demonstrate redirection, command line parameters, and using ANSI escape codes to change text colors. For background, see Standard I/O::Redirection on the menu

A few things to know about bash:
  • We can execute multiple commands on a single line by using ; (semicolon) to separate them.

  • Shell variables are declared as we might expect. Putting a $ in front of the variable name means use the value of the variable.

  • Anything enclosed in double quotes " " is passed on exactly as presented except that the values of shell variables are substituted.

  • Within single quotes ' ' the values of shell variables are not substituted.

  • $( ) or backticks ` ` are used to enclose commands: shell variable values are substituted and the item is replaced by the standard output of the command.

  • The read built-in command is the counterpart of the echo command. One line is read from the standard input, and processed according to the options.

  • Text on the command line after the command is read as positional parameters starting with the name of the command itself in position zero.

  • The -e option of the echo command enables parsing of the escape sequences. Check Quick Reference :: Escape Codes on the menu for a list of possibilities.

  • By default, the echo command prints everything on a line by itself; the -n option lets us have several outputs on the same line.

  • Use ${#var} to get the length of variable var, or ${#num} to get the length of positional parameter num


Since we need some short text, here is a quickstart list of switches for the date command:
  • +%x local date representation (e.g., 12/31/99)
  • +%X local time representation (e.g., 23:13:48)
  • +%r 12-hour clock time (e.g., 11:11:04 PM)
  • +%R 24-hour hour and minute; same as %H:%M
  • +%A full weekday name (e.g., Sunday)
  • +%B full month name (e.g., January)
  • +%n a newline


And here's the script! The suggestions assume you named it showparams.sh and are running it from the current directory.
Check for it in usr/local/bin
#!/bin/bash
    echo "Read from stdin"
    read HOST PORT FILE therest

    echo -n -e "\e[01;32m"; echo -n $HOST; echo -e "\e[00m" 
    echo -n -e "\e[01;31m"; echo -n $PORT; echo -e "\e[00m" 
    echo -n -e "\e[01;34m"; echo -n $FILE; echo -e "\e[00m" 
    echo "anything else: $therest" 
    echo 
    echo "$0 command line positional parameters"
    echo -n -e "\e[01;32m"; echo -n $1; echo -e "\e[00m" 
    echo -n -e "\e[01;31m"; echo -n $2; echo -e "\e[00m" 
    echo -n -e "\e[01;34m"; echo -n $3; echo -e "\e[00m" 
    echo

    # suggestions:
    # echo "$(date +%A) $(date +%x) $(date +%R)" |./showparams.sh 1 2 3 4
    # echo "1 2 3 4" |./showparams.sh $(date +%A) $(date +%x) $(date +%R)
In this form the read command assigns the first word of the line to the variable HOST, the second word to PORT, and so on, with all leftover words assigned to the last name, theRest. If there are fewer words read from the input stream than there are names, the remaining names are assigned empty values. If no names are supplied, the whole line is assigned to the variable REPLY.

The values of the variables read from stdin are printed first, then the values of the positional parameters.

"Here Documents"

In bash and some other shells, a "Here Document" is a type of redirection that allows you to pass multiple lines of input to a command. This technique is not used too often, but it saves some typing when you write the script.

The first line starts with an optional command followed by the special redirection operator  <<  and the endpoint identifier. You can use any string for this, the most commonly used are EOF or END. If the endpoint identifier is in single quotes, the lines of the Here Document are passed along 'as-is'; if it is in double quotes the shell will substitute all variables, commands and special characters before passing the lines to stdout.

The here-document block can contain strings, variables, commands and any other type of input. However, leading whitespace characters are not allowed (there is a way to use a leading Tab in the source, but it will not get passed along to the listener, which is very bad for our ascii art!).

The last line ends with the endpoint identifier. Again, white space in front is not allowed.

Here are three scripts that process a Here Document and redirect the output. Note that the middle one assumes the named pipe already exists, so be sure it does! (see the Named Pipes section in the Redirection page under Standard I/O on the menu). The script names ccat, pcat, ncat are just suggestions, call them whatever you like ...

Local
Just to the screen
Named Pipe
Listener on another VT
Netcat
Listener across the Network
  cat /tmp/piper nc -l -p 23432
Run from the current directory as
./ccat ./pcat /tmp/piper ./ncat hostIP 23432
#!/bin/bash
echo 
cat <<'END' 
:                   :
:    /\_/\          :
:   / 0 0 \         :
:  ====v==== Seecat :
:   \  W  /   ____  :
:    |   |   /      :
:   / ___ \ _|      :
:  / /   \ \        :
: (((-----)))       :
:                   :
END
echo 
#!/bin/bash
exec 6<>$1
cat <<'END' >&6
:                   :
:    /\_/\          :
:   / 0 0 \         :
:  ====v==== Catcat :   
:   \  W  /   ____  :
:    |   |   /      :
:   / ___ \  |      :
:  / /   \ \-'      :
: (((-----)))       :
:                   :
END
exec 6>&-
#!/bin/bash

cat <<'END' |nc $1 $2
:                   :
:    /\_/\          :
:   / 0 0 \         :
:  ====v==== Netcat :   
:   \  W  /   ____  :
:    |   |   /      :
:   / ___ \  |      :
:  / /   \ \-'      :
: (((-----)))       :
:                   :
END



To grab these off this site, get an editor that will save the file with "Unix End-of-Line" on your host system: Notepad++ from portableapps.com is recommended. Copy the script from the webpage and paste it into a blank document, making sure there is NO WHITESPACE BEFORE #!/bin/bash (no spaces, tabs or empty lines!). Then check Edit::EOL Conversion on the Notepad++ menu to make sure it says UNIX (LF) before you save it.

To put the script on your TinyNet VM, check the Shared Folders page under Orientation on the menu. Using a SSH Client on the Host is easy and reliable: get WinSCP from PortableApps.com and install it on your host machine, then connect to the ssh server running on your TinyNet Gateway or Webserver VM (same subnet!).

Web CGI Scripts With bash

Whenever we see a form on a webpage, there is a "CGI script" in the background to process process the data when the browser sends it back to the server. Web CGI programs can be written in any language which can process standard input (stdin), environment variables and write to standard output (stdout). The web server will interact with all CGI programs using the "Common Gateway Interface" (CGI) standard as set by RFC 3875. While bash is definitely not the first choice for web programming, it does illustrate the essential simplicity of html.

CGI programs typically process some input: a URL or form data. This is done with environment variables set by the web server, and the "Form Method" used by the browser to return data to the server.
  • With form method=GET all of the data in the form is passed back in the URL (some like to call this REST or restful). The CGI script processes the environment variable QUERY_STRING, which is set by the server. With this method, all of the form data is clearly visible in the part of the URL after the filename, where ? marks the start of the data, and each name=value pair starts with &

  • With form method=POST the script gets the form data as its stdin (not visible in the URL). The CGI script processes the environment variable CONTENT_LENGTH, which is set by the server, and reads this much data from stdin. The data itself is in the same format as with the GET method: each name=value pair starts with &
Aside from processing the data, all CGI scripts must write out a properly formatted web page that the webserver can pass along to the browser. This includes a header used by the browser to identify the content (which MUST be followed by a blank line), and the HTML content to be viewed. The content typically has the structure of the "head" which contains non-viewable content and "body" which provides the viewable content.

Grab this off this site and replace the test.sh in /var/monkey/htdocs to give it a try.

#!/bin/bash
# yolinux.com/tutorials/BashShellCGI.html
# super sed: Greg Ippolito, 2010

echo "Content-type: text/html"
echo ""

echo '<html><head>'
echo '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">'
echo '<title>Form Example</title>'
echo '</head><body>'

### uncomment *only one* of these two
# echo "<form method=GET action=\"${SCRIPT_NAME}\">"
  echo "<form method=POST action=\"${SCRIPT_NAME}\">"
###

cat <<EOF
<h3>Simple CGI Form with bash</h3>
<table cellpadding="4" nowrap>
<tr>
<td align="right">Username</td><td>
<input type="text" name="nom" size=12>
</td><td>&nbsp;</td>
</tr><tr>
<td align="right">Password</td><td>
<input type="password" name="pwd" size=12 value="">
</td><td>&nbsp;</td>
</tr><tr><td>
<input type="radio" name="opt" value="1" checked> Option 1
</td><td align="center">
<input type="radio" name="opt" value="2"> Option 2
</td><td>
<input type="radio" name="opt" value="3"> Option 3
</td></tr><tr>
<td>&nbsp;</td><td align="right">
<input type="checkbox" name="chk"> Show<br>Extra<br>Info?
</td><td>&nbsp;</td>
</tr><tr>
<td>&nbsp;</td><td>
<input type="submit" value="Process Form">
</td><td>
<input type="reset" value="Reset">
</td></tr>
</table>
</form>
EOF

# ---Process Form---
# when method==GET the server supplies a value for QUERY_STRING
  if [ "$REQUEST_METHOD" != "GET" ]; then
     QUERY_STRING=""
     if [ "$REQUEST_METHOD" != "POST" ]; then
        echo "<hr>Script Error:"\
             "<br>Cannot complete request, REQUEST_METHOD != GET or POST."\
             "<br>Check the METHOD= in your FORM declaration.<hr>"
     else
        if [ "$CONTENT_LENGTH" -gt 0 ]; then
           read -n $CONTENT_LENGTH POST_DATA <&0
           QUERY_STRING=$POST_DATA
        fi
     fi
  fi
#---

# If no form data to process, exit gracefully now.
  if [ -z "$QUERY_STRING" ]; then
     exit 0
  else
  # extract the data we are looking for with sed:
     NN=`echo "$QUERY_STRING" | sed -n 's/^.*nom=\([^&]*\).*$/\1/p' | sed "s/%20/ /g"`
     PW=`echo "$QUERY_STRING" | sed -n 's/^.*pwd=\([^&]*\).*$/\1/p' | sed "s/%20/ /g"`
     ZZ=`echo "$QUERY_STRING" | sed -n 's/^.*opt=\([^&]*\).*$/\1/p' | sed "s/%20/ /g"`
     CB=`echo "$QUERY_STRING" | sed -n 's/^.*chk=\([^&]*\).*$/\1/p' | sed "s/%20/ /g"`

     if [ -z "$NN" ]; then XX='<i>(empty)</i>'; fi
     if [ -z "$PW" ]; then YY='<i>(empty)</i>'; fi
     if [ -z "$CB" ]; then UU='<i>(off)</i>'; fi

     echo "Username was: " $NN
     echo '&nbsp;&nbsp;&nbsp;&nbsp;'
     echo "Password was: " $PW
     echo '&nbsp;&nbsp;&nbsp;&nbsp;'
     echo "Option was: " $ZZ
     echo '&nbsp;&nbsp;&nbsp;&nbsp;'
     echo "Check status: " $CB

     if [ "$CB" == "on" ]; then 
        echo '<br><br><b>Environment Variables:</b>'
        echo '<pre>'
        /usr/bin/env
        echo '</pre>'
     fi
  fi

echo '</body>'
echo '</html>'
exit 0
Warning: The sed commands here simply parse the variable names and values, and replace the html escaped space %20 with a normal space character. It is important to remember that user input always must be pre-processed to avoid trouble from hackers. For web pages, it is critical to filter the input to avoid cross site scripting: don't allow / changed to space, and filter out < > & * ? . / \