14554 lines
376 KiB
HTML
14554 lines
376 KiB
HTML
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
|
|||
|
<HTML
|
|||
|
><HEAD
|
|||
|
><TITLE
|
|||
|
>Contributed Scripts</TITLE
|
|||
|
><META
|
|||
|
NAME="GENERATOR"
|
|||
|
CONTENT="Modular DocBook HTML Stylesheet Version 1.7"><LINK
|
|||
|
REL="HOME"
|
|||
|
TITLE="Advanced Bash-Scripting Guide"
|
|||
|
HREF="index.html"><LINK
|
|||
|
REL="PREVIOUS"
|
|||
|
TITLE="Bibliography"
|
|||
|
HREF="biblio.html"><LINK
|
|||
|
REL="NEXT"
|
|||
|
TITLE="Reference Cards"
|
|||
|
HREF="refcards.html"></HEAD
|
|||
|
><BODY
|
|||
|
CLASS="APPENDIX"
|
|||
|
BGCOLOR="#FFFFFF"
|
|||
|
TEXT="#000000"
|
|||
|
LINK="#0000FF"
|
|||
|
VLINK="#840084"
|
|||
|
ALINK="#0000FF"
|
|||
|
><DIV
|
|||
|
CLASS="NAVHEADER"
|
|||
|
><TABLE
|
|||
|
SUMMARY="Header navigation table"
|
|||
|
WIDTH="100%"
|
|||
|
BORDER="0"
|
|||
|
CELLPADDING="0"
|
|||
|
CELLSPACING="0"
|
|||
|
><TR
|
|||
|
><TH
|
|||
|
COLSPAN="3"
|
|||
|
ALIGN="center"
|
|||
|
>Advanced Bash-Scripting Guide: </TH
|
|||
|
></TR
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
WIDTH="10%"
|
|||
|
ALIGN="left"
|
|||
|
VALIGN="bottom"
|
|||
|
><A
|
|||
|
HREF="biblio.html"
|
|||
|
ACCESSKEY="P"
|
|||
|
>Prev</A
|
|||
|
></TD
|
|||
|
><TD
|
|||
|
WIDTH="80%"
|
|||
|
ALIGN="center"
|
|||
|
VALIGN="bottom"
|
|||
|
></TD
|
|||
|
><TD
|
|||
|
WIDTH="10%"
|
|||
|
ALIGN="right"
|
|||
|
VALIGN="bottom"
|
|||
|
><A
|
|||
|
HREF="refcards.html"
|
|||
|
ACCESSKEY="N"
|
|||
|
>Next</A
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
><HR
|
|||
|
ALIGN="LEFT"
|
|||
|
WIDTH="100%"></DIV
|
|||
|
><DIV
|
|||
|
CLASS="APPENDIX"
|
|||
|
><H1
|
|||
|
><A
|
|||
|
NAME="CONTRIBUTED-SCRIPTS"
|
|||
|
></A
|
|||
|
>Appendix A. Contributed Scripts</H1
|
|||
|
><P
|
|||
|
>These scripts, while not fitting into the text of this document, do
|
|||
|
illustrate some interesting shell programming techniques. Some are useful,
|
|||
|
too. Have fun analyzing and running them.</P
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="MAILFORMAT"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-1. <I
|
|||
|
CLASS="FIRSTTERM"
|
|||
|
>mailformat</I
|
|||
|
>: Formatting an e-mail
|
|||
|
message</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
# mail-format.sh (ver. 1.1): Format e-mail messages.
|
|||
|
|
|||
|
# Gets rid of carets, tabs, and also folds excessively long lines.
|
|||
|
|
|||
|
# =================================================================
|
|||
|
# Standard Check for Script Argument(s)
|
|||
|
ARGS=1
|
|||
|
E_BADARGS=85
|
|||
|
E_NOFILE=86
|
|||
|
|
|||
|
if [ $# -ne $ARGS ] # Correct number of arguments passed to script?
|
|||
|
then
|
|||
|
echo "Usage: `basename $0` filename"
|
|||
|
exit $E_BADARGS
|
|||
|
fi
|
|||
|
|
|||
|
if [ -f "$1" ] # Check if file exists.
|
|||
|
then
|
|||
|
file_name=$1
|
|||
|
else
|
|||
|
echo "File \"$1\" does not exist."
|
|||
|
exit $E_NOFILE
|
|||
|
fi
|
|||
|
# -----------------------------------------------------------------
|
|||
|
|
|||
|
MAXWIDTH=70 # Width to fold excessively long lines to.
|
|||
|
|
|||
|
# =================================
|
|||
|
# A variable can hold a sed script.
|
|||
|
# It's a useful technique.
|
|||
|
sedscript='s/^>//
|
|||
|
s/^ *>//
|
|||
|
s/^ *//
|
|||
|
s/ *//'
|
|||
|
# =================================
|
|||
|
|
|||
|
# Delete carets and tabs at beginning of lines,
|
|||
|
#+ then fold lines to $MAXWIDTH characters.
|
|||
|
sed "$sedscript" $1 | fold -s --width=$MAXWIDTH
|
|||
|
# -s option to "fold"
|
|||
|
#+ breaks lines at whitespace, if possible.
|
|||
|
|
|||
|
|
|||
|
# This script was inspired by an article in a well-known trade journal
|
|||
|
#+ extolling a 164K MS Windows utility with similar functionality.
|
|||
|
#
|
|||
|
# An nice set of text processing utilities and an efficient
|
|||
|
#+ scripting language provide an alternative to the bloated executables
|
|||
|
#+ of a clunky operating system.
|
|||
|
|
|||
|
exit $?</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="RN"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-2. <I
|
|||
|
CLASS="FIRSTTERM"
|
|||
|
>rn</I
|
|||
|
>: A simple-minded file renaming
|
|||
|
utility</B
|
|||
|
></P
|
|||
|
><P
|
|||
|
>This script is a modification of <A
|
|||
|
HREF="textproc.html#LOWERCASE"
|
|||
|
>Example 16-22</A
|
|||
|
>.</P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#! /bin/bash
|
|||
|
# rn.sh
|
|||
|
|
|||
|
# Very simpleminded filename "rename" utility (based on "lowercase.sh").
|
|||
|
#
|
|||
|
# The "ren" utility, by Vladimir Lanin (lanin@csd2.nyu.edu),
|
|||
|
#+ does a much better job of this.
|
|||
|
|
|||
|
|
|||
|
ARGS=2
|
|||
|
E_BADARGS=85
|
|||
|
ONE=1 # For getting singular/plural right (see below).
|
|||
|
|
|||
|
if [ $# -ne "$ARGS" ]
|
|||
|
then
|
|||
|
echo "Usage: `basename $0` old-pattern new-pattern"
|
|||
|
# As in "rn gif jpg", which renames all gif files in working directory to jpg.
|
|||
|
exit $E_BADARGS
|
|||
|
fi
|
|||
|
|
|||
|
number=0 # Keeps track of how many files actually renamed.
|
|||
|
|
|||
|
|
|||
|
for filename in *$1* #Traverse all matching files in directory.
|
|||
|
do
|
|||
|
if [ -f "$filename" ] # If finds match...
|
|||
|
then
|
|||
|
fname=`basename $filename` # Strip off path.
|
|||
|
n=`echo $fname | sed -e "s/$1/$2/"` # Substitute new for old in filename.
|
|||
|
mv $fname $n # Rename.
|
|||
|
let "number += 1"
|
|||
|
fi
|
|||
|
done
|
|||
|
|
|||
|
if [ "$number" -eq "$ONE" ] # For correct grammar.
|
|||
|
then
|
|||
|
echo "$number file renamed."
|
|||
|
else
|
|||
|
echo "$number files renamed."
|
|||
|
fi
|
|||
|
|
|||
|
exit $?
|
|||
|
|
|||
|
|
|||
|
# Exercises:
|
|||
|
# ---------
|
|||
|
# What types of files will this not work on?
|
|||
|
# How can this be fixed?</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="BLANKRENAME"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-3. <I
|
|||
|
CLASS="FIRSTTERM"
|
|||
|
>blank-rename</I
|
|||
|
>: Renames filenames containing
|
|||
|
blanks</B
|
|||
|
></P
|
|||
|
><P
|
|||
|
>This is an even simpler-minded version of previous script.</P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#! /bin/bash
|
|||
|
# blank-rename.sh
|
|||
|
#
|
|||
|
# Substitutes underscores for blanks in all the filenames in a directory.
|
|||
|
|
|||
|
ONE=1 # For getting singular/plural right (see below).
|
|||
|
number=0 # Keeps track of how many files actually renamed.
|
|||
|
FOUND=0 # Successful return value.
|
|||
|
|
|||
|
for filename in * #Traverse all files in directory.
|
|||
|
do
|
|||
|
echo "$filename" | grep -q " " # Check whether filename
|
|||
|
if [ $? -eq $FOUND ] #+ contains space(s).
|
|||
|
then
|
|||
|
fname=$filename # Yes, this filename needs work.
|
|||
|
n=`echo $fname | sed -e "s/ /_/g"` # Substitute underscore for blank.
|
|||
|
mv "$fname" "$n" # Do the actual renaming.
|
|||
|
let "number += 1"
|
|||
|
fi
|
|||
|
done
|
|||
|
|
|||
|
if [ "$number" -eq "$ONE" ] # For correct grammar.
|
|||
|
then
|
|||
|
echo "$number file renamed."
|
|||
|
else
|
|||
|
echo "$number files renamed."
|
|||
|
fi
|
|||
|
|
|||
|
exit 0</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="ENCRYPTEDPW"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-4. <I
|
|||
|
CLASS="FIRSTTERM"
|
|||
|
>encryptedpw</I
|
|||
|
>: Uploading to an ftp site,
|
|||
|
using a locally encrypted password</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
|
|||
|
# Example "ex72.sh" modified to use encrypted password.
|
|||
|
|
|||
|
# Note that this is still rather insecure,
|
|||
|
#+ since the decrypted password is sent in the clear.
|
|||
|
# Use something like "ssh" if this is a concern.
|
|||
|
|
|||
|
E_BADARGS=85
|
|||
|
|
|||
|
if [ -z "$1" ]
|
|||
|
then
|
|||
|
echo "Usage: `basename $0` filename"
|
|||
|
exit $E_BADARGS
|
|||
|
fi
|
|||
|
|
|||
|
Username=bozo # Change to suit.
|
|||
|
pword=/home/bozo/secret/password_encrypted.file
|
|||
|
# File containing encrypted password.
|
|||
|
|
|||
|
Filename=`basename $1` # Strips pathname out of file name.
|
|||
|
|
|||
|
Server="XXX"
|
|||
|
Directory="YYY" # Change above to actual server name & directory.
|
|||
|
|
|||
|
|
|||
|
Password=`cruft <$pword` # Decrypt password.
|
|||
|
# Uses the author's own "cruft" file encryption package,
|
|||
|
#+ based on the classic "onetime pad" algorithm,
|
|||
|
#+ and obtainable from:
|
|||
|
#+ Primary-site: ftp://ibiblio.org/pub/Linux/utils/file
|
|||
|
#+ cruft-0.2.tar.gz [16k]
|
|||
|
|
|||
|
|
|||
|
ftp -n $Server <<End-Of-Session
|
|||
|
user $Username $Password
|
|||
|
binary
|
|||
|
bell
|
|||
|
cd $Directory
|
|||
|
put $Filename
|
|||
|
bye
|
|||
|
End-Of-Session
|
|||
|
# -n option to "ftp" disables auto-logon.
|
|||
|
# Note that "bell" rings 'bell' after each file transfer.
|
|||
|
|
|||
|
exit 0</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="COPYCD"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-5. <I
|
|||
|
CLASS="FIRSTTERM"
|
|||
|
>copy-cd</I
|
|||
|
>: Copying a data CD</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
# copy-cd.sh: copying a data CD
|
|||
|
|
|||
|
CDROM=/dev/cdrom # CD ROM device
|
|||
|
OF=/home/bozo/projects/cdimage.iso # output file
|
|||
|
# /xxxx/xxxxxxxx/ Change to suit your system.
|
|||
|
BLOCKSIZE=2048
|
|||
|
# SPEED=10 # If unspecified, uses max spd.
|
|||
|
# DEVICE=/dev/cdrom older version.
|
|||
|
DEVICE="1,0,0"
|
|||
|
|
|||
|
echo; echo "Insert source CD, but do *not* mount it."
|
|||
|
echo "Press ENTER when ready. "
|
|||
|
read ready # Wait for input, $ready not used.
|
|||
|
|
|||
|
echo; echo "Copying the source CD to $OF."
|
|||
|
echo "This may take a while. Please be patient."
|
|||
|
|
|||
|
dd if=$CDROM of=$OF bs=$BLOCKSIZE # Raw device copy.
|
|||
|
|
|||
|
|
|||
|
echo; echo "Remove data CD."
|
|||
|
echo "Insert blank CDR."
|
|||
|
echo "Press ENTER when ready. "
|
|||
|
read ready # Wait for input, $ready not used.
|
|||
|
|
|||
|
echo "Copying $OF to CDR."
|
|||
|
|
|||
|
# cdrecord -v -isosize speed=$SPEED dev=$DEVICE $OF # Old version.
|
|||
|
wodim -v -isosize dev=$DEVICE $OF
|
|||
|
# Uses Joerg Schilling's "cdrecord" package (see its docs).
|
|||
|
# http://www.fokus.gmd.de/nthp/employees/schilling/cdrecord.html
|
|||
|
# Newer Linux distros may use "wodim" rather than "cdrecord" ...
|
|||
|
|
|||
|
|
|||
|
echo; echo "Done copying $OF to CDR on device $CDROM."
|
|||
|
|
|||
|
echo "Do you want to erase the image file (y/n)? " # Probably a huge file.
|
|||
|
read answer
|
|||
|
|
|||
|
case "$answer" in
|
|||
|
[yY]) rm -f $OF
|
|||
|
echo "$OF erased."
|
|||
|
;;
|
|||
|
*) echo "$OF not erased.";;
|
|||
|
esac
|
|||
|
|
|||
|
echo
|
|||
|
|
|||
|
# Exercise:
|
|||
|
# Change the above "case" statement to also accept "yes" and "Yes" as input.
|
|||
|
|
|||
|
exit 0</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="COLLATZ"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-6. Collatz series</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
# collatz.sh
|
|||
|
|
|||
|
# The notorious "hailstone" or Collatz series.
|
|||
|
# -------------------------------------------
|
|||
|
# 1) Get the integer "seed" from the command-line.
|
|||
|
# 2) NUMBER <-- seed
|
|||
|
# 3) Print NUMBER.
|
|||
|
# 4) If NUMBER is even, divide by 2, or
|
|||
|
# 5)+ if odd, multiply by 3 and add 1.
|
|||
|
# 6) NUMBER <-- result
|
|||
|
# 7) Loop back to step 3 (for specified number of iterations).
|
|||
|
#
|
|||
|
# The theory is that every such sequence,
|
|||
|
#+ no matter how large the initial value,
|
|||
|
#+ eventually settles down to repeating "4,2,1..." cycles,
|
|||
|
#+ even after fluctuating through a wide range of values.
|
|||
|
#
|
|||
|
# This is an instance of an "iterate,"
|
|||
|
#+ an operation that feeds its output back into its input.
|
|||
|
# Sometimes the result is a "chaotic" series.
|
|||
|
|
|||
|
|
|||
|
MAX_ITERATIONS=200
|
|||
|
# For large seed numbers (>32000), try increasing MAX_ITERATIONS.
|
|||
|
|
|||
|
h=${1:-$$} # Seed.
|
|||
|
# Use $PID as seed,
|
|||
|
#+ if not specified as command-line arg.
|
|||
|
|
|||
|
echo
|
|||
|
echo "C($h) -*- $MAX_ITERATIONS Iterations"
|
|||
|
echo
|
|||
|
|
|||
|
for ((i=1; i<=MAX_ITERATIONS; i++))
|
|||
|
do
|
|||
|
|
|||
|
# echo -n "$h "
|
|||
|
# ^^^
|
|||
|
# tab
|
|||
|
# printf does it better ...
|
|||
|
COLWIDTH=%7d
|
|||
|
printf $COLWIDTH $h
|
|||
|
|
|||
|
let "remainder = h % 2"
|
|||
|
if [ "$remainder" -eq 0 ] # Even?
|
|||
|
then
|
|||
|
let "h /= 2" # Divide by 2.
|
|||
|
else
|
|||
|
let "h = h*3 + 1" # Multiply by 3 and add 1.
|
|||
|
fi
|
|||
|
|
|||
|
|
|||
|
COLUMNS=10 # Output 10 values per line.
|
|||
|
let "line_break = i % $COLUMNS"
|
|||
|
if [ "$line_break" -eq 0 ]
|
|||
|
then
|
|||
|
echo
|
|||
|
fi
|
|||
|
|
|||
|
done
|
|||
|
|
|||
|
echo
|
|||
|
|
|||
|
# For more information on this strange mathematical function,
|
|||
|
#+ see _Computers, Pattern, Chaos, and Beauty_, by Pickover, p. 185 ff.,
|
|||
|
#+ as listed in the bibliography.
|
|||
|
|
|||
|
exit 0</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><P
|
|||
|
><A
|
|||
|
NAME="DAYSBETWEEN0"
|
|||
|
></A
|
|||
|
></P
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="DAYSBETWEEN"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-7. <I
|
|||
|
CLASS="FIRSTTERM"
|
|||
|
>days-between</I
|
|||
|
>: Days between two
|
|||
|
dates</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
# days-between.sh: Number of days between two dates.
|
|||
|
# Usage: ./days-between.sh [M]M/[D]D/YYYY [M]M/[D]D/YYYY
|
|||
|
#
|
|||
|
# Note: Script modified to account for changes in Bash, v. 2.05b +,
|
|||
|
#+ that closed the loophole permitting large negative
|
|||
|
#+ integer return values.
|
|||
|
|
|||
|
ARGS=2 # Two command-line parameters expected.
|
|||
|
E_PARAM_ERR=85 # Param error.
|
|||
|
|
|||
|
REFYR=1600 # Reference year.
|
|||
|
CENTURY=100
|
|||
|
DIY=365
|
|||
|
ADJ_DIY=367 # Adjusted for leap year + fraction.
|
|||
|
MIY=12
|
|||
|
DIM=31
|
|||
|
LEAPCYCLE=4
|
|||
|
|
|||
|
MAXRETVAL=255 # Largest permissible
|
|||
|
#+ positive return value from a function.
|
|||
|
|
|||
|
diff= # Declare global variable for date difference.
|
|||
|
value= # Declare global variable for absolute value.
|
|||
|
day= # Declare globals for day, month, year.
|
|||
|
month=
|
|||
|
year=
|
|||
|
|
|||
|
|
|||
|
Param_Error () # Command-line parameters wrong.
|
|||
|
{
|
|||
|
echo "Usage: `basename $0` [M]M/[D]D/YYYY [M]M/[D]D/YYYY"
|
|||
|
echo " (date must be after 1/3/1600)"
|
|||
|
exit $E_PARAM_ERR
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
Parse_Date () # Parse date from command-line params.
|
|||
|
{
|
|||
|
month=${1%%/**}
|
|||
|
dm=${1%/**} # Day and month.
|
|||
|
day=${dm#*/}
|
|||
|
let "year = `basename $1`" # Not a filename, but works just the same.
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
check_date () # Checks for invalid date(s) passed.
|
|||
|
{
|
|||
|
[ "$day" -gt "$DIM" ] || [ "$month" -gt "$MIY" ] ||
|
|||
|
[ "$year" -lt "$REFYR" ] && Param_Error
|
|||
|
# Exit script on bad value(s).
|
|||
|
# Uses or-list / and-list.
|
|||
|
#
|
|||
|
# Exercise: Implement more rigorous date checking.
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
strip_leading_zero () # Better to strip possible leading zero(s)
|
|||
|
{ #+ from day and/or month
|
|||
|
return ${1#0} #+ since otherwise Bash will interpret them
|
|||
|
} #+ as octal values (POSIX.2, sect 2.9.2.1).
|
|||
|
|
|||
|
|
|||
|
day_index () # Gauss' Formula:
|
|||
|
{ # Days from March 1, 1600 to date passed as param.
|
|||
|
# ^^^^^^^^^^^^^
|
|||
|
day=$1
|
|||
|
month=$2
|
|||
|
year=$3
|
|||
|
|
|||
|
let "month = $month - 2"
|
|||
|
if [ "$month" -le 0 ]
|
|||
|
then
|
|||
|
let "month += 12"
|
|||
|
let "year -= 1"
|
|||
|
fi
|
|||
|
|
|||
|
let "year -= $REFYR"
|
|||
|
let "indexyr = $year / $CENTURY"
|
|||
|
|
|||
|
|
|||
|
let "Days = $DIY*$year + $year/$LEAPCYCLE - $indexyr \
|
|||
|
+ $indexyr/$LEAPCYCLE + $ADJ_DIY*$month/$MIY + $day - $DIM"
|
|||
|
# For an in-depth explanation of this algorithm, see
|
|||
|
#+ http://weblogs.asp.net/pgreborio/archive/2005/01/06/347968.aspx
|
|||
|
|
|||
|
|
|||
|
echo $Days
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
calculate_difference () # Difference between two day indices.
|
|||
|
{
|
|||
|
let "diff = $1 - $2" # Global variable.
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
abs () # Absolute value
|
|||
|
{ # Uses global "value" variable.
|
|||
|
if [ "$1" -lt 0 ] # If negative
|
|||
|
then #+ then
|
|||
|
let "value = 0 - $1" #+ change sign,
|
|||
|
else #+ else
|
|||
|
let "value = $1" #+ leave it alone.
|
|||
|
fi
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
if [ $# -ne "$ARGS" ] # Require two command-line params.
|
|||
|
then
|
|||
|
Param_Error
|
|||
|
fi
|
|||
|
|
|||
|
Parse_Date $1
|
|||
|
check_date $day $month $year # See if valid date.
|
|||
|
|
|||
|
strip_leading_zero $day # Remove any leading zeroes
|
|||
|
day=$? #+ on day and/or month.
|
|||
|
strip_leading_zero $month
|
|||
|
month=$?
|
|||
|
|
|||
|
let "date1 = `day_index $day $month $year`"
|
|||
|
|
|||
|
|
|||
|
Parse_Date $2
|
|||
|
check_date $day $month $year
|
|||
|
|
|||
|
strip_leading_zero $day
|
|||
|
day=$?
|
|||
|
strip_leading_zero $month
|
|||
|
month=$?
|
|||
|
|
|||
|
date2=$(day_index $day $month $year) # Command substitution.
|
|||
|
|
|||
|
|
|||
|
calculate_difference $date1 $date2
|
|||
|
|
|||
|
abs $diff # Make sure it's positive.
|
|||
|
diff=$value
|
|||
|
|
|||
|
echo $diff
|
|||
|
|
|||
|
exit 0
|
|||
|
|
|||
|
# Exercise:
|
|||
|
# --------
|
|||
|
# If given only one command-line parameter, have the script
|
|||
|
#+ use today's date as the second.
|
|||
|
|
|||
|
|
|||
|
# Compare this script with
|
|||
|
#+ the implementation of Gauss' Formula in a C program at
|
|||
|
#+ http://buschencrew.hypermart.net/software/datedif</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="MAKEDICT"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-8. Making a <I
|
|||
|
CLASS="FIRSTTERM"
|
|||
|
>dictionary</I
|
|||
|
></B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
# makedict.sh [make dictionary]
|
|||
|
|
|||
|
# Modification of /usr/sbin/mkdict (/usr/sbin/cracklib-forman) script.
|
|||
|
# Original script copyright 1993, by Alec Muffett.
|
|||
|
#
|
|||
|
# This modified script included in this document in a manner
|
|||
|
#+ consistent with the "LICENSE" document of the "Crack" package
|
|||
|
#+ that the original script is a part of.
|
|||
|
|
|||
|
# This script processes text files to produce a sorted list
|
|||
|
#+ of words found in the files.
|
|||
|
# This may be useful for compiling dictionaries
|
|||
|
#+ and for other lexicographic purposes.
|
|||
|
|
|||
|
|
|||
|
E_BADARGS=85
|
|||
|
|
|||
|
if [ ! -r "$1" ] # Need at least one
|
|||
|
then #+ valid file argument.
|
|||
|
echo "Usage: $0 files-to-process"
|
|||
|
exit $E_BADARGS
|
|||
|
fi
|
|||
|
|
|||
|
|
|||
|
# SORT="sort" # No longer necessary to define
|
|||
|
#+ options to sort. Changed from
|
|||
|
#+ original script.
|
|||
|
|
|||
|
cat $* | # Dump specified files to stdout.
|
|||
|
tr A-Z a-z | # Convert to lowercase.
|
|||
|
tr ' ' '\012' | # New: change spaces to newlines.
|
|||
|
# tr -cd '\012[a-z][0-9]' | # Get rid of everything
|
|||
|
#+ non-alphanumeric (in orig. script).
|
|||
|
tr -c '\012a-z' '\012' | # Rather than deleting non-alpha
|
|||
|
#+ chars, change them to newlines.
|
|||
|
sort | # $SORT options unnecessary now.
|
|||
|
uniq | # Remove duplicates.
|
|||
|
grep -v '^#' | # Delete lines starting with #.
|
|||
|
grep -v '^$' # Delete blank lines.
|
|||
|
|
|||
|
exit $?</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><P
|
|||
|
><A
|
|||
|
NAME="SOUNDEX0"
|
|||
|
></A
|
|||
|
></P
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="SOUNDEX"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-9. Soundex conversion</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
# soundex.sh: Calculate "soundex" code for names
|
|||
|
|
|||
|
# =======================================================
|
|||
|
# Soundex script
|
|||
|
# by
|
|||
|
# Mendel Cooper
|
|||
|
# thegrendel.abs@gmail.com
|
|||
|
# reldate: 23 January, 2002
|
|||
|
#
|
|||
|
# Placed in the Public Domain.
|
|||
|
#
|
|||
|
# A slightly different version of this script appeared in
|
|||
|
#+ Ed Schaefer's July, 2002 "Shell Corner" column
|
|||
|
#+ in "Unix Review" on-line,
|
|||
|
#+ http://www.unixreview.com/documents/uni1026336632258/
|
|||
|
# =======================================================
|
|||
|
|
|||
|
|
|||
|
ARGCOUNT=1 # Need name as argument.
|
|||
|
E_WRONGARGS=90
|
|||
|
|
|||
|
if [ $# -ne "$ARGCOUNT" ]
|
|||
|
then
|
|||
|
echo "Usage: `basename $0` name"
|
|||
|
exit $E_WRONGARGS
|
|||
|
fi
|
|||
|
|
|||
|
|
|||
|
assign_value () # Assigns numerical value
|
|||
|
{ #+ to letters of name.
|
|||
|
|
|||
|
val1=bfpv # 'b,f,p,v' = 1
|
|||
|
val2=cgjkqsxz # 'c,g,j,k,q,s,x,z' = 2
|
|||
|
val3=dt # etc.
|
|||
|
val4=l
|
|||
|
val5=mn
|
|||
|
val6=r
|
|||
|
|
|||
|
# Exceptionally clever use of 'tr' follows.
|
|||
|
# Try to figure out what is going on here.
|
|||
|
|
|||
|
value=$( echo "$1" \
|
|||
|
| tr -d wh \
|
|||
|
| tr $val1 1 | tr $val2 2 | tr $val3 3 \
|
|||
|
| tr $val4 4 | tr $val5 5 | tr $val6 6 \
|
|||
|
| tr -s 123456 \
|
|||
|
| tr -d aeiouy )
|
|||
|
|
|||
|
# Assign letter values.
|
|||
|
# Remove duplicate numbers, except when separated by vowels.
|
|||
|
# Ignore vowels, except as separators, so delete them last.
|
|||
|
# Ignore 'w' and 'h', even as separators, so delete them first.
|
|||
|
#
|
|||
|
# The above command substitution lays more pipe than a plumber <g>.
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
input_name="$1"
|
|||
|
echo
|
|||
|
echo "Name = $input_name"
|
|||
|
|
|||
|
|
|||
|
# Change all characters of name input to lowercase.
|
|||
|
# ------------------------------------------------
|
|||
|
name=$( echo $input_name | tr A-Z a-z )
|
|||
|
# ------------------------------------------------
|
|||
|
# Just in case argument to script is mixed case.
|
|||
|
|
|||
|
|
|||
|
# Prefix of soundex code: first letter of name.
|
|||
|
# --------------------------------------------
|
|||
|
|
|||
|
|
|||
|
char_pos=0 # Initialize character position.
|
|||
|
prefix0=${name:$char_pos:1}
|
|||
|
prefix=`echo $prefix0 | tr a-z A-Z`
|
|||
|
# Uppercase 1st letter of soundex.
|
|||
|
|
|||
|
let "char_pos += 1" # Bump character position to 2nd letter of name.
|
|||
|
name1=${name:$char_pos}
|
|||
|
|
|||
|
|
|||
|
# ++++++++++++++++++++++++++ Exception Patch ++++++++++++++++++++++++++++++
|
|||
|
# Now, we run both the input name and the name shifted one char
|
|||
|
#+ to the right through the value-assigning function.
|
|||
|
# If we get the same value out, that means that the first two characters
|
|||
|
#+ of the name have the same value assigned, and that one should cancel.
|
|||
|
# However, we also need to test whether the first letter of the name
|
|||
|
#+ is a vowel or 'w' or 'h', because otherwise this would bollix things up.
|
|||
|
|
|||
|
char1=`echo $prefix | tr A-Z a-z` # First letter of name, lowercased.
|
|||
|
|
|||
|
assign_value $name
|
|||
|
s1=$value
|
|||
|
assign_value $name1
|
|||
|
s2=$value
|
|||
|
assign_value $char1
|
|||
|
s3=$value
|
|||
|
s3=9$s3 # If first letter of name is a vowel
|
|||
|
#+ or 'w' or 'h',
|
|||
|
#+ then its "value" will be null (unset).
|
|||
|
#+ Therefore, set it to 9, an otherwise
|
|||
|
#+ unused value, which can be tested for.
|
|||
|
|
|||
|
|
|||
|
if [[ "$s1" -ne "$s2" || "$s3" -eq 9 ]]
|
|||
|
then
|
|||
|
suffix=$s2
|
|||
|
else
|
|||
|
suffix=${s2:$char_pos}
|
|||
|
fi
|
|||
|
# ++++++++++++++++++++++ end Exception Patch ++++++++++++++++++++++++++++++
|
|||
|
|
|||
|
|
|||
|
padding=000 # Use at most 3 zeroes to pad.
|
|||
|
|
|||
|
|
|||
|
soun=$prefix$suffix$padding # Pad with zeroes.
|
|||
|
|
|||
|
MAXLEN=4 # Truncate to maximum of 4 chars.
|
|||
|
soundex=${soun:0:$MAXLEN}
|
|||
|
|
|||
|
echo "Soundex = $soundex"
|
|||
|
|
|||
|
echo
|
|||
|
|
|||
|
# The soundex code is a method of indexing and classifying names
|
|||
|
#+ by grouping together the ones that sound alike.
|
|||
|
# The soundex code for a given name is the first letter of the name,
|
|||
|
#+ followed by a calculated three-number code.
|
|||
|
# Similar sounding names should have almost the same soundex codes.
|
|||
|
|
|||
|
# Examples:
|
|||
|
# Smith and Smythe both have a "S-530" soundex.
|
|||
|
# Harrison = H-625
|
|||
|
# Hargison = H-622
|
|||
|
# Harriman = H-655
|
|||
|
|
|||
|
# This works out fairly well in practice, but there are numerous anomalies.
|
|||
|
#
|
|||
|
#
|
|||
|
# The U.S. Census and certain other governmental agencies use soundex,
|
|||
|
# as do genealogical researchers.
|
|||
|
#
|
|||
|
# For more information,
|
|||
|
#+ see the "National Archives and Records Administration home page",
|
|||
|
#+ http://www.nara.gov/genealogy/soundex/soundex.html
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# Exercise:
|
|||
|
# --------
|
|||
|
# Simplify the "Exception Patch" section of this script.
|
|||
|
|
|||
|
exit 0</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><P
|
|||
|
><A
|
|||
|
NAME="LIFEREF"
|
|||
|
></A
|
|||
|
></P
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="LIFESLOW"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-10. <I
|
|||
|
CLASS="FIRSTTERM"
|
|||
|
>Game of Life</I
|
|||
|
></B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
# life.sh: "Life in the Slow Lane"
|
|||
|
# Author: Mendel Cooper
|
|||
|
# License: GPL3
|
|||
|
|
|||
|
# Version 0.2: Patched by Daniel Albers
|
|||
|
#+ to allow non-square grids as input.
|
|||
|
# Version 0.2.1: Added 2-second delay between generations.
|
|||
|
|
|||
|
# ##################################################################### #
|
|||
|
# This is the Bash script version of John Conway's "Game of Life". #
|
|||
|
# "Life" is a simple implementation of cellular automata. #
|
|||
|
# --------------------------------------------------------------------- #
|
|||
|
# On a rectangular grid, let each "cell" be either "living" or "dead." #
|
|||
|
# Designate a living cell with a dot, and a dead one with a blank space.#
|
|||
|
# Begin with an arbitrarily drawn dot-and-blank grid, #
|
|||
|
#+ and let this be the starting generation: generation 0. #
|
|||
|
# Determine each successive generation by the following rules: #
|
|||
|
# 1) Each cell has 8 neighbors, the adjoining cells #
|
|||
|
#+ left, right, top, bottom, and the 4 diagonals. #
|
|||
|
# #
|
|||
|
# 123 #
|
|||
|
# 4*5 The * is the cell under consideration. #
|
|||
|
# 678 #
|
|||
|
# #
|
|||
|
# 2) A living cell with either 2 or 3 living neighbors remains alive. #
|
|||
|
SURVIVE=2 #
|
|||
|
# 3) A dead cell with 3 living neighbors comes alive, a "birth." #
|
|||
|
BIRTH=3 #
|
|||
|
# 4) All other cases result in a dead cell for the next generation. #
|
|||
|
# ##################################################################### #
|
|||
|
|
|||
|
|
|||
|
startfile=gen0 # Read the starting generation from the file "gen0" ...
|
|||
|
# Default, if no other file specified when invoking script.
|
|||
|
#
|
|||
|
if [ -n "$1" ] # Specify another "generation 0" file.
|
|||
|
then
|
|||
|
startfile="$1"
|
|||
|
fi
|
|||
|
|
|||
|
############################################
|
|||
|
# Abort script if "startfile" not specified
|
|||
|
#+ and
|
|||
|
#+ default file "gen0" not present.
|
|||
|
|
|||
|
E_NOSTARTFILE=86
|
|||
|
|
|||
|
if [ ! -e "$startfile" ]
|
|||
|
then
|
|||
|
echo "Startfile \""$startfile"\" missing!"
|
|||
|
exit $E_NOSTARTFILE
|
|||
|
fi
|
|||
|
############################################
|
|||
|
|
|||
|
|
|||
|
ALIVE1=.
|
|||
|
DEAD1=_
|
|||
|
# Represent living and dead cells in the start-up file.
|
|||
|
|
|||
|
# -----------------------------------------------------#
|
|||
|
# This script uses a 10 x 10 grid (may be increased,
|
|||
|
#+ but a large grid will slow down execution).
|
|||
|
ROWS=10
|
|||
|
COLS=10
|
|||
|
# Change above two variables to match desired grid size.
|
|||
|
# -----------------------------------------------------#
|
|||
|
|
|||
|
GENERATIONS=10 # How many generations to cycle through.
|
|||
|
# Adjust this upwards
|
|||
|
#+ if you have time on your hands.
|
|||
|
|
|||
|
NONE_ALIVE=85 # Exit status on premature bailout,
|
|||
|
#+ if no cells left alive.
|
|||
|
DELAY=2 # Pause between generations.
|
|||
|
TRUE=0
|
|||
|
FALSE=1
|
|||
|
ALIVE=0
|
|||
|
DEAD=1
|
|||
|
|
|||
|
avar= # Global; holds current generation.
|
|||
|
generation=0 # Initialize generation count.
|
|||
|
|
|||
|
# =================================================================
|
|||
|
|
|||
|
let "cells = $ROWS * $COLS" # How many cells.
|
|||
|
|
|||
|
# Arrays containing "cells."
|
|||
|
declare -a initial
|
|||
|
declare -a current
|
|||
|
|
|||
|
display ()
|
|||
|
{
|
|||
|
|
|||
|
alive=0 # How many cells alive at any given time.
|
|||
|
# Initially zero.
|
|||
|
|
|||
|
declare -a arr
|
|||
|
arr=( `echo "$1"` ) # Convert passed arg to array.
|
|||
|
|
|||
|
element_count=${#arr[*]}
|
|||
|
|
|||
|
local i
|
|||
|
local rowcheck
|
|||
|
|
|||
|
for ((i=0; i<$element_count; i++))
|
|||
|
do
|
|||
|
|
|||
|
# Insert newline at end of each row.
|
|||
|
let "rowcheck = $i % COLS"
|
|||
|
if [ "$rowcheck" -eq 0 ]
|
|||
|
then
|
|||
|
echo # Newline.
|
|||
|
echo -n " " # Indent.
|
|||
|
fi
|
|||
|
|
|||
|
cell=${arr[i]}
|
|||
|
|
|||
|
if [ "$cell" = . ]
|
|||
|
then
|
|||
|
let "alive += 1"
|
|||
|
fi
|
|||
|
|
|||
|
echo -n "$cell" | sed -e 's/_/ /g'
|
|||
|
# Print out array, changing underscores to spaces.
|
|||
|
done
|
|||
|
|
|||
|
return
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
IsValid () # Test if cell coordinate valid.
|
|||
|
{
|
|||
|
|
|||
|
if [ -z "$1" -o -z "$2" ] # Mandatory arguments missing?
|
|||
|
then
|
|||
|
return $FALSE
|
|||
|
fi
|
|||
|
|
|||
|
local row
|
|||
|
local lower_limit=0 # Disallow negative coordinate.
|
|||
|
local upper_limit
|
|||
|
local left
|
|||
|
local right
|
|||
|
|
|||
|
let "upper_limit = $ROWS * $COLS - 1" # Total number of cells.
|
|||
|
|
|||
|
|
|||
|
if [ "$1" -lt "$lower_limit" -o "$1" -gt "$upper_limit" ]
|
|||
|
then
|
|||
|
return $FALSE # Out of array bounds.
|
|||
|
fi
|
|||
|
|
|||
|
row=$2
|
|||
|
let "left = $row * $COLS" # Left limit.
|
|||
|
let "right = $left + $COLS - 1" # Right limit.
|
|||
|
|
|||
|
if [ "$1" -lt "$left" -o "$1" -gt "$right" ]
|
|||
|
then
|
|||
|
return $FALSE # Beyond row boundary.
|
|||
|
fi
|
|||
|
|
|||
|
return $TRUE # Valid coordinate.
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
IsAlive () # Test whether cell is alive.
|
|||
|
# Takes array, cell number, and
|
|||
|
{ #+ state of cell as arguments.
|
|||
|
GetCount "$1" $2 # Get alive cell count in neighborhood.
|
|||
|
local nhbd=$?
|
|||
|
|
|||
|
if [ "$nhbd" -eq "$BIRTH" ] # Alive in any case.
|
|||
|
then
|
|||
|
return $ALIVE
|
|||
|
fi
|
|||
|
|
|||
|
if [ "$3" = "." -a "$nhbd" -eq "$SURVIVE" ]
|
|||
|
then # Alive only if previously alive.
|
|||
|
return $ALIVE
|
|||
|
fi
|
|||
|
|
|||
|
return $DEAD # Defaults to dead.
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
GetCount () # Count live cells in passed cell's neighborhood.
|
|||
|
# Two arguments needed:
|
|||
|
# $1) variable holding array
|
|||
|
# $2) cell number
|
|||
|
{
|
|||
|
local cell_number=$2
|
|||
|
local array
|
|||
|
local top
|
|||
|
local center
|
|||
|
local bottom
|
|||
|
local r
|
|||
|
local row
|
|||
|
local i
|
|||
|
local t_top
|
|||
|
local t_cen
|
|||
|
local t_bot
|
|||
|
local count=0
|
|||
|
local ROW_NHBD=3
|
|||
|
|
|||
|
array=( `echo "$1"` )
|
|||
|
|
|||
|
let "top = $cell_number - $COLS - 1" # Set up cell neighborhood.
|
|||
|
let "center = $cell_number - 1"
|
|||
|
let "bottom = $cell_number + $COLS - 1"
|
|||
|
let "r = $cell_number / $COLS"
|
|||
|
|
|||
|
for ((i=0; i<$ROW_NHBD; i++)) # Traverse from left to right.
|
|||
|
do
|
|||
|
let "t_top = $top + $i"
|
|||
|
let "t_cen = $center + $i"
|
|||
|
let "t_bot = $bottom + $i"
|
|||
|
|
|||
|
|
|||
|
let "row = $r" # Count center row.
|
|||
|
IsValid $t_cen $row # Valid cell position?
|
|||
|
if [ $? -eq "$TRUE" ]
|
|||
|
then
|
|||
|
if [ ${array[$t_cen]} = "$ALIVE1" ] # Is it alive?
|
|||
|
then # If yes, then ...
|
|||
|
let "count += 1" # Increment count.
|
|||
|
fi
|
|||
|
fi
|
|||
|
|
|||
|
let "row = $r - 1" # Count top row.
|
|||
|
IsValid $t_top $row
|
|||
|
if [ $? -eq "$TRUE" ]
|
|||
|
then
|
|||
|
if [ ${array[$t_top]} = "$ALIVE1" ] # Redundancy here.
|
|||
|
then # Can it be optimized?
|
|||
|
let "count += 1"
|
|||
|
fi
|
|||
|
fi
|
|||
|
|
|||
|
let "row = $r + 1" # Count bottom row.
|
|||
|
IsValid $t_bot $row
|
|||
|
if [ $? -eq "$TRUE" ]
|
|||
|
then
|
|||
|
if [ ${array[$t_bot]} = "$ALIVE1" ]
|
|||
|
then
|
|||
|
let "count += 1"
|
|||
|
fi
|
|||
|
fi
|
|||
|
|
|||
|
done
|
|||
|
|
|||
|
|
|||
|
if [ ${array[$cell_number]} = "$ALIVE1" ]
|
|||
|
then
|
|||
|
let "count -= 1" # Make sure value of tested cell itself
|
|||
|
fi #+ is not counted.
|
|||
|
|
|||
|
|
|||
|
return $count
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
next_gen () # Update generation array.
|
|||
|
{
|
|||
|
|
|||
|
local array
|
|||
|
local i=0
|
|||
|
|
|||
|
array=( `echo "$1"` ) # Convert passed arg to array.
|
|||
|
|
|||
|
while [ "$i" -lt "$cells" ]
|
|||
|
do
|
|||
|
IsAlive "$1" $i ${array[$i]} # Is the cell alive?
|
|||
|
if [ $? -eq "$ALIVE" ]
|
|||
|
then # If alive, then
|
|||
|
array[$i]=. #+ represent the cell as a period.
|
|||
|
else
|
|||
|
array[$i]="_" # Otherwise underscore
|
|||
|
fi #+ (will later be converted to space).
|
|||
|
let "i += 1"
|
|||
|
done
|
|||
|
|
|||
|
|
|||
|
# let "generation += 1" # Increment generation count.
|
|||
|
### Why was the above line commented out?
|
|||
|
|
|||
|
|
|||
|
# Set variable to pass as parameter to "display" function.
|
|||
|
avar=`echo ${array[@]}` # Convert array back to string variable.
|
|||
|
display "$avar" # Display it.
|
|||
|
echo; echo
|
|||
|
echo "Generation $generation - $alive alive"
|
|||
|
|
|||
|
if [ "$alive" -eq 0 ]
|
|||
|
then
|
|||
|
echo
|
|||
|
echo "Premature exit: no more cells alive!"
|
|||
|
exit $NONE_ALIVE # No point in continuing
|
|||
|
fi #+ if no live cells.
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
# =========================================================
|
|||
|
|
|||
|
# main ()
|
|||
|
# {
|
|||
|
|
|||
|
# Load initial array with contents of startup file.
|
|||
|
initial=( `cat "$startfile" | sed -e '/#/d' | tr -d '\n' |\
|
|||
|
# Delete lines containing '#' comment character.
|
|||
|
sed -e 's/\./\. /g' -e 's/_/_ /g'` )
|
|||
|
# Remove linefeeds and insert space between elements.
|
|||
|
|
|||
|
clear # Clear screen.
|
|||
|
|
|||
|
echo # Title
|
|||
|
setterm -reverse on
|
|||
|
echo "======================="
|
|||
|
setterm -reverse off
|
|||
|
echo " $GENERATIONS generations"
|
|||
|
echo " of"
|
|||
|
echo "\"Life in the Slow Lane\""
|
|||
|
setterm -reverse on
|
|||
|
echo "======================="
|
|||
|
setterm -reverse off
|
|||
|
|
|||
|
sleep $DELAY # Display "splash screen" for 2 seconds.
|
|||
|
|
|||
|
|
|||
|
# -------- Display first generation. --------
|
|||
|
Gen0=`echo ${initial[@]}`
|
|||
|
display "$Gen0" # Display only.
|
|||
|
echo; echo
|
|||
|
echo "Generation $generation - $alive alive"
|
|||
|
sleep $DELAY
|
|||
|
# -------------------------------------------
|
|||
|
|
|||
|
|
|||
|
let "generation += 1" # Bump generation count.
|
|||
|
echo
|
|||
|
|
|||
|
# ------- Display second generation. -------
|
|||
|
Cur=`echo ${initial[@]}`
|
|||
|
next_gen "$Cur" # Update & display.
|
|||
|
sleep $DELAY
|
|||
|
# ------------------------------------------
|
|||
|
|
|||
|
let "generation += 1" # Increment generation count.
|
|||
|
|
|||
|
# ------ Main loop for displaying subsequent generations ------
|
|||
|
while [ "$generation" -le "$GENERATIONS" ]
|
|||
|
do
|
|||
|
Cur="$avar"
|
|||
|
next_gen "$Cur"
|
|||
|
let "generation += 1"
|
|||
|
sleep $DELAY
|
|||
|
done
|
|||
|
# ==============================================================
|
|||
|
|
|||
|
echo
|
|||
|
# }
|
|||
|
|
|||
|
exit 0 # CEOF:EOF
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# The grid in this script has a "boundary problem."
|
|||
|
# The the top, bottom, and sides border on a void of dead cells.
|
|||
|
# Exercise: Change the script to have the grid wrap around,
|
|||
|
# + so that the left and right sides will "touch,"
|
|||
|
# + as will the top and bottom.
|
|||
|
#
|
|||
|
# Exercise: Create a new "gen0" file to seed this script.
|
|||
|
# Use a 12 x 16 grid, instead of the original 10 x 10 one.
|
|||
|
# Make the necessary changes to the script,
|
|||
|
#+ so it will run with the altered file.
|
|||
|
#
|
|||
|
# Exercise: Modify this script so that it can determine the grid size
|
|||
|
#+ from the "gen0" file, and set any variables necessary
|
|||
|
#+ for the script to run.
|
|||
|
# This would make unnecessary any changes to variables
|
|||
|
#+ in the script for an altered grid size.
|
|||
|
#
|
|||
|
# Exercise: Optimize this script.
|
|||
|
# It has redundant code.</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="GEN0DATA"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-11. Data file for <I
|
|||
|
CLASS="FIRSTTERM"
|
|||
|
>Game of Life</I
|
|||
|
></B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
># gen0
|
|||
|
#
|
|||
|
# This is an example "generation 0" start-up file for "life.sh".
|
|||
|
# --------------------------------------------------------------
|
|||
|
# The "gen0" file is a 10 x 10 grid using a period (.) for live cells,
|
|||
|
#+ and an underscore (_) for dead ones. We cannot simply use spaces
|
|||
|
#+ for dead cells in this file because of a peculiarity in Bash arrays.
|
|||
|
# [Exercise for the reader: explain this.]
|
|||
|
#
|
|||
|
# Lines beginning with a '#' are comments, and the script ignores them.
|
|||
|
__.__..___
|
|||
|
__.._.____
|
|||
|
____.___..
|
|||
|
_._______.
|
|||
|
____._____
|
|||
|
..__...___
|
|||
|
____._____
|
|||
|
___...____
|
|||
|
__.._..___
|
|||
|
_..___..__</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><P
|
|||
|
>+++</P
|
|||
|
><P
|
|||
|
>The following script is by Mark Moraes of the University
|
|||
|
of Toronto. See the file <TT
|
|||
|
CLASS="FILENAME"
|
|||
|
>Moraes-COPYRIGHT</TT
|
|||
|
>
|
|||
|
for permissions and restrictions. This file is included in the
|
|||
|
combined <A
|
|||
|
HREF="mirrorsites.html#WHERE_TARBALL"
|
|||
|
>HTML/source tarball</A
|
|||
|
>
|
|||
|
of the <EM
|
|||
|
>ABS Guide</EM
|
|||
|
>.</P
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="BEHEAD"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-12. <I
|
|||
|
CLASS="FIRSTTERM"
|
|||
|
>behead</I
|
|||
|
>: Removing mail and news
|
|||
|
message headers</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#! /bin/sh
|
|||
|
# Strips off the header from a mail/News message i.e. till the first
|
|||
|
#+ empty line.
|
|||
|
# Author: Mark Moraes, University of Toronto
|
|||
|
|
|||
|
# ==> These comments added by author of this document.
|
|||
|
|
|||
|
if [ $# -eq 0 ]; then
|
|||
|
# ==> If no command-line args present, then works on file redirected to stdin.
|
|||
|
sed -e '1,/^$/d' -e '/^[ ]*$/d'
|
|||
|
# --> Delete empty lines and all lines until
|
|||
|
# --> first one beginning with white space.
|
|||
|
else
|
|||
|
# ==> If command-line args present, then work on files named.
|
|||
|
for i do
|
|||
|
sed -e '1,/^$/d' -e '/^[ ]*$/d' $i
|
|||
|
# --> Ditto, as above.
|
|||
|
done
|
|||
|
fi
|
|||
|
|
|||
|
exit
|
|||
|
|
|||
|
# ==> Exercise: Add error checking and other options.
|
|||
|
# ==>
|
|||
|
# ==> Note that the small sed script repeats, except for the arg passed.
|
|||
|
# ==> Does it make sense to embed it in a function? Why or why not?
|
|||
|
|
|||
|
|
|||
|
/*
|
|||
|
* Copyright University of Toronto 1988, 1989.
|
|||
|
* Written by Mark Moraes
|
|||
|
*
|
|||
|
* Permission is granted to anyone to use this software for any purpose on
|
|||
|
* any computer system, and to alter it and redistribute it freely, subject
|
|||
|
* to the following restrictions:
|
|||
|
*
|
|||
|
* 1. The author and the University of Toronto are not responsible
|
|||
|
* for the consequences of use of this software, no matter how awful,
|
|||
|
* even if they arise from flaws in it.
|
|||
|
*
|
|||
|
* 2. The origin of this software must not be misrepresented, either by
|
|||
|
* explicit claim or by omission. Since few users ever read sources,
|
|||
|
* credits must appear in the documentation.
|
|||
|
*
|
|||
|
* 3. Altered versions must be plainly marked as such, and must not be
|
|||
|
* misrepresented as being the original software. Since few users
|
|||
|
* ever read sources, credits must appear in the documentation.
|
|||
|
*
|
|||
|
* 4. This notice may not be removed or altered.
|
|||
|
*/</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><P
|
|||
|
>+</P
|
|||
|
><P
|
|||
|
>Antek Sawicki contributed the following script, which makes very
|
|||
|
clever use of the parameter substitution operators discussed in
|
|||
|
<A
|
|||
|
HREF="parameter-substitution.html"
|
|||
|
>Section 10.2</A
|
|||
|
>.</P
|
|||
|
><P
|
|||
|
><A
|
|||
|
NAME="PW0"
|
|||
|
></A
|
|||
|
></P
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="PW"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-13. <I
|
|||
|
CLASS="FIRSTTERM"
|
|||
|
>password</I
|
|||
|
>: Generating random
|
|||
|
8-character passwords</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
#
|
|||
|
#
|
|||
|
# Random password generator for Bash 2.x +
|
|||
|
#+ by Antek Sawicki <tenox@tenox.tc>,
|
|||
|
#+ who generously gave usage permission to the ABS Guide author.
|
|||
|
#
|
|||
|
# ==> Comments added by document author ==>
|
|||
|
|
|||
|
|
|||
|
MATRIX="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
|||
|
# ==> Password will consist of alphanumeric characters.
|
|||
|
LENGTH="8"
|
|||
|
# ==> May change 'LENGTH' for longer password.
|
|||
|
|
|||
|
|
|||
|
while [ "${n:=1}" -le "$LENGTH" ]
|
|||
|
# ==> Recall that := is "default substitution" operator.
|
|||
|
# ==> So, if 'n' has not been initialized, set it to 1.
|
|||
|
do
|
|||
|
PASS="$PASS${MATRIX:$(($RANDOM%${#MATRIX})):1}"
|
|||
|
# ==> Very clever, but tricky.
|
|||
|
|
|||
|
# ==> Starting from the innermost nesting...
|
|||
|
# ==> ${#MATRIX} returns length of array MATRIX.
|
|||
|
|
|||
|
# ==> $RANDOM%${#MATRIX} returns random number between 1
|
|||
|
# ==> and [length of MATRIX] - 1.
|
|||
|
|
|||
|
# ==> ${MATRIX:$(($RANDOM%${#MATRIX})):1}
|
|||
|
# ==> returns expansion of MATRIX at random position, by length 1.
|
|||
|
# ==> See {var:pos:len} parameter substitution in Chapter 9.
|
|||
|
# ==> and the associated examples.
|
|||
|
|
|||
|
# ==> PASS=... simply pastes this result onto previous PASS (concatenation).
|
|||
|
|
|||
|
# ==> To visualize this more clearly, uncomment the following line
|
|||
|
# echo "$PASS"
|
|||
|
# ==> to see PASS being built up,
|
|||
|
# ==> one character at a time, each iteration of the loop.
|
|||
|
|
|||
|
let n+=1
|
|||
|
# ==> Increment 'n' for next pass.
|
|||
|
done
|
|||
|
|
|||
|
echo "$PASS" # ==> Or, redirect to a file, as desired.
|
|||
|
|
|||
|
exit 0</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><P
|
|||
|
>+</P
|
|||
|
><P
|
|||
|
><A
|
|||
|
NAME="ZFIFO"
|
|||
|
></A
|
|||
|
>James R. Van Zandt contributed this script
|
|||
|
which uses named pipes and, in his words, <SPAN
|
|||
|
CLASS="QUOTE"
|
|||
|
>"really exercises
|
|||
|
quoting and escaping."</SPAN
|
|||
|
></P
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="FIFO"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-14. <I
|
|||
|
CLASS="FIRSTTERM"
|
|||
|
>fifo</I
|
|||
|
>: Making daily backups, using
|
|||
|
named pipes</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
# ==> Script by James R. Van Zandt, and used here with his permission.
|
|||
|
|
|||
|
# ==> Comments added by author of this document.
|
|||
|
|
|||
|
|
|||
|
HERE=`uname -n` # ==> hostname
|
|||
|
THERE=bilbo
|
|||
|
echo "starting remote backup to $THERE at `date +%r`"
|
|||
|
# ==> `date +%r` returns time in 12-hour format, i.e. "08:08:34 PM".
|
|||
|
|
|||
|
# make sure /pipe really is a pipe and not a plain file
|
|||
|
rm -rf /pipe
|
|||
|
mkfifo /pipe # ==> Create a "named pipe", named "/pipe" ...
|
|||
|
|
|||
|
# ==> 'su xyz' runs commands as user "xyz".
|
|||
|
# ==> 'ssh' invokes secure shell (remote login client).
|
|||
|
su xyz -c "ssh $THERE \"cat > /home/xyz/backup/${HERE}-daily.tar.gz\" < /pipe"&
|
|||
|
cd /
|
|||
|
tar -czf - bin boot dev etc home info lib man root sbin share usr var > /pipe
|
|||
|
# ==> Uses named pipe, /pipe, to communicate between processes:
|
|||
|
# ==> 'tar/gzip' writes to /pipe and 'ssh' reads from /pipe.
|
|||
|
|
|||
|
# ==> The end result is this backs up the main directories, from / on down.
|
|||
|
|
|||
|
# ==> What are the advantages of a "named pipe" in this situation,
|
|||
|
# ==>+ as opposed to an "anonymous pipe", with |?
|
|||
|
# ==> Will an anonymous pipe even work here?
|
|||
|
|
|||
|
# ==> Is it necessary to delete the pipe before exiting the script?
|
|||
|
# ==> How could that be done?
|
|||
|
|
|||
|
|
|||
|
exit 0</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><P
|
|||
|
>+</P
|
|||
|
><P
|
|||
|
><A
|
|||
|
NAME="PRIMES1"
|
|||
|
></A
|
|||
|
></P
|
|||
|
><P
|
|||
|
>St<53>phane Chazelas used the following script to
|
|||
|
demonstrate generating prime numbers without arrays.</P
|
|||
|
><P
|
|||
|
><A
|
|||
|
NAME="PRIMES00"
|
|||
|
></A
|
|||
|
></P
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="PRIMES"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-15. Generating prime numbers using the modulo operator</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
# primes.sh: Generate prime numbers, without using arrays.
|
|||
|
# Script contributed by Stephane Chazelas.
|
|||
|
|
|||
|
# This does *not* use the classic "Sieve of Eratosthenes" algorithm,
|
|||
|
#+ but instead the more intuitive method of testing each candidate number
|
|||
|
#+ for factors (divisors), using the "%" modulo operator.
|
|||
|
|
|||
|
|
|||
|
LIMIT=1000 # Primes, 2 ... 1000.
|
|||
|
|
|||
|
Primes()
|
|||
|
{
|
|||
|
(( n = $1 + 1 )) # Bump to next integer.
|
|||
|
shift # Next parameter in list.
|
|||
|
# echo "_n=$n i=$i_"
|
|||
|
|
|||
|
if (( n == LIMIT ))
|
|||
|
then echo $*
|
|||
|
return
|
|||
|
fi
|
|||
|
|
|||
|
for i; do # "i" set to "@", previous values of $n.
|
|||
|
# echo "-n=$n i=$i-"
|
|||
|
(( i * i > n )) && break # Optimization.
|
|||
|
(( n % i )) && continue # Sift out non-primes using modulo operator.
|
|||
|
Primes $n $@ # Recursion inside loop.
|
|||
|
return
|
|||
|
done
|
|||
|
|
|||
|
Primes $n $@ $n # Recursion outside loop.
|
|||
|
# Successively accumulate
|
|||
|
#+ positional parameters.
|
|||
|
# "$@" is the accumulating list of primes.
|
|||
|
}
|
|||
|
|
|||
|
Primes 1
|
|||
|
|
|||
|
exit $?
|
|||
|
|
|||
|
# Pipe output of the script to 'fmt' for prettier printing.
|
|||
|
|
|||
|
# Uncomment lines 16 and 24 to help figure out what is going on.
|
|||
|
|
|||
|
# Compare the speed of this algorithm for generating primes
|
|||
|
#+ with the Sieve of Eratosthenes (ex68.sh).
|
|||
|
|
|||
|
|
|||
|
# Exercise: Rewrite this script without recursion.</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><P
|
|||
|
>+</P
|
|||
|
><P
|
|||
|
>Rick Boivie's revision of Jordi Sanfeliu's
|
|||
|
<EM
|
|||
|
>tree</EM
|
|||
|
> script.</P
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="TREE"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-16. <I
|
|||
|
CLASS="FIRSTTERM"
|
|||
|
>tree</I
|
|||
|
>: Displaying a directory tree</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
# tree.sh
|
|||
|
|
|||
|
# Written by Rick Boivie.
|
|||
|
# Used with permission.
|
|||
|
# This is a revised and simplified version of a script
|
|||
|
#+ by Jordi Sanfeliu (the original author), and patched by Ian Kjos.
|
|||
|
# This script replaces the earlier version used in
|
|||
|
#+ previous releases of the Advanced Bash Scripting Guide.
|
|||
|
# Copyright (c) 2002, by Jordi Sanfeliu, Rick Boivie, and Ian Kjos.
|
|||
|
|
|||
|
# ==> Comments added by the author of this document.
|
|||
|
|
|||
|
|
|||
|
search () {
|
|||
|
for dir in `echo *`
|
|||
|
# ==> `echo *` lists all the files in current working directory,
|
|||
|
#+ ==> without line breaks.
|
|||
|
# ==> Similar effect to for dir in *
|
|||
|
# ==> but "dir in `echo *`" will not handle filenames with blanks.
|
|||
|
do
|
|||
|
if [ -d "$dir" ] ; then # ==> If it is a directory (-d)...
|
|||
|
zz=0 # ==> Temp variable, keeping track of
|
|||
|
# directory level.
|
|||
|
while [ $zz != $1 ] # Keep track of inner nested loop.
|
|||
|
do
|
|||
|
echo -n "| " # ==> Display vertical connector symbol,
|
|||
|
# ==> with 2 spaces & no line feed
|
|||
|
# in order to indent.
|
|||
|
zz=`expr $zz + 1` # ==> Increment zz.
|
|||
|
done
|
|||
|
|
|||
|
if [ -L "$dir" ] ; then # ==> If directory is a symbolic link...
|
|||
|
echo "+---$dir" `ls -l $dir | sed 's/^.*'$dir' //'`
|
|||
|
# ==> Display horiz. connector and list directory name, but...
|
|||
|
# ==> delete date/time part of long listing.
|
|||
|
else
|
|||
|
echo "+---$dir" # ==> Display horizontal connector symbol...
|
|||
|
# ==> and print directory name.
|
|||
|
numdirs=`expr $numdirs + 1` # ==> Increment directory count.
|
|||
|
if cd "$dir" ; then # ==> If can move to subdirectory...
|
|||
|
search `expr $1 + 1` # with recursion ;-)
|
|||
|
# ==> Function calls itself.
|
|||
|
cd ..
|
|||
|
fi
|
|||
|
fi
|
|||
|
fi
|
|||
|
done
|
|||
|
}
|
|||
|
|
|||
|
if [ $# != 0 ] ; then
|
|||
|
cd $1 # Move to indicated directory.
|
|||
|
#else # stay in current directory
|
|||
|
fi
|
|||
|
|
|||
|
echo "Initial directory = `pwd`"
|
|||
|
numdirs=0
|
|||
|
|
|||
|
search 0
|
|||
|
echo "Total directories = $numdirs"
|
|||
|
|
|||
|
exit 0</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><P
|
|||
|
>Patsie's version of a directory <I
|
|||
|
CLASS="FIRSTTERM"
|
|||
|
>tree</I
|
|||
|
>
|
|||
|
script.</P
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="TREE2"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-17. <I
|
|||
|
CLASS="FIRSTTERM"
|
|||
|
>tree2</I
|
|||
|
>: Alternate directory tree script</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
# tree2.sh
|
|||
|
|
|||
|
# Lightly modified/reformatted by ABS Guide author.
|
|||
|
# Included in ABS Guide with permission of script author (thanks!).
|
|||
|
|
|||
|
## Recursive file/dirsize checking script, by Patsie
|
|||
|
##
|
|||
|
## This script builds a list of files/directories and their size (du -akx)
|
|||
|
## and processes this list to a human readable tree shape
|
|||
|
## The 'du -akx' is only as good as the permissions the owner has.
|
|||
|
## So preferably run as root* to get the best results, or use only on
|
|||
|
## directories for which you have read permissions. Anything you can't
|
|||
|
## read is not in the list.
|
|||
|
|
|||
|
#* ABS Guide author advises caution when running scripts as root!
|
|||
|
|
|||
|
|
|||
|
########## THIS IS CONFIGURABLE ##########
|
|||
|
|
|||
|
TOP=5 # Top 5 biggest (sub)directories.
|
|||
|
MAXRECURS=5 # Max 5 subdirectories/recursions deep.
|
|||
|
E_BL=80 # Blank line already returned.
|
|||
|
E_DIR=81 # Directory not specified.
|
|||
|
|
|||
|
|
|||
|
########## DON'T CHANGE ANYTHING BELOW THIS LINE ##########
|
|||
|
|
|||
|
PID=$$ # Our own process ID.
|
|||
|
SELF=`basename $0` # Our own program name.
|
|||
|
TMP="/tmp/${SELF}.${PID}.tmp" # Temporary 'du' result.
|
|||
|
|
|||
|
# Convert number to dotted thousand.
|
|||
|
function dot { echo " $*" |
|
|||
|
sed -e :a -e 's/\(.*[0-9]\)\([0-9]\{3\}\)/\1,\2/;ta' |
|
|||
|
tail -c 12; }
|
|||
|
|
|||
|
# Usage: tree <recursion> <indent prefix> <min size> <directory>
|
|||
|
function tree {
|
|||
|
recurs="$1" # How deep nested are we?
|
|||
|
prefix="$2" # What do we display before file/dirname?
|
|||
|
minsize="$3" # What is the minumum file/dirsize?
|
|||
|
dirname="$4" # Which directory are we checking?
|
|||
|
|
|||
|
# Get ($TOP) biggest subdirs/subfiles from TMP file.
|
|||
|
LIST=`egrep "[[:space:]]${dirname}/[^/]*$" "$TMP" |
|
|||
|
awk '{if($1>'$minsize') print;}' | sort -nr | head -$TOP`
|
|||
|
[ -z "$LIST" ] && return # Empty list, then go back.
|
|||
|
|
|||
|
cnt=0
|
|||
|
num=`echo "$LIST" | wc -l` # How many entries in the list.
|
|||
|
|
|||
|
## Main loop
|
|||
|
echo "$LIST" | while read size name; do
|
|||
|
((cnt+=1)) # Count entry number.
|
|||
|
bname=`basename "$name"` # We only need a basename of the entry.
|
|||
|
[ -d "$name" ] && bname="$bname/"
|
|||
|
# If it's a directory, append a slash.
|
|||
|
echo "`dot $size`$prefix +-$bname"
|
|||
|
# Display the result.
|
|||
|
# Call ourself recursively if it's a directory
|
|||
|
#+ and we're not nested too deep ($MAXRECURS).
|
|||
|
# The recursion goes up: $((recurs+1))
|
|||
|
# The prefix gets a space if it's the last entry,
|
|||
|
#+ or a pipe if there are more entries.
|
|||
|
# The minimum file/dirsize becomes
|
|||
|
#+ a tenth of his parent: $((size/10)).
|
|||
|
# Last argument is the full directory name to check.
|
|||
|
if [ -d "$name" -a $recurs -lt $MAXRECURS ]; then
|
|||
|
[ $cnt -lt $num ] \
|
|||
|
|| (tree $((recurs+1)) "$prefix " $((size/10)) "$name") \
|
|||
|
&& (tree $((recurs+1)) "$prefix |" $((size/10)) "$name")
|
|||
|
fi
|
|||
|
done
|
|||
|
|
|||
|
[ $? -eq 0 ] && echo " $prefix"
|
|||
|
# Every time we jump back add a 'blank' line.
|
|||
|
return $E_BL
|
|||
|
# We return 80 to tell we added a blank line already.
|
|||
|
}
|
|||
|
|
|||
|
### ###
|
|||
|
### main program ###
|
|||
|
### ###
|
|||
|
|
|||
|
rootdir="$@"
|
|||
|
[ -d "$rootdir" ] ||
|
|||
|
{ echo "$SELF: Usage: $SELF <directory>" >&2; exit $E_DIR; }
|
|||
|
# We should be called with a directory name.
|
|||
|
|
|||
|
echo "Building inventory list, please wait ..."
|
|||
|
# Show "please wait" message.
|
|||
|
du -akx "$rootdir" 1>"$TMP" 2>/dev/null
|
|||
|
# Build a temporary list of all files/dirs and their size.
|
|||
|
size=`tail -1 "$TMP" | awk '{print $1}'`
|
|||
|
# What is our rootdirectory's size?
|
|||
|
echo "`dot $size` $rootdir"
|
|||
|
# Display rootdirectory's entry.
|
|||
|
tree 0 "" 0 "$rootdir"
|
|||
|
# Display the tree below our rootdirectory.
|
|||
|
|
|||
|
rm "$TMP" 2>/dev/null
|
|||
|
# Clean up TMP file.
|
|||
|
|
|||
|
exit $?</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><P
|
|||
|
>Noah Friedman permitted use of his <EM
|
|||
|
>string
|
|||
|
function</EM
|
|||
|
> script. It essentially reproduces some
|
|||
|
of the <I
|
|||
|
CLASS="FIRSTTERM"
|
|||
|
>C</I
|
|||
|
>-library string manipulation
|
|||
|
functions.</P
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="STRING"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-18. <I
|
|||
|
CLASS="FIRSTTERM"
|
|||
|
>string functions</I
|
|||
|
>: C-style string
|
|||
|
functions</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
|
|||
|
# string.bash --- bash emulation of string(3) library routines
|
|||
|
# Author: Noah Friedman <friedman@prep.ai.mit.edu>
|
|||
|
# ==> Used with his kind permission in this document.
|
|||
|
# Created: 1992-07-01
|
|||
|
# Last modified: 1993-09-29
|
|||
|
# Public domain
|
|||
|
|
|||
|
# Conversion to bash v2 syntax done by Chet Ramey
|
|||
|
|
|||
|
# Commentary:
|
|||
|
# Code:
|
|||
|
|
|||
|
#:docstring strcat:
|
|||
|
# Usage: strcat s1 s2
|
|||
|
#
|
|||
|
# Strcat appends the value of variable s2 to variable s1.
|
|||
|
#
|
|||
|
# Example:
|
|||
|
# a="foo"
|
|||
|
# b="bar"
|
|||
|
# strcat a b
|
|||
|
# echo $a
|
|||
|
# => foobar
|
|||
|
#
|
|||
|
#:end docstring:
|
|||
|
|
|||
|
###;;;autoload ==> Autoloading of function commented out.
|
|||
|
function strcat ()
|
|||
|
{
|
|||
|
local s1_val s2_val
|
|||
|
|
|||
|
s1_val=${!1} # indirect variable expansion
|
|||
|
s2_val=${!2}
|
|||
|
eval "$1"=\'"${s1_val}${s2_val}"\'
|
|||
|
# ==> eval $1='${s1_val}${s2_val}' avoids problems,
|
|||
|
# ==> if one of the variables contains a single quote.
|
|||
|
}
|
|||
|
|
|||
|
#:docstring strncat:
|
|||
|
# Usage: strncat s1 s2 $n
|
|||
|
#
|
|||
|
# Line strcat, but strncat appends a maximum of n characters from the value
|
|||
|
# of variable s2. It copies fewer if the value of variabl s2 is shorter
|
|||
|
# than n characters. Echoes result on stdout.
|
|||
|
#
|
|||
|
# Example:
|
|||
|
# a=foo
|
|||
|
# b=barbaz
|
|||
|
# strncat a b 3
|
|||
|
# echo $a
|
|||
|
# => foobar
|
|||
|
#
|
|||
|
#:end docstring:
|
|||
|
|
|||
|
###;;;autoload
|
|||
|
function strncat ()
|
|||
|
{
|
|||
|
local s1="$1"
|
|||
|
local s2="$2"
|
|||
|
local -i n="$3"
|
|||
|
local s1_val s2_val
|
|||
|
|
|||
|
s1_val=${!s1} # ==> indirect variable expansion
|
|||
|
s2_val=${!s2}
|
|||
|
|
|||
|
if [ ${#s2_val} -gt ${n} ]; then
|
|||
|
s2_val=${s2_val:0:$n} # ==> substring extraction
|
|||
|
fi
|
|||
|
|
|||
|
eval "$s1"=\'"${s1_val}${s2_val}"\'
|
|||
|
# ==> eval $1='${s1_val}${s2_val}' avoids problems,
|
|||
|
# ==> if one of the variables contains a single quote.
|
|||
|
}
|
|||
|
|
|||
|
#:docstring strcmp:
|
|||
|
# Usage: strcmp $s1 $s2
|
|||
|
#
|
|||
|
# Strcmp compares its arguments and returns an integer less than, equal to,
|
|||
|
# or greater than zero, depending on whether string s1 is lexicographically
|
|||
|
# less than, equal to, or greater than string s2.
|
|||
|
#:end docstring:
|
|||
|
|
|||
|
###;;;autoload
|
|||
|
function strcmp ()
|
|||
|
{
|
|||
|
[ "$1" = "$2" ] && return 0
|
|||
|
|
|||
|
[ "${1}" '<' "${2}" ] > /dev/null && return -1
|
|||
|
|
|||
|
return 1
|
|||
|
}
|
|||
|
|
|||
|
#:docstring strncmp:
|
|||
|
# Usage: strncmp $s1 $s2 $n
|
|||
|
#
|
|||
|
# Like strcmp, but makes the comparison by examining a maximum of n
|
|||
|
# characters (n less than or equal to zero yields equality).
|
|||
|
#:end docstring:
|
|||
|
|
|||
|
###;;;autoload
|
|||
|
function strncmp ()
|
|||
|
{
|
|||
|
if [ -z "${3}" -o "${3}" -le "0" ]; then
|
|||
|
return 0
|
|||
|
fi
|
|||
|
|
|||
|
if [ ${3} -ge ${#1} -a ${3} -ge ${#2} ]; then
|
|||
|
strcmp "$1" "$2"
|
|||
|
return $?
|
|||
|
else
|
|||
|
s1=${1:0:$3}
|
|||
|
s2=${2:0:$3}
|
|||
|
strcmp $s1 $s2
|
|||
|
return $?
|
|||
|
fi
|
|||
|
}
|
|||
|
|
|||
|
#:docstring strlen:
|
|||
|
# Usage: strlen s
|
|||
|
#
|
|||
|
# Strlen returns the number of characters in string literal s.
|
|||
|
#:end docstring:
|
|||
|
|
|||
|
###;;;autoload
|
|||
|
function strlen ()
|
|||
|
{
|
|||
|
eval echo "\${#${1}}"
|
|||
|
# ==> Returns the length of the value of the variable
|
|||
|
# ==> whose name is passed as an argument.
|
|||
|
}
|
|||
|
|
|||
|
#:docstring strspn:
|
|||
|
# Usage: strspn $s1 $s2
|
|||
|
#
|
|||
|
# Strspn returns the length of the maximum initial segment of string s1,
|
|||
|
# which consists entirely of characters from string s2.
|
|||
|
#:end docstring:
|
|||
|
|
|||
|
###;;;autoload
|
|||
|
function strspn ()
|
|||
|
{
|
|||
|
# Unsetting IFS allows whitespace to be handled as normal chars.
|
|||
|
local IFS=
|
|||
|
local result="${1%%[!${2}]*}"
|
|||
|
|
|||
|
echo ${#result}
|
|||
|
}
|
|||
|
|
|||
|
#:docstring strcspn:
|
|||
|
# Usage: strcspn $s1 $s2
|
|||
|
#
|
|||
|
# Strcspn returns the length of the maximum initial segment of string s1,
|
|||
|
# which consists entirely of characters not from string s2.
|
|||
|
#:end docstring:
|
|||
|
|
|||
|
###;;;autoload
|
|||
|
function strcspn ()
|
|||
|
{
|
|||
|
# Unsetting IFS allows whitspace to be handled as normal chars.
|
|||
|
local IFS=
|
|||
|
local result="${1%%[${2}]*}"
|
|||
|
|
|||
|
echo ${#result}
|
|||
|
}
|
|||
|
|
|||
|
#:docstring strstr:
|
|||
|
# Usage: strstr s1 s2
|
|||
|
#
|
|||
|
# Strstr echoes a substring starting at the first occurrence of string s2 in
|
|||
|
# string s1, or nothing if s2 does not occur in the string. If s2 points to
|
|||
|
# a string of zero length, strstr echoes s1.
|
|||
|
#:end docstring:
|
|||
|
|
|||
|
###;;;autoload
|
|||
|
function strstr ()
|
|||
|
{
|
|||
|
# if s2 points to a string of zero length, strstr echoes s1
|
|||
|
[ ${#2} -eq 0 ] && { echo "$1" ; return 0; }
|
|||
|
|
|||
|
# strstr echoes nothing if s2 does not occur in s1
|
|||
|
case "$1" in
|
|||
|
*$2*) ;;
|
|||
|
*) return 1;;
|
|||
|
esac
|
|||
|
|
|||
|
# use the pattern matching code to strip off the match and everything
|
|||
|
# following it
|
|||
|
first=${1/$2*/}
|
|||
|
|
|||
|
# then strip off the first unmatched portion of the string
|
|||
|
echo "${1##$first}"
|
|||
|
}
|
|||
|
|
|||
|
#:docstring strtok:
|
|||
|
# Usage: strtok s1 s2
|
|||
|
#
|
|||
|
# Strtok considers the string s1 to consist of a sequence of zero or more
|
|||
|
# text tokens separated by spans of one or more characters from the
|
|||
|
# separator string s2. The first call (with a non-empty string s1
|
|||
|
# specified) echoes a string consisting of the first token on stdout. The
|
|||
|
# function keeps track of its position in the string s1 between separate
|
|||
|
# calls, so that subsequent calls made with the first argument an empty
|
|||
|
# string will work through the string immediately following that token. In
|
|||
|
# this way subsequent calls will work through the string s1 until no tokens
|
|||
|
# remain. The separator string s2 may be different from call to call.
|
|||
|
# When no token remains in s1, an empty value is echoed on stdout.
|
|||
|
#:end docstring:
|
|||
|
|
|||
|
###;;;autoload
|
|||
|
function strtok ()
|
|||
|
{
|
|||
|
:
|
|||
|
}
|
|||
|
|
|||
|
#:docstring strtrunc:
|
|||
|
# Usage: strtrunc $n $s1 {$s2} {$...}
|
|||
|
#
|
|||
|
# Used by many functions like strncmp to truncate arguments for comparison.
|
|||
|
# Echoes the first n characters of each string s1 s2 ... on stdout.
|
|||
|
#:end docstring:
|
|||
|
|
|||
|
###;;;autoload
|
|||
|
function strtrunc ()
|
|||
|
{
|
|||
|
n=$1 ; shift
|
|||
|
for z; do
|
|||
|
echo "${z:0:$n}"
|
|||
|
done
|
|||
|
}
|
|||
|
|
|||
|
# provide string
|
|||
|
|
|||
|
# string.bash ends here
|
|||
|
|
|||
|
|
|||
|
# ========================================================================== #
|
|||
|
# ==> Everything below here added by the document author.
|
|||
|
|
|||
|
# ==> Suggested use of this script is to delete everything below here,
|
|||
|
# ==> and "source" this file into your own scripts.
|
|||
|
|
|||
|
# strcat
|
|||
|
string0=one
|
|||
|
string1=two
|
|||
|
echo
|
|||
|
echo "Testing \"strcat\" function:"
|
|||
|
echo "Original \"string0\" = $string0"
|
|||
|
echo "\"string1\" = $string1"
|
|||
|
strcat string0 string1
|
|||
|
echo "New \"string0\" = $string0"
|
|||
|
echo
|
|||
|
|
|||
|
# strlen
|
|||
|
echo
|
|||
|
echo "Testing \"strlen\" function:"
|
|||
|
str=123456789
|
|||
|
echo "\"str\" = $str"
|
|||
|
echo -n "Length of \"str\" = "
|
|||
|
strlen str
|
|||
|
echo
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# Exercise:
|
|||
|
# --------
|
|||
|
# Add code to test all the other string functions above.
|
|||
|
|
|||
|
|
|||
|
exit 0</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><P
|
|||
|
>Michael Zick's complex array example uses the <A
|
|||
|
HREF="filearchiv.html#MD5SUMREF"
|
|||
|
>md5sum</A
|
|||
|
> check sum command to encode directory
|
|||
|
information.</P
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="DIRECTORYINFO"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-19. Directory information</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#! /bin/bash
|
|||
|
# directory-info.sh
|
|||
|
# Parses and lists directory information.
|
|||
|
|
|||
|
# NOTE: Change lines 273 and 353 per "README" file.
|
|||
|
|
|||
|
# Michael Zick is the author of this script.
|
|||
|
# Used here with his permission.
|
|||
|
|
|||
|
# Controls
|
|||
|
# If overridden by command arguments, they must be in the order:
|
|||
|
# Arg1: "Descriptor Directory"
|
|||
|
# Arg2: "Exclude Paths"
|
|||
|
# Arg3: "Exclude Directories"
|
|||
|
#
|
|||
|
# Environment Settings override Defaults.
|
|||
|
# Command arguments override Environment Settings.
|
|||
|
|
|||
|
# Default location for content addressed file descriptors.
|
|||
|
MD5UCFS=${1:-${MD5UCFS:-'/tmpfs/ucfs'}}
|
|||
|
|
|||
|
# Directory paths never to list or enter
|
|||
|
declare -a \
|
|||
|
EXCLUDE_PATHS=${2:-${EXCLUDE_PATHS:-'(/proc /dev /devfs /tmpfs)'}}
|
|||
|
|
|||
|
# Directories never to list or enter
|
|||
|
declare -a \
|
|||
|
EXCLUDE_DIRS=${3:-${EXCLUDE_DIRS:-'(ucfs lost+found tmp wtmp)'}}
|
|||
|
|
|||
|
# Files never to list or enter
|
|||
|
declare -a \
|
|||
|
EXCLUDE_FILES=${3:-${EXCLUDE_FILES:-'(core "Name with Spaces")'}}
|
|||
|
|
|||
|
|
|||
|
# Here document used as a comment block.
|
|||
|
: <<LSfieldsDoc
|
|||
|
# # # # # List Filesystem Directory Information # # # # #
|
|||
|
#
|
|||
|
# ListDirectory "FileGlob" "Field-Array-Name"
|
|||
|
# or
|
|||
|
# ListDirectory -of "FileGlob" "Field-Array-Filename"
|
|||
|
# '-of' meaning 'output to filename'
|
|||
|
# # # # #
|
|||
|
|
|||
|
String format description based on: ls (GNU fileutils) version 4.0.36
|
|||
|
|
|||
|
Produces a line (or more) formatted:
|
|||
|
inode permissions hard-links owner group ...
|
|||
|
32736 -rw------- 1 mszick mszick
|
|||
|
|
|||
|
size day month date hh:mm:ss year path
|
|||
|
2756608 Sun Apr 20 08:53:06 2003 /home/mszick/core
|
|||
|
|
|||
|
Unless it is formatted:
|
|||
|
inode permissions hard-links owner group ...
|
|||
|
266705 crw-rw---- 1 root uucp
|
|||
|
|
|||
|
major minor day month date hh:mm:ss year path
|
|||
|
4, 68 Sun Apr 20 09:27:33 2003 /dev/ttyS4
|
|||
|
NOTE: that pesky comma after the major number
|
|||
|
|
|||
|
NOTE: the 'path' may be multiple fields:
|
|||
|
/home/mszick/core
|
|||
|
/proc/982/fd/0 -> /dev/null
|
|||
|
/proc/982/fd/1 -> /home/mszick/.xsession-errors
|
|||
|
/proc/982/fd/13 -> /tmp/tmpfZVVOCs (deleted)
|
|||
|
/proc/982/fd/7 -> /tmp/kde-mszick/ksycoca
|
|||
|
/proc/982/fd/8 -> socket:[11586]
|
|||
|
/proc/982/fd/9 -> pipe:[11588]
|
|||
|
|
|||
|
If that isn't enough to keep your parser guessing,
|
|||
|
either or both of the path components may be relative:
|
|||
|
../Built-Shared -> Built-Static
|
|||
|
../linux-2.4.20.tar.bz2 -> ../../../SRCS/linux-2.4.20.tar.bz2
|
|||
|
|
|||
|
The first character of the 11 (10?) character permissions field:
|
|||
|
's' Socket
|
|||
|
'd' Directory
|
|||
|
'b' Block device
|
|||
|
'c' Character device
|
|||
|
'l' Symbolic link
|
|||
|
NOTE: Hard links not marked - test for identical inode numbers
|
|||
|
on identical filesystems.
|
|||
|
All information about hard linked files are shared, except
|
|||
|
for the names and the name's location in the directory system.
|
|||
|
NOTE: A "Hard link" is known as a "File Alias" on some systems.
|
|||
|
'-' An undistingushed file
|
|||
|
|
|||
|
Followed by three groups of letters for: User, Group, Others
|
|||
|
Character 1: '-' Not readable; 'r' Readable
|
|||
|
Character 2: '-' Not writable; 'w' Writable
|
|||
|
Character 3, User and Group: Combined execute and special
|
|||
|
'-' Not Executable, Not Special
|
|||
|
'x' Executable, Not Special
|
|||
|
's' Executable, Special
|
|||
|
'S' Not Executable, Special
|
|||
|
Character 3, Others: Combined execute and sticky (tacky?)
|
|||
|
'-' Not Executable, Not Tacky
|
|||
|
'x' Executable, Not Tacky
|
|||
|
't' Executable, Tacky
|
|||
|
'T' Not Executable, Tacky
|
|||
|
|
|||
|
Followed by an access indicator
|
|||
|
Haven't tested this one, it may be the eleventh character
|
|||
|
or it may generate another field
|
|||
|
' ' No alternate access
|
|||
|
'+' Alternate access
|
|||
|
LSfieldsDoc
|
|||
|
|
|||
|
|
|||
|
ListDirectory()
|
|||
|
{
|
|||
|
local -a T
|
|||
|
local -i of=0 # Default return in variable
|
|||
|
# OLD_IFS=$IFS # Using BASH default ' \t\n'
|
|||
|
|
|||
|
case "$#" in
|
|||
|
3) case "$1" in
|
|||
|
-of) of=1 ; shift ;;
|
|||
|
* ) return 1 ;;
|
|||
|
esac ;;
|
|||
|
2) : ;; # Poor man's "continue"
|
|||
|
*) return 1 ;;
|
|||
|
esac
|
|||
|
|
|||
|
# NOTE: the (ls) command is NOT quoted (")
|
|||
|
T=( $(ls --inode --ignore-backups --almost-all --directory \
|
|||
|
--full-time --color=none --time=status --sort=none \
|
|||
|
--format=long $1) )
|
|||
|
|
|||
|
case $of in
|
|||
|
# Assign T back to the array whose name was passed as $2
|
|||
|
0) eval $2=\( \"\$\{T\[@\]\}\" \) ;;
|
|||
|
# Write T into filename passed as $2
|
|||
|
1) echo "${T[@]}" > "$2" ;;
|
|||
|
esac
|
|||
|
return 0
|
|||
|
}
|
|||
|
|
|||
|
# # # # # Is that string a legal number? # # # # #
|
|||
|
#
|
|||
|
# IsNumber "Var"
|
|||
|
# # # # # There has to be a better way, sigh...
|
|||
|
|
|||
|
IsNumber()
|
|||
|
{
|
|||
|
local -i int
|
|||
|
if [ $# -eq 0 ]
|
|||
|
then
|
|||
|
return 1
|
|||
|
else
|
|||
|
(let int=$1) 2>/dev/null
|
|||
|
return $? # Exit status of the let thread
|
|||
|
fi
|
|||
|
}
|
|||
|
|
|||
|
# # # # # Index Filesystem Directory Information # # # # #
|
|||
|
#
|
|||
|
# IndexList "Field-Array-Name" "Index-Array-Name"
|
|||
|
# or
|
|||
|
# IndexList -if Field-Array-Filename Index-Array-Name
|
|||
|
# IndexList -of Field-Array-Name Index-Array-Filename
|
|||
|
# IndexList -if -of Field-Array-Filename Index-Array-Filename
|
|||
|
# # # # #
|
|||
|
|
|||
|
: <<IndexListDoc
|
|||
|
Walk an array of directory fields produced by ListDirectory
|
|||
|
|
|||
|
Having suppressed the line breaks in an otherwise line oriented
|
|||
|
report, build an index to the array element which starts each line.
|
|||
|
|
|||
|
Each line gets two index entries, the first element of each line
|
|||
|
(inode) and the element that holds the pathname of the file.
|
|||
|
|
|||
|
The first index entry pair (Line-Number==0) are informational:
|
|||
|
Index-Array-Name[0] : Number of "Lines" indexed
|
|||
|
Index-Array-Name[1] : "Current Line" pointer into Index-Array-Name
|
|||
|
|
|||
|
The following index pairs (if any) hold element indexes into
|
|||
|
the Field-Array-Name per:
|
|||
|
Index-Array-Name[Line-Number * 2] : The "inode" field element.
|
|||
|
NOTE: This distance may be either +11 or +12 elements.
|
|||
|
Index-Array-Name[(Line-Number * 2) + 1] : The "pathname" element.
|
|||
|
NOTE: This distance may be a variable number of elements.
|
|||
|
Next line index pair for Line-Number+1.
|
|||
|
IndexListDoc
|
|||
|
|
|||
|
|
|||
|
|
|||
|
IndexList()
|
|||
|
{
|
|||
|
local -a LIST # Local of listname passed
|
|||
|
local -a -i INDEX=( 0 0 ) # Local of index to return
|
|||
|
local -i Lidx Lcnt
|
|||
|
local -i if=0 of=0 # Default to variable names
|
|||
|
|
|||
|
case "$#" in # Simplistic option testing
|
|||
|
0) return 1 ;;
|
|||
|
1) return 1 ;;
|
|||
|
2) : ;; # Poor man's continue
|
|||
|
3) case "$1" in
|
|||
|
-if) if=1 ;;
|
|||
|
-of) of=1 ;;
|
|||
|
* ) return 1 ;;
|
|||
|
esac ; shift ;;
|
|||
|
4) if=1 ; of=1 ; shift ; shift ;;
|
|||
|
*) return 1
|
|||
|
esac
|
|||
|
|
|||
|
# Make local copy of list
|
|||
|
case "$if" in
|
|||
|
0) eval LIST=\( \"\$\{$1\[@\]\}\" \) ;;
|
|||
|
1) LIST=( $(cat $1) ) ;;
|
|||
|
esac
|
|||
|
|
|||
|
# Grok (grope?) the array
|
|||
|
Lcnt=${#LIST[@]}
|
|||
|
Lidx=0
|
|||
|
until (( Lidx >= Lcnt ))
|
|||
|
do
|
|||
|
if IsNumber ${LIST[$Lidx]}
|
|||
|
then
|
|||
|
local -i inode name
|
|||
|
local ft
|
|||
|
inode=Lidx
|
|||
|
local m=${LIST[$Lidx+2]} # Hard Links field
|
|||
|
ft=${LIST[$Lidx+1]:0:1} # Fast-Stat
|
|||
|
case $ft in
|
|||
|
b) ((Lidx+=12)) ;; # Block device
|
|||
|
c) ((Lidx+=12)) ;; # Character device
|
|||
|
*) ((Lidx+=11)) ;; # Anything else
|
|||
|
esac
|
|||
|
name=Lidx
|
|||
|
case $ft in
|
|||
|
-) ((Lidx+=1)) ;; # The easy one
|
|||
|
b) ((Lidx+=1)) ;; # Block device
|
|||
|
c) ((Lidx+=1)) ;; # Character device
|
|||
|
d) ((Lidx+=1)) ;; # The other easy one
|
|||
|
l) ((Lidx+=3)) ;; # At LEAST two more fields
|
|||
|
# A little more elegance here would handle pipes,
|
|||
|
#+ sockets, deleted files - later.
|
|||
|
*) until IsNumber ${LIST[$Lidx]} || ((Lidx >= Lcnt))
|
|||
|
do
|
|||
|
((Lidx+=1))
|
|||
|
done
|
|||
|
;; # Not required
|
|||
|
esac
|
|||
|
INDEX[${#INDEX[*]}]=$inode
|
|||
|
INDEX[${#INDEX[*]}]=$name
|
|||
|
INDEX[0]=${INDEX[0]}+1 # One more "line" found
|
|||
|
# echo "Line: ${INDEX[0]} Type: $ft Links: $m Inode: \
|
|||
|
# ${LIST[$inode]} Name: ${LIST[$name]}"
|
|||
|
|
|||
|
else
|
|||
|
((Lidx+=1))
|
|||
|
fi
|
|||
|
done
|
|||
|
case "$of" in
|
|||
|
0) eval $2=\( \"\$\{INDEX\[@\]\}\" \) ;;
|
|||
|
1) echo "${INDEX[@]}" > "$2" ;;
|
|||
|
esac
|
|||
|
return 0 # What could go wrong?
|
|||
|
}
|
|||
|
|
|||
|
# # # # # Content Identify File # # # # #
|
|||
|
#
|
|||
|
# DigestFile Input-Array-Name Digest-Array-Name
|
|||
|
# or
|
|||
|
# DigestFile -if Input-FileName Digest-Array-Name
|
|||
|
# # # # #
|
|||
|
|
|||
|
# Here document used as a comment block.
|
|||
|
: <<DigestFilesDoc
|
|||
|
|
|||
|
The key (no pun intended) to a Unified Content File System (UCFS)
|
|||
|
is to distinguish the files in the system based on their content.
|
|||
|
Distinguishing files by their name is just so 20th Century.
|
|||
|
|
|||
|
The content is distinguished by computing a checksum of that content.
|
|||
|
This version uses the md5sum program to generate a 128 bit checksum
|
|||
|
representative of the file's contents.
|
|||
|
There is a chance that two files having different content might
|
|||
|
generate the same checksum using md5sum (or any checksum). Should
|
|||
|
that become a problem, then the use of md5sum can be replace by a
|
|||
|
cyrptographic signature. But until then...
|
|||
|
|
|||
|
The md5sum program is documented as outputting three fields (and it
|
|||
|
does), but when read it appears as two fields (array elements). This
|
|||
|
is caused by the lack of whitespace between the second and third field.
|
|||
|
So this function gropes the md5sum output and returns:
|
|||
|
[0] 32 character checksum in hexidecimal (UCFS filename)
|
|||
|
[1] Single character: ' ' text file, '*' binary file
|
|||
|
[2] Filesystem (20th Century Style) name
|
|||
|
Note: That name may be the character '-' indicating STDIN read.
|
|||
|
|
|||
|
DigestFilesDoc
|
|||
|
|
|||
|
|
|||
|
|
|||
|
DigestFile()
|
|||
|
{
|
|||
|
local if=0 # Default, variable name
|
|||
|
local -a T1 T2
|
|||
|
|
|||
|
case "$#" in
|
|||
|
3) case "$1" in
|
|||
|
-if) if=1 ; shift ;;
|
|||
|
* ) return 1 ;;
|
|||
|
esac ;;
|
|||
|
2) : ;; # Poor man's "continue"
|
|||
|
*) return 1 ;;
|
|||
|
esac
|
|||
|
|
|||
|
case $if in
|
|||
|
0) eval T1=\( \"\$\{$1\[@\]\}\" \)
|
|||
|
T2=( $(echo ${T1[@]} | md5sum -) )
|
|||
|
;;
|
|||
|
1) T2=( $(md5sum $1) )
|
|||
|
;;
|
|||
|
esac
|
|||
|
|
|||
|
case ${#T2[@]} in
|
|||
|
0) return 1 ;;
|
|||
|
1) return 1 ;;
|
|||
|
2) case ${T2[1]:0:1} in # SanScrit-2.0.5
|
|||
|
\*) T2[${#T2[@]}]=${T2[1]:1}
|
|||
|
T2[1]=\*
|
|||
|
;;
|
|||
|
*) T2[${#T2[@]}]=${T2[1]}
|
|||
|
T2[1]=" "
|
|||
|
;;
|
|||
|
esac
|
|||
|
;;
|
|||
|
3) : ;; # Assume it worked
|
|||
|
*) return 1 ;;
|
|||
|
esac
|
|||
|
|
|||
|
local -i len=${#T2[0]}
|
|||
|
if [ $len -ne 32 ] ; then return 1 ; fi
|
|||
|
eval $2=\( \"\$\{T2\[@\]\}\" \)
|
|||
|
}
|
|||
|
|
|||
|
# # # # # Locate File # # # # #
|
|||
|
#
|
|||
|
# LocateFile [-l] FileName Location-Array-Name
|
|||
|
# or
|
|||
|
# LocateFile [-l] -of FileName Location-Array-FileName
|
|||
|
# # # # #
|
|||
|
|
|||
|
# A file location is Filesystem-id and inode-number
|
|||
|
|
|||
|
# Here document used as a comment block.
|
|||
|
: <<StatFieldsDoc
|
|||
|
Based on stat, version 2.2
|
|||
|
stat -t and stat -lt fields
|
|||
|
[0] name
|
|||
|
[1] Total size
|
|||
|
File - number of bytes
|
|||
|
Symbolic link - string length of pathname
|
|||
|
[2] Number of (512 byte) blocks allocated
|
|||
|
[3] File type and Access rights (hex)
|
|||
|
[4] User ID of owner
|
|||
|
[5] Group ID of owner
|
|||
|
[6] Device number
|
|||
|
[7] Inode number
|
|||
|
[8] Number of hard links
|
|||
|
[9] Device type (if inode device) Major
|
|||
|
[10] Device type (if inode device) Minor
|
|||
|
[11] Time of last access
|
|||
|
May be disabled in 'mount' with noatime
|
|||
|
atime of files changed by exec, read, pipe, utime, mknod (mmap?)
|
|||
|
atime of directories changed by addition/deletion of files
|
|||
|
[12] Time of last modification
|
|||
|
mtime of files changed by write, truncate, utime, mknod
|
|||
|
mtime of directories changed by addtition/deletion of files
|
|||
|
[13] Time of last change
|
|||
|
ctime reflects time of changed inode information (owner, group
|
|||
|
permissions, link count
|
|||
|
-*-*- Per:
|
|||
|
Return code: 0
|
|||
|
Size of array: 14
|
|||
|
Contents of array
|
|||
|
Element 0: /home/mszick
|
|||
|
Element 1: 4096
|
|||
|
Element 2: 8
|
|||
|
Element 3: 41e8
|
|||
|
Element 4: 500
|
|||
|
Element 5: 500
|
|||
|
Element 6: 303
|
|||
|
Element 7: 32385
|
|||
|
Element 8: 22
|
|||
|
Element 9: 0
|
|||
|
Element 10: 0
|
|||
|
Element 11: 1051221030
|
|||
|
Element 12: 1051214068
|
|||
|
Element 13: 1051214068
|
|||
|
|
|||
|
For a link in the form of linkname -> realname
|
|||
|
stat -t linkname returns the linkname (link) information
|
|||
|
stat -lt linkname returns the realname information
|
|||
|
|
|||
|
stat -tf and stat -ltf fields
|
|||
|
[0] name
|
|||
|
[1] ID-0? # Maybe someday, but Linux stat structure
|
|||
|
[2] ID-0? # does not have either LABEL nor UUID
|
|||
|
# fields, currently information must come
|
|||
|
# from file-system specific utilities
|
|||
|
These will be munged into:
|
|||
|
[1] UUID if possible
|
|||
|
[2] Volume Label if possible
|
|||
|
Note: 'mount -l' does return the label and could return the UUID
|
|||
|
|
|||
|
[3] Maximum length of filenames
|
|||
|
[4] Filesystem type
|
|||
|
[5] Total blocks in the filesystem
|
|||
|
[6] Free blocks
|
|||
|
[7] Free blocks for non-root user(s)
|
|||
|
[8] Block size of the filesystem
|
|||
|
[9] Total inodes
|
|||
|
[10] Free inodes
|
|||
|
|
|||
|
-*-*- Per:
|
|||
|
Return code: 0
|
|||
|
Size of array: 11
|
|||
|
Contents of array
|
|||
|
Element 0: /home/mszick
|
|||
|
Element 1: 0
|
|||
|
Element 2: 0
|
|||
|
Element 3: 255
|
|||
|
Element 4: ef53
|
|||
|
Element 5: 2581445
|
|||
|
Element 6: 2277180
|
|||
|
Element 7: 2146050
|
|||
|
Element 8: 4096
|
|||
|
Element 9: 1311552
|
|||
|
Element 10: 1276425
|
|||
|
|
|||
|
StatFieldsDoc
|
|||
|
|
|||
|
|
|||
|
# LocateFile [-l] FileName Location-Array-Name
|
|||
|
# LocateFile [-l] -of FileName Location-Array-FileName
|
|||
|
|
|||
|
LocateFile()
|
|||
|
{
|
|||
|
local -a LOC LOC1 LOC2
|
|||
|
local lk="" of=0
|
|||
|
|
|||
|
case "$#" in
|
|||
|
0) return 1 ;;
|
|||
|
1) return 1 ;;
|
|||
|
2) : ;;
|
|||
|
*) while (( "$#" > 2 ))
|
|||
|
do
|
|||
|
case "$1" in
|
|||
|
-l) lk=-1 ;;
|
|||
|
-of) of=1 ;;
|
|||
|
*) return 1 ;;
|
|||
|
esac
|
|||
|
shift
|
|||
|
done ;;
|
|||
|
esac
|
|||
|
|
|||
|
# More Sanscrit-2.0.5
|
|||
|
# LOC1=( $(stat -t $lk $1) )
|
|||
|
# LOC2=( $(stat -tf $lk $1) )
|
|||
|
# Uncomment above two lines if system has "stat" command installed.
|
|||
|
LOC=( ${LOC1[@]:0:1} ${LOC1[@]:3:11}
|
|||
|
${LOC2[@]:1:2} ${LOC2[@]:4:1} )
|
|||
|
|
|||
|
case "$of" in
|
|||
|
0) eval $2=\( \"\$\{LOC\[@\]\}\" \) ;;
|
|||
|
1) echo "${LOC[@]}" > "$2" ;;
|
|||
|
esac
|
|||
|
return 0
|
|||
|
# Which yields (if you are lucky, and have "stat" installed)
|
|||
|
# -*-*- Location Discriptor -*-*-
|
|||
|
# Return code: 0
|
|||
|
# Size of array: 15
|
|||
|
# Contents of array
|
|||
|
# Element 0: /home/mszick 20th Century name
|
|||
|
# Element 1: 41e8 Type and Permissions
|
|||
|
# Element 2: 500 User
|
|||
|
# Element 3: 500 Group
|
|||
|
# Element 4: 303 Device
|
|||
|
# Element 5: 32385 inode
|
|||
|
# Element 6: 22 Link count
|
|||
|
# Element 7: 0 Device Major
|
|||
|
# Element 8: 0 Device Minor
|
|||
|
# Element 9: 1051224608 Last Access
|
|||
|
# Element 10: 1051214068 Last Modify
|
|||
|
# Element 11: 1051214068 Last Status
|
|||
|
# Element 12: 0 UUID (to be)
|
|||
|
# Element 13: 0 Volume Label (to be)
|
|||
|
# Element 14: ef53 Filesystem type
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# And then there was some test code
|
|||
|
|
|||
|
ListArray() # ListArray Name
|
|||
|
{
|
|||
|
local -a Ta
|
|||
|
|
|||
|
eval Ta=\( \"\$\{$1\[@\]\}\" \)
|
|||
|
echo
|
|||
|
echo "-*-*- List of Array -*-*-"
|
|||
|
echo "Size of array $1: ${#Ta[*]}"
|
|||
|
echo "Contents of array $1:"
|
|||
|
for (( i=0 ; i<${#Ta[*]} ; i++ ))
|
|||
|
do
|
|||
|
echo -e "\tElement $i: ${Ta[$i]}"
|
|||
|
done
|
|||
|
return 0
|
|||
|
}
|
|||
|
|
|||
|
declare -a CUR_DIR
|
|||
|
# For small arrays
|
|||
|
ListDirectory "${PWD}" CUR_DIR
|
|||
|
ListArray CUR_DIR
|
|||
|
|
|||
|
declare -a DIR_DIG
|
|||
|
DigestFile CUR_DIR DIR_DIG
|
|||
|
echo "The new \"name\" (checksum) for ${CUR_DIR[9]} is ${DIR_DIG[0]}"
|
|||
|
|
|||
|
declare -a DIR_ENT
|
|||
|
# BIG_DIR # For really big arrays - use a temporary file in ramdisk
|
|||
|
# BIG-DIR # ListDirectory -of "${CUR_DIR[11]}/*" "/tmpfs/junk2"
|
|||
|
ListDirectory "${CUR_DIR[11]}/*" DIR_ENT
|
|||
|
|
|||
|
declare -a DIR_IDX
|
|||
|
# BIG-DIR # IndexList -if "/tmpfs/junk2" DIR_IDX
|
|||
|
IndexList DIR_ENT DIR_IDX
|
|||
|
|
|||
|
declare -a IDX_DIG
|
|||
|
# BIG-DIR # DIR_ENT=( $(cat /tmpfs/junk2) )
|
|||
|
# BIG-DIR # DigestFile -if /tmpfs/junk2 IDX_DIG
|
|||
|
DigestFile DIR_ENT IDX_DIG
|
|||
|
# Small (should) be able to parallize IndexList & DigestFile
|
|||
|
# Large (should) be able to parallize IndexList & DigestFile & the assignment
|
|||
|
echo "The \"name\" (checksum) for the contents of ${PWD} is ${IDX_DIG[0]}"
|
|||
|
|
|||
|
declare -a FILE_LOC
|
|||
|
LocateFile ${PWD} FILE_LOC
|
|||
|
ListArray FILE_LOC
|
|||
|
|
|||
|
exit 0</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><P
|
|||
|
>St<53>phane Chazelas demonstrates object-oriented programming in a
|
|||
|
Bash script.</P
|
|||
|
><P
|
|||
|
>Mariusz Gniazdowski contributed a <A
|
|||
|
HREF="internal.html#HASHREF"
|
|||
|
>hash</A
|
|||
|
>
|
|||
|
library for use in scripts.</P
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="HASHLIB"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-20. Library of hash functions</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
># Hash:
|
|||
|
# Hash function library
|
|||
|
# Author: Mariusz Gniazdowski <mariusz.gn-at-gmail.com>
|
|||
|
# Date: 2005-04-07
|
|||
|
|
|||
|
# Functions making emulating hashes in Bash a little less painful.
|
|||
|
|
|||
|
|
|||
|
# Limitations:
|
|||
|
# * Only global variables are supported.
|
|||
|
# * Each hash instance generates one global variable per value.
|
|||
|
# * Variable names collisions are possible
|
|||
|
#+ if you define variable like __hash__hashname_key
|
|||
|
# * Keys must use chars that can be part of a Bash variable name
|
|||
|
#+ (no dashes, periods, etc.).
|
|||
|
# * The hash is created as a variable:
|
|||
|
# ... hashname_keyname
|
|||
|
# So if somone will create hashes like:
|
|||
|
# myhash_ + mykey = myhash__mykey
|
|||
|
# myhash + _mykey = myhash__mykey
|
|||
|
# Then there will be a collision.
|
|||
|
# (This should not pose a major problem.)
|
|||
|
|
|||
|
|
|||
|
Hash_config_varname_prefix=__hash__
|
|||
|
|
|||
|
|
|||
|
# Emulates: hash[key]=value
|
|||
|
#
|
|||
|
# Params:
|
|||
|
# 1 - hash
|
|||
|
# 2 - key
|
|||
|
# 3 - value
|
|||
|
function hash_set {
|
|||
|
eval "${Hash_config_varname_prefix}${1}_${2}=\"${3}\""
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
# Emulates: value=hash[key]
|
|||
|
#
|
|||
|
# Params:
|
|||
|
# 1 - hash
|
|||
|
# 2 - key
|
|||
|
# 3 - value (name of global variable to set)
|
|||
|
function hash_get_into {
|
|||
|
eval "$3=\"\$${Hash_config_varname_prefix}${1}_${2}\""
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
# Emulates: echo hash[key]
|
|||
|
#
|
|||
|
# Params:
|
|||
|
# 1 - hash
|
|||
|
# 2 - key
|
|||
|
# 3 - echo params (like -n, for example)
|
|||
|
function hash_echo {
|
|||
|
eval "echo $3 \"\$${Hash_config_varname_prefix}${1}_${2}\""
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
# Emulates: hash1[key1]=hash2[key2]
|
|||
|
#
|
|||
|
# Params:
|
|||
|
# 1 - hash1
|
|||
|
# 2 - key1
|
|||
|
# 3 - hash2
|
|||
|
# 4 - key2
|
|||
|
function hash_copy {
|
|||
|
eval "${Hash_config_varname_prefix}${1}_${2}\
|
|||
|
=\"\$${Hash_config_varname_prefix}${3}_${4}\""
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
# Emulates: hash[keyN-1]=hash[key2]=...hash[key1]
|
|||
|
#
|
|||
|
# Copies first key to rest of keys.
|
|||
|
#
|
|||
|
# Params:
|
|||
|
# 1 - hash1
|
|||
|
# 2 - key1
|
|||
|
# 3 - key2
|
|||
|
# . . .
|
|||
|
# N - keyN
|
|||
|
function hash_dup {
|
|||
|
local hashName="$1" keyName="$2"
|
|||
|
shift 2
|
|||
|
until [ ${#} -le 0 ]; do
|
|||
|
eval "${Hash_config_varname_prefix}${hashName}_${1}\
|
|||
|
=\"\$${Hash_config_varname_prefix}${hashName}_${keyName}\""
|
|||
|
shift;
|
|||
|
done;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
# Emulates: unset hash[key]
|
|||
|
#
|
|||
|
# Params:
|
|||
|
# 1 - hash
|
|||
|
# 2 - key
|
|||
|
function hash_unset {
|
|||
|
eval "unset ${Hash_config_varname_prefix}${1}_${2}"
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
# Emulates something similar to: ref=&hash[key]
|
|||
|
#
|
|||
|
# The reference is name of the variable in which value is held.
|
|||
|
#
|
|||
|
# Params:
|
|||
|
# 1 - hash
|
|||
|
# 2 - key
|
|||
|
# 3 - ref - Name of global variable to set.
|
|||
|
function hash_get_ref_into {
|
|||
|
eval "$3=\"${Hash_config_varname_prefix}${1}_${2}\""
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
# Emulates something similar to: echo &hash[key]
|
|||
|
#
|
|||
|
# That reference is name of variable in which value is held.
|
|||
|
#
|
|||
|
# Params:
|
|||
|
# 1 - hash
|
|||
|
# 2 - key
|
|||
|
# 3 - echo params (like -n for example)
|
|||
|
function hash_echo_ref {
|
|||
|
eval "echo $3 \"${Hash_config_varname_prefix}${1}_${2}\""
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# Emulates something similar to: $$hash[key](param1, param2, ...)
|
|||
|
#
|
|||
|
# Params:
|
|||
|
# 1 - hash
|
|||
|
# 2 - key
|
|||
|
# 3,4, ... - Function parameters
|
|||
|
function hash_call {
|
|||
|
local hash key
|
|||
|
hash=$1
|
|||
|
key=$2
|
|||
|
shift 2
|
|||
|
eval "eval \"\$${Hash_config_varname_prefix}${hash}_${key} \\\"\\\$@\\\"\""
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
# Emulates something similar to: isset(hash[key]) or hash[key]==NULL
|
|||
|
#
|
|||
|
# Params:
|
|||
|
# 1 - hash
|
|||
|
# 2 - key
|
|||
|
# Returns:
|
|||
|
# 0 - there is such key
|
|||
|
# 1 - there is no such key
|
|||
|
function hash_is_set {
|
|||
|
eval "if [[ \"\${${Hash_config_varname_prefix}${1}_${2}-a}\" = \"a\" &&
|
|||
|
\"\${${Hash_config_varname_prefix}${1}_${2}-b}\" = \"b\" ]]
|
|||
|
then return 1; else return 0; fi"
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
# Emulates something similar to:
|
|||
|
# foreach($hash as $key => $value) { fun($key,$value); }
|
|||
|
#
|
|||
|
# It is possible to write different variations of this function.
|
|||
|
# Here we use a function call to make it as "generic" as possible.
|
|||
|
#
|
|||
|
# Params:
|
|||
|
# 1 - hash
|
|||
|
# 2 - function name
|
|||
|
function hash_foreach {
|
|||
|
local keyname oldIFS="$IFS"
|
|||
|
IFS=' '
|
|||
|
for i in $(eval "echo \${!${Hash_config_varname_prefix}${1}_*}"); do
|
|||
|
keyname=$(eval "echo \${i##${Hash_config_varname_prefix}${1}_}")
|
|||
|
eval "$2 $keyname \"\$$i\""
|
|||
|
done
|
|||
|
IFS="$oldIFS"
|
|||
|
}
|
|||
|
|
|||
|
# NOTE: In lines 103 and 116, ampersand changed.
|
|||
|
# But, it doesn't matter, because these are comment lines anyhow.</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><P
|
|||
|
>Here is an example script using the foregoing hash library.</P
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="HASHEXAMPLE"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-21. Colorizing text using hash functions</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
# hash-example.sh: Colorizing text.
|
|||
|
# Author: Mariusz Gniazdowski <mariusz.gn-at-gmail.com>
|
|||
|
|
|||
|
. Hash.lib # Load the library of functions.
|
|||
|
|
|||
|
hash_set colors red "\033[0;31m"
|
|||
|
hash_set colors blue "\033[0;34m"
|
|||
|
hash_set colors light_blue "\033[1;34m"
|
|||
|
hash_set colors light_red "\033[1;31m"
|
|||
|
hash_set colors cyan "\033[0;36m"
|
|||
|
hash_set colors light_green "\033[1;32m"
|
|||
|
hash_set colors light_gray "\033[0;37m"
|
|||
|
hash_set colors green "\033[0;32m"
|
|||
|
hash_set colors yellow "\033[1;33m"
|
|||
|
hash_set colors light_purple "\033[1;35m"
|
|||
|
hash_set colors purple "\033[0;35m"
|
|||
|
hash_set colors reset_color "\033[0;00m"
|
|||
|
|
|||
|
|
|||
|
# $1 - keyname
|
|||
|
# $2 - value
|
|||
|
try_colors() {
|
|||
|
echo -en "$2"
|
|||
|
echo "This line is $1."
|
|||
|
}
|
|||
|
hash_foreach colors try_colors
|
|||
|
hash_echo colors reset_color -en
|
|||
|
|
|||
|
echo -e '\nLet us overwrite some colors with yellow.\n'
|
|||
|
# It's hard to read yellow text on some terminals.
|
|||
|
hash_dup colors yellow red light_green blue green light_gray cyan
|
|||
|
hash_foreach colors try_colors
|
|||
|
hash_echo colors reset_color -en
|
|||
|
|
|||
|
echo -e '\nLet us delete them and try colors once more . . .\n'
|
|||
|
|
|||
|
for i in red light_green blue green light_gray cyan; do
|
|||
|
hash_unset colors $i
|
|||
|
done
|
|||
|
hash_foreach colors try_colors
|
|||
|
hash_echo colors reset_color -en
|
|||
|
|
|||
|
hash_set other txt "Other examples . . ."
|
|||
|
hash_echo other txt
|
|||
|
hash_get_into other txt text
|
|||
|
echo $text
|
|||
|
|
|||
|
hash_set other my_fun try_colors
|
|||
|
hash_call other my_fun purple "`hash_echo colors purple`"
|
|||
|
hash_echo colors reset_color -en
|
|||
|
|
|||
|
echo; echo "Back to normal?"; echo
|
|||
|
|
|||
|
exit $?
|
|||
|
|
|||
|
# On some terminals, the "light" colors print in bold,
|
|||
|
# and end up looking darker than the normal ones.
|
|||
|
# Why is this? </PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><P
|
|||
|
><A
|
|||
|
NAME="HASHEX2_0"
|
|||
|
></A
|
|||
|
>An example illustrating the mechanics
|
|||
|
of hashing, but from a different point of view.</P
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="HASHEX2"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-22. More on hash functions</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
# $Id: ha.sh,v 1.2 2005/04/21 23:24:26 oliver Exp $
|
|||
|
# Copyright 2005 Oliver Beckstein
|
|||
|
# Released under the GNU Public License
|
|||
|
# Author of script granted permission for inclusion in ABS Guide.
|
|||
|
# (Thank you!)
|
|||
|
|
|||
|
#----------------------------------------------------------------
|
|||
|
# pseudo hash based on indirect parameter expansion
|
|||
|
# API: access through functions:
|
|||
|
#
|
|||
|
# create the hash:
|
|||
|
#
|
|||
|
# newhash Lovers
|
|||
|
#
|
|||
|
# add entries (note single quotes for spaces)
|
|||
|
#
|
|||
|
# addhash Lovers Tristan Isolde
|
|||
|
# addhash Lovers 'Romeo Montague' 'Juliet Capulet'
|
|||
|
#
|
|||
|
# access value by key
|
|||
|
#
|
|||
|
# gethash Lovers Tristan ----> Isolde
|
|||
|
#
|
|||
|
# show all keys
|
|||
|
#
|
|||
|
# keyshash Lovers ----> 'Tristan' 'Romeo Montague'
|
|||
|
#
|
|||
|
#
|
|||
|
# Convention: instead of perls' foo{bar} = boing' syntax,
|
|||
|
# use
|
|||
|
# '_foo_bar=boing' (two underscores, no spaces)
|
|||
|
#
|
|||
|
# 1) store key in _NAME_keys[]
|
|||
|
# 2) store value in _NAME_values[] using the same integer index
|
|||
|
# The integer index for the last entry is _NAME_ptr
|
|||
|
#
|
|||
|
# NOTE: No error or sanity checks, just bare bones.
|
|||
|
|
|||
|
|
|||
|
function _inihash () {
|
|||
|
# private function
|
|||
|
# call at the beginning of each procedure
|
|||
|
# defines: _keys _values _ptr
|
|||
|
#
|
|||
|
# Usage: _inihash NAME
|
|||
|
local name=$1
|
|||
|
_keys=_${name}_keys
|
|||
|
_values=_${name}_values
|
|||
|
_ptr=_${name}_ptr
|
|||
|
}
|
|||
|
|
|||
|
function newhash () {
|
|||
|
# Usage: newhash NAME
|
|||
|
# NAME should not contain spaces or dots.
|
|||
|
# Actually: it must be a legal name for a Bash variable.
|
|||
|
# We rely on Bash automatically recognising arrays.
|
|||
|
local name=$1
|
|||
|
local _keys _values _ptr
|
|||
|
_inihash ${name}
|
|||
|
eval ${_ptr}=0
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
function addhash () {
|
|||
|
# Usage: addhash NAME KEY 'VALUE with spaces'
|
|||
|
# arguments with spaces need to be quoted with single quotes ''
|
|||
|
local name=$1 k="$2" v="$3"
|
|||
|
local _keys _values _ptr
|
|||
|
_inihash ${name}
|
|||
|
|
|||
|
#echo "DEBUG(addhash): ${_ptr}=${!_ptr}"
|
|||
|
|
|||
|
eval let ${_ptr}=${_ptr}+1
|
|||
|
eval "$_keys[${!_ptr}]=\"${k}\""
|
|||
|
eval "$_values[${!_ptr}]=\"${v}\""
|
|||
|
}
|
|||
|
|
|||
|
function gethash () {
|
|||
|
# Usage: gethash NAME KEY
|
|||
|
# Returns boing
|
|||
|
# ERR=0 if entry found, 1 otherwise
|
|||
|
# That's not a proper hash --
|
|||
|
#+ we simply linearly search through the keys.
|
|||
|
local name=$1 key="$2"
|
|||
|
local _keys _values _ptr
|
|||
|
local k v i found h
|
|||
|
_inihash ${name}
|
|||
|
|
|||
|
# _ptr holds the highest index in the hash
|
|||
|
found=0
|
|||
|
|
|||
|
for i in $(seq 1 ${!_ptr}); do
|
|||
|
h="\${${_keys}[${i}]}" # Safer to do it in two steps,
|
|||
|
eval k=${h} #+ especially when quoting for spaces.
|
|||
|
if [ "${k}" = "${key}" ]; then found=1; break; fi
|
|||
|
done;
|
|||
|
|
|||
|
[ ${found} = 0 ] && return 1;
|
|||
|
# else: i is the index that matches the key
|
|||
|
h="\${${_values}[${i}]}"
|
|||
|
eval echo "${h}"
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
function keyshash () {
|
|||
|
# Usage: keyshash NAME
|
|||
|
# Returns list of all keys defined for hash name.
|
|||
|
local name=$1 key="$2"
|
|||
|
local _keys _values _ptr
|
|||
|
local k i h
|
|||
|
_inihash ${name}
|
|||
|
|
|||
|
# _ptr holds the highest index in the hash
|
|||
|
for i in $(seq 1 ${!_ptr}); do
|
|||
|
h="\${${_keys}[${i}]}" # Safer to do it in two steps,
|
|||
|
eval k=${h} #+ especially when quoting for spaces.
|
|||
|
echo -n "'${k}' "
|
|||
|
done;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
# -----------------------------------------------------------------------
|
|||
|
|
|||
|
# Now, let's test it.
|
|||
|
# (Per comments at the beginning of the script.)
|
|||
|
newhash Lovers
|
|||
|
addhash Lovers Tristan Isolde
|
|||
|
addhash Lovers 'Romeo Montague' 'Juliet Capulet'
|
|||
|
|
|||
|
# Output results.
|
|||
|
echo
|
|||
|
gethash Lovers Tristan # Isolde
|
|||
|
echo
|
|||
|
keyshash Lovers # 'Tristan' 'Romeo Montague'
|
|||
|
echo; echo
|
|||
|
|
|||
|
|
|||
|
exit 0
|
|||
|
|
|||
|
# Exercise:
|
|||
|
# --------
|
|||
|
|
|||
|
# Add error checks to the functions.</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><P
|
|||
|
>Now for a script that installs and mounts
|
|||
|
those cute USB keychain solid-state <SPAN
|
|||
|
CLASS="QUOTE"
|
|||
|
>"hard drives."</SPAN
|
|||
|
></P
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="USBINST"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-23. Mounting USB keychain storage devices</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
# ==> usb.sh
|
|||
|
# ==> Script for mounting and installing pen/keychain USB storage devices.
|
|||
|
# ==> Runs as root at system startup (see below).
|
|||
|
# ==>
|
|||
|
# ==> Newer Linux distros (2004 or later) autodetect
|
|||
|
# ==> and install USB pen drives, and therefore don't need this script.
|
|||
|
# ==> But, it's still instructive.
|
|||
|
|
|||
|
# This code is free software covered by GNU GPL license version 2 or above.
|
|||
|
# Please refer to http://www.gnu.org/ for the full license text.
|
|||
|
#
|
|||
|
# Some code lifted from usb-mount by Michael Hamilton's usb-mount (LGPL)
|
|||
|
#+ see http://users.actrix.co.nz/michael/usbmount.html
|
|||
|
#
|
|||
|
# INSTALL
|
|||
|
# -------
|
|||
|
# Put this in /etc/hotplug/usb/diskonkey.
|
|||
|
# Then look in /etc/hotplug/usb.distmap, and copy all usb-storage entries
|
|||
|
#+ into /etc/hotplug/usb.usermap, substituting "usb-storage" for "diskonkey".
|
|||
|
# Otherwise this code is only run during the kernel module invocation/removal
|
|||
|
#+ (at least in my tests), which defeats the purpose.
|
|||
|
#
|
|||
|
# TODO
|
|||
|
# ----
|
|||
|
# Handle more than one diskonkey device at one time (e.g. /dev/diskonkey1
|
|||
|
#+ and /mnt/diskonkey1), etc. The biggest problem here is the handling in
|
|||
|
#+ devlabel, which I haven't yet tried.
|
|||
|
#
|
|||
|
# AUTHOR and SUPPORT
|
|||
|
# ------------------
|
|||
|
# Konstantin Riabitsev, <icon linux duke edu>.
|
|||
|
# Send any problem reports to my email address at the moment.
|
|||
|
#
|
|||
|
# ==> Comments added by ABS Guide author.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
SYMLINKDEV=/dev/diskonkey
|
|||
|
MOUNTPOINT=/mnt/diskonkey
|
|||
|
DEVLABEL=/sbin/devlabel
|
|||
|
DEVLABELCONFIG=/etc/sysconfig/devlabel
|
|||
|
IAM=$0
|
|||
|
|
|||
|
##
|
|||
|
# Functions lifted near-verbatim from usb-mount code.
|
|||
|
#
|
|||
|
function allAttachedScsiUsb {
|
|||
|
find /proc/scsi/ -path '/proc/scsi/usb-storage*' -type f |
|
|||
|
xargs grep -l 'Attached: Yes'
|
|||
|
}
|
|||
|
function scsiDevFromScsiUsb {
|
|||
|
echo $1 | awk -F"[-/]" '{ n=$(NF-1);
|
|||
|
print "/dev/sd" substr("abcdefghijklmnopqrstuvwxyz", n+1, 1) }'
|
|||
|
}
|
|||
|
|
|||
|
if [ "${ACTION}" = "add" ] && [ -f "${DEVICE}" ]; then
|
|||
|
##
|
|||
|
# lifted from usbcam code.
|
|||
|
#
|
|||
|
if [ -f /var/run/console.lock ]; then
|
|||
|
CONSOLEOWNER=`cat /var/run/console.lock`
|
|||
|
elif [ -f /var/lock/console.lock ]; then
|
|||
|
CONSOLEOWNER=`cat /var/lock/console.lock`
|
|||
|
else
|
|||
|
CONSOLEOWNER=
|
|||
|
fi
|
|||
|
for procEntry in $(allAttachedScsiUsb); do
|
|||
|
scsiDev=$(scsiDevFromScsiUsb $procEntry)
|
|||
|
# Some bug with usb-storage?
|
|||
|
# Partitions are not in /proc/partitions until they are accessed
|
|||
|
#+ somehow.
|
|||
|
/sbin/fdisk -l $scsiDev >/dev/null
|
|||
|
##
|
|||
|
# Most devices have partitioning info, so the data would be on
|
|||
|
#+ /dev/sd?1. However, some stupider ones don't have any partitioning
|
|||
|
#+ and use the entire device for data storage. This tries to
|
|||
|
#+ guess semi-intelligently if we have a /dev/sd?1 and if not, then
|
|||
|
#+ it uses the entire device and hopes for the better.
|
|||
|
#
|
|||
|
if grep -q `basename $scsiDev`1 /proc/partitions; then
|
|||
|
part="$scsiDev""1"
|
|||
|
else
|
|||
|
part=$scsiDev
|
|||
|
fi
|
|||
|
##
|
|||
|
# Change ownership of the partition to the console user so they can
|
|||
|
#+ mount it.
|
|||
|
#
|
|||
|
if [ ! -z "$CONSOLEOWNER" ]; then
|
|||
|
chown $CONSOLEOWNER:disk $part
|
|||
|
fi
|
|||
|
##
|
|||
|
# This checks if we already have this UUID defined with devlabel.
|
|||
|
# If not, it then adds the device to the list.
|
|||
|
#
|
|||
|
prodid=`$DEVLABEL printid -d $part`
|
|||
|
if ! grep -q $prodid $DEVLABELCONFIG; then
|
|||
|
# cross our fingers and hope it works
|
|||
|
$DEVLABEL add -d $part -s $SYMLINKDEV 2>/dev/null
|
|||
|
fi
|
|||
|
##
|
|||
|
# Check if the mount point exists and create if it doesn't.
|
|||
|
#
|
|||
|
if [ ! -e $MOUNTPOINT ]; then
|
|||
|
mkdir -p $MOUNTPOINT
|
|||
|
fi
|
|||
|
##
|
|||
|
# Take care of /etc/fstab so mounting is easy.
|
|||
|
#
|
|||
|
if ! grep -q "^$SYMLINKDEV" /etc/fstab; then
|
|||
|
# Add an fstab entry
|
|||
|
echo -e \
|
|||
|
"$SYMLINKDEV\t\t$MOUNTPOINT\t\tauto\tnoauto,owner,kudzu 0 0" \
|
|||
|
>> /etc/fstab
|
|||
|
fi
|
|||
|
done
|
|||
|
if [ ! -z "$REMOVER" ]; then
|
|||
|
##
|
|||
|
# Make sure this script is triggered on device removal.
|
|||
|
#
|
|||
|
mkdir -p `dirname $REMOVER`
|
|||
|
ln -s $IAM $REMOVER
|
|||
|
fi
|
|||
|
elif [ "${ACTION}" = "remove" ]; then
|
|||
|
##
|
|||
|
# If the device is mounted, unmount it cleanly.
|
|||
|
#
|
|||
|
if grep -q "$MOUNTPOINT" /etc/mtab; then
|
|||
|
# unmount cleanly
|
|||
|
umount -l $MOUNTPOINT
|
|||
|
fi
|
|||
|
##
|
|||
|
# Remove it from /etc/fstab if it's there.
|
|||
|
#
|
|||
|
if grep -q "^$SYMLINKDEV" /etc/fstab; then
|
|||
|
grep -v "^$SYMLINKDEV" /etc/fstab > /etc/.fstab.new
|
|||
|
mv -f /etc/.fstab.new /etc/fstab
|
|||
|
fi
|
|||
|
fi
|
|||
|
|
|||
|
exit 0</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><P
|
|||
|
>Converting a text file to HTML format.</P
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="TOHTML"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-24. Converting to HTML</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
# tohtml.sh [v. 0.2.01, reldate: 04/13/12, a teeny bit less buggy]
|
|||
|
|
|||
|
# Convert a text file to HTML format.
|
|||
|
# Author: Mendel Cooper
|
|||
|
# License: GPL3
|
|||
|
# Usage: sh tohtml.sh < textfile > htmlfile
|
|||
|
# Script can easily be modified to accept source and target filenames.
|
|||
|
|
|||
|
# Assumptions:
|
|||
|
# 1) Paragraphs in (target) text file are separated by a blank line.
|
|||
|
# 2) Jpeg images (*.jpg) are located in "images" subdirectory.
|
|||
|
# In the target file, the image names are enclosed in square brackets,
|
|||
|
# for example, [image01.jpg].
|
|||
|
# 3) Emphasized (italic) phrases begin with a space+underscore
|
|||
|
#+ or the first character on the line is an underscore,
|
|||
|
#+ and end with an underscore+space or underscore+end-of-line.
|
|||
|
|
|||
|
|
|||
|
# Settings
|
|||
|
FNTSIZE=2 # Small-medium font size
|
|||
|
IMGDIR="images" # Image directory
|
|||
|
# Headers
|
|||
|
HDR01='<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">'
|
|||
|
HDR02='<!-- Converted to HTML by ***tohtml.sh*** script -->'
|
|||
|
HDR03='<!-- script author: M. Leo Cooper <thegrendel.abs@gmail.com> -->'
|
|||
|
HDR10='<html>'
|
|||
|
HDR11='<head>'
|
|||
|
HDR11a='</head>'
|
|||
|
HDR12a='<title>'
|
|||
|
HDR12b='</title>'
|
|||
|
HDR121='<META NAME="GENERATOR" CONTENT="tohtml.sh script">'
|
|||
|
HDR13='<body bgcolor="#dddddd">' # Change background color to suit.
|
|||
|
HDR14a='<font size='
|
|||
|
HDR14b='>'
|
|||
|
# Footers
|
|||
|
FTR10='</body>'
|
|||
|
FTR11='</html>'
|
|||
|
# Tags
|
|||
|
BOLD="<b>"
|
|||
|
CENTER="<center>"
|
|||
|
END_CENTER="</center>"
|
|||
|
LF="<br>"
|
|||
|
|
|||
|
|
|||
|
write_headers ()
|
|||
|
{
|
|||
|
echo "$HDR01"
|
|||
|
echo
|
|||
|
echo "$HDR02"
|
|||
|
echo "$HDR03"
|
|||
|
echo
|
|||
|
echo
|
|||
|
echo "$HDR10"
|
|||
|
echo "$HDR11"
|
|||
|
echo "$HDR121"
|
|||
|
echo "$HDR11a"
|
|||
|
echo "$HDR13"
|
|||
|
echo
|
|||
|
echo -n "$HDR14a"
|
|||
|
echo -n "$FNTSIZE"
|
|||
|
echo "$HDR14b"
|
|||
|
echo
|
|||
|
echo "$BOLD" # Everything in bold (more easily readable).
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
process_text ()
|
|||
|
{
|
|||
|
while read line # Read one line at a time.
|
|||
|
do
|
|||
|
{
|
|||
|
if [ ! "$line" ] # Blank line?
|
|||
|
then # Then new paragraph must follow.
|
|||
|
echo
|
|||
|
echo "$LF" # Insert two <br> tags.
|
|||
|
echo "$LF"
|
|||
|
echo
|
|||
|
continue # Skip the underscore test.
|
|||
|
else # Otherwise . . .
|
|||
|
|
|||
|
if [[ "$line" =~ \[*jpg\] ]] # Is a graphic?
|
|||
|
then # Strip away brackets.
|
|||
|
temp=$( echo "$line" | sed -e 's/\[//' -e 's/\]//' )
|
|||
|
line=""$CENTER" <img src="\"$IMGDIR"/$temp\"> "$END_CENTER" "
|
|||
|
# Add image tag.
|
|||
|
# And, center it.
|
|||
|
fi
|
|||
|
|
|||
|
fi
|
|||
|
|
|||
|
|
|||
|
echo "$line" | grep -q _
|
|||
|
if [ "$?" -eq 0 ] # If line contains underscore ...
|
|||
|
then
|
|||
|
# ===================================================
|
|||
|
# Convert underscored phrase to italics.
|
|||
|
temp=$( echo "$line" |
|
|||
|
sed -e 's/ _/ <i>/' -e 's/_/<\/i> /' |
|
|||
|
sed -e 's/^_/<i>/' -e 's/_/<\/i>/' )
|
|||
|
# Process only underscores prefixed by space,
|
|||
|
#+ or at beginning or end of line.
|
|||
|
# Do not convert underscores embedded within a word!
|
|||
|
line="$temp"
|
|||
|
# Slows script execution. Can be optimized?
|
|||
|
# ===================================================
|
|||
|
fi
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# echo
|
|||
|
echo "$line"
|
|||
|
# echo
|
|||
|
# Don't want extra blank lines in generated text!
|
|||
|
} # End while
|
|||
|
done
|
|||
|
} # End process_text ()
|
|||
|
|
|||
|
|
|||
|
write_footers () # Termination tags.
|
|||
|
{
|
|||
|
echo "$FTR10"
|
|||
|
echo "$FTR11"
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
# main () {
|
|||
|
# =========
|
|||
|
write_headers
|
|||
|
process_text
|
|||
|
write_footers
|
|||
|
# =========
|
|||
|
# }
|
|||
|
|
|||
|
exit $?
|
|||
|
|
|||
|
# Exercises:
|
|||
|
# ---------
|
|||
|
# 1) Fixup: Check for closing underscore before a comma or period.
|
|||
|
# 2) Add a test for the presence of a closing underscore
|
|||
|
#+ in phrases to be italicized.</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><P
|
|||
|
>Here is something to warm the hearts of webmasters and mistresses:
|
|||
|
a script that saves weblogs.</P
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="ARCHIVWEBLOGS"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-25. Preserving weblogs</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
# archiveweblogs.sh v1.0
|
|||
|
|
|||
|
# Troy Engel <tengel@fluid.com>
|
|||
|
# Slightly modified by document author.
|
|||
|
# Used with permission.
|
|||
|
#
|
|||
|
# This script will preserve the normally rotated and
|
|||
|
#+ thrown away weblogs from a default RedHat/Apache installation.
|
|||
|
# It will save the files with a date/time stamp in the filename,
|
|||
|
#+ bzipped, to a given directory.
|
|||
|
#
|
|||
|
# Run this from crontab nightly at an off hour,
|
|||
|
#+ as bzip2 can suck up some serious CPU on huge logs:
|
|||
|
# 0 2 * * * /opt/sbin/archiveweblogs.sh
|
|||
|
|
|||
|
|
|||
|
PROBLEM=66
|
|||
|
|
|||
|
# Set this to your backup dir.
|
|||
|
BKP_DIR=/opt/backups/weblogs
|
|||
|
|
|||
|
# Default Apache/RedHat stuff
|
|||
|
LOG_DAYS="4 3 2 1"
|
|||
|
LOG_DIR=/var/log/httpd
|
|||
|
LOG_FILES="access_log error_log"
|
|||
|
|
|||
|
# Default RedHat program locations
|
|||
|
LS=/bin/ls
|
|||
|
MV=/bin/mv
|
|||
|
ID=/usr/bin/id
|
|||
|
CUT=/bin/cut
|
|||
|
COL=/usr/bin/column
|
|||
|
BZ2=/usr/bin/bzip2
|
|||
|
|
|||
|
# Are we root?
|
|||
|
USER=`$ID -u`
|
|||
|
if [ "X$USER" != "X0" ]; then
|
|||
|
echo "PANIC: Only root can run this script!"
|
|||
|
exit $PROBLEM
|
|||
|
fi
|
|||
|
|
|||
|
# Backup dir exists/writable?
|
|||
|
if [ ! -x $BKP_DIR ]; then
|
|||
|
echo "PANIC: $BKP_DIR doesn't exist or isn't writable!"
|
|||
|
exit $PROBLEM
|
|||
|
fi
|
|||
|
|
|||
|
# Move, rename and bzip2 the logs
|
|||
|
for logday in $LOG_DAYS; do
|
|||
|
for logfile in $LOG_FILES; do
|
|||
|
MYFILE="$LOG_DIR/$logfile.$logday"
|
|||
|
if [ -w $MYFILE ]; then
|
|||
|
DTS=`$LS -lgo --time-style=+%Y%m%d $MYFILE | $COL -t | $CUT -d ' ' -f7`
|
|||
|
$MV $MYFILE $BKP_DIR/$logfile.$DTS
|
|||
|
$BZ2 $BKP_DIR/$logfile.$DTS
|
|||
|
else
|
|||
|
# Only spew an error if the file exits (ergo non-writable).
|
|||
|
if [ -f $MYFILE ]; then
|
|||
|
echo "ERROR: $MYFILE not writable. Skipping."
|
|||
|
fi
|
|||
|
fi
|
|||
|
done
|
|||
|
done
|
|||
|
|
|||
|
exit 0</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><P
|
|||
|
><A
|
|||
|
NAME="PROTECTLITERAL0"
|
|||
|
></A
|
|||
|
>How to keep the shell from
|
|||
|
expanding and reinterpreting text strings.</P
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="PROTECTLITERAL"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-26. Protecting literal strings</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#! /bin/bash
|
|||
|
# protect_literal.sh
|
|||
|
|
|||
|
# set -vx
|
|||
|
|
|||
|
:<<-'_Protect_Literal_String_Doc'
|
|||
|
|
|||
|
Copyright (c) Michael S. Zick, 2003; All Rights Reserved
|
|||
|
License: Unrestricted reuse in any form, for any purpose.
|
|||
|
Warranty: None
|
|||
|
Revision: $ID$
|
|||
|
|
|||
|
Documentation redirected to the Bash no-operation.
|
|||
|
Bash will '/dev/null' this block when the script is first read.
|
|||
|
(Uncomment the above set command to see this action.)
|
|||
|
|
|||
|
Remove the first (Sha-Bang) line when sourcing this as a library
|
|||
|
procedure. Also comment out the example use code in the two
|
|||
|
places where shown.
|
|||
|
|
|||
|
|
|||
|
Usage:
|
|||
|
_protect_literal_str 'Whatever string meets your ${fancy}'
|
|||
|
Just echos the argument to standard out, hard quotes
|
|||
|
restored.
|
|||
|
|
|||
|
$(_protect_literal_str 'Whatever string meets your ${fancy}')
|
|||
|
as the right-hand-side of an assignment statement.
|
|||
|
|
|||
|
Does:
|
|||
|
As the right-hand-side of an assignment, preserves the
|
|||
|
hard quotes protecting the contents of the literal during
|
|||
|
assignment.
|
|||
|
|
|||
|
Notes:
|
|||
|
The strange names (_*) are used to avoid trampling on
|
|||
|
the user's chosen names when this is sourced as a
|
|||
|
library.
|
|||
|
|
|||
|
_Protect_Literal_String_Doc
|
|||
|
|
|||
|
# The 'for illustration' function form
|
|||
|
|
|||
|
_protect_literal_str() {
|
|||
|
|
|||
|
# Pick an un-used, non-printing character as local IFS.
|
|||
|
# Not required, but shows that we are ignoring it.
|
|||
|
local IFS=$'\x1B' # \ESC character
|
|||
|
|
|||
|
# Enclose the All-Elements-Of in hard quotes during assignment.
|
|||
|
local tmp=$'\x27'$@$'\x27'
|
|||
|
# local tmp=$'\''$@$'\'' # Even uglier.
|
|||
|
|
|||
|
local len=${#tmp} # Info only.
|
|||
|
echo $tmp is $len long. # Output AND information.
|
|||
|
}
|
|||
|
|
|||
|
# This is the short-named version.
|
|||
|
_pls() {
|
|||
|
local IFS=$'x1B' # \ESC character (not required)
|
|||
|
echo $'\x27'$@$'\x27' # Hard quoted parameter glob
|
|||
|
}
|
|||
|
|
|||
|
# :<<-'_Protect_Literal_String_Test'
|
|||
|
# # # Remove the above "# " to disable this code. # # #
|
|||
|
|
|||
|
# See how that looks when printed.
|
|||
|
echo
|
|||
|
echo "- - Test One - -"
|
|||
|
_protect_literal_str 'Hello $user'
|
|||
|
_protect_literal_str 'Hello "${username}"'
|
|||
|
echo
|
|||
|
|
|||
|
# Which yields:
|
|||
|
# - - Test One - -
|
|||
|
# 'Hello $user' is 13 long.
|
|||
|
# 'Hello "${username}"' is 21 long.
|
|||
|
|
|||
|
# Looks as expected, but why all of the trouble?
|
|||
|
# The difference is hidden inside the Bash internal order
|
|||
|
#+ of operations.
|
|||
|
# Which shows when you use it on the RHS of an assignment.
|
|||
|
|
|||
|
# Declare an array for test values.
|
|||
|
declare -a arrayZ
|
|||
|
|
|||
|
# Assign elements with various types of quotes and escapes.
|
|||
|
arrayZ=( zero "$(_pls 'Hello ${Me}')" 'Hello ${You}' "\'Pass: ${pw}\'" )
|
|||
|
|
|||
|
# Now list that array and see what is there.
|
|||
|
echo "- - Test Two - -"
|
|||
|
for (( i=0 ; i<${#arrayZ[*]} ; i++ ))
|
|||
|
do
|
|||
|
echo Element $i: ${arrayZ[$i]} is: ${#arrayZ[$i]} long.
|
|||
|
done
|
|||
|
echo
|
|||
|
|
|||
|
# Which yields:
|
|||
|
# - - Test Two - -
|
|||
|
# Element 0: zero is: 4 long. # Our marker element
|
|||
|
# Element 1: 'Hello ${Me}' is: 13 long. # Our "$(_pls '...' )"
|
|||
|
# Element 2: Hello ${You} is: 12 long. # Quotes are missing
|
|||
|
# Element 3: \'Pass: \' is: 10 long. # ${pw} expanded to nothing
|
|||
|
|
|||
|
# Now make an assignment with that result.
|
|||
|
declare -a array2=( ${arrayZ[@]} )
|
|||
|
|
|||
|
# And print what happened.
|
|||
|
echo "- - Test Three - -"
|
|||
|
for (( i=0 ; i<${#array2[*]} ; i++ ))
|
|||
|
do
|
|||
|
echo Element $i: ${array2[$i]} is: ${#array2[$i]} long.
|
|||
|
done
|
|||
|
echo
|
|||
|
|
|||
|
# Which yields:
|
|||
|
# - - Test Three - -
|
|||
|
# Element 0: zero is: 4 long. # Our marker element.
|
|||
|
# Element 1: Hello ${Me} is: 11 long. # Intended result.
|
|||
|
# Element 2: Hello is: 5 long. # ${You} expanded to nothing.
|
|||
|
# Element 3: 'Pass: is: 6 long. # Split on the whitespace.
|
|||
|
# Element 4: ' is: 1 long. # The end quote is here now.
|
|||
|
|
|||
|
# Our Element 1 has had its leading and trailing hard quotes stripped.
|
|||
|
# Although not shown, leading and trailing whitespace is also stripped.
|
|||
|
# Now that the string contents are set, Bash will always, internally,
|
|||
|
#+ hard quote the contents as required during its operations.
|
|||
|
|
|||
|
# Why?
|
|||
|
# Considering our "$(_pls 'Hello ${Me}')" construction:
|
|||
|
# " ... " -> Expansion required, strip the quotes.
|
|||
|
# $( ... ) -> Replace with the result of..., strip this.
|
|||
|
# _pls ' ... ' -> called with literal arguments, strip the quotes.
|
|||
|
# The result returned includes hard quotes; BUT the above processing
|
|||
|
#+ has already been done, so they become part of the value assigned.
|
|||
|
#
|
|||
|
# Similarly, during further usage of the string variable, the ${Me}
|
|||
|
#+ is part of the contents (result) and survives any operations
|
|||
|
# (Until explicitly told to evaluate the string).
|
|||
|
|
|||
|
# Hint: See what happens when the hard quotes ($'\x27') are replaced
|
|||
|
#+ with soft quotes ($'\x22') in the above procedures.
|
|||
|
# Interesting also is to remove the addition of any quoting.
|
|||
|
|
|||
|
# _Protect_Literal_String_Test
|
|||
|
# # # Remove the above "# " to disable this code. # # #
|
|||
|
|
|||
|
exit 0</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><P
|
|||
|
><A
|
|||
|
NAME="UNPROTECTLITERAL0"
|
|||
|
></A
|
|||
|
>But, what if you
|
|||
|
<EM
|
|||
|
>want</EM
|
|||
|
> the shell to expand
|
|||
|
and reinterpret strings?</P
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="UNPROTECTLITERAL"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-27. Unprotecting literal strings</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#! /bin/bash
|
|||
|
# unprotect_literal.sh
|
|||
|
|
|||
|
# set -vx
|
|||
|
|
|||
|
:<<-'_UnProtect_Literal_String_Doc'
|
|||
|
|
|||
|
Copyright (c) Michael S. Zick, 2003; All Rights Reserved
|
|||
|
License: Unrestricted reuse in any form, for any purpose.
|
|||
|
Warranty: None
|
|||
|
Revision: $ID$
|
|||
|
|
|||
|
Documentation redirected to the Bash no-operation. Bash will
|
|||
|
'/dev/null' this block when the script is first read.
|
|||
|
(Uncomment the above set command to see this action.)
|
|||
|
|
|||
|
Remove the first (Sha-Bang) line when sourcing this as a library
|
|||
|
procedure. Also comment out the example use code in the two
|
|||
|
places where shown.
|
|||
|
|
|||
|
|
|||
|
Usage:
|
|||
|
Complement of the "$(_pls 'Literal String')" function.
|
|||
|
(See the protect_literal.sh example.)
|
|||
|
|
|||
|
StringVar=$(_upls ProtectedSringVariable)
|
|||
|
|
|||
|
Does:
|
|||
|
When used on the right-hand-side of an assignment statement;
|
|||
|
makes the substitions embedded in the protected string.
|
|||
|
|
|||
|
Notes:
|
|||
|
The strange names (_*) are used to avoid trampling on
|
|||
|
the user's chosen names when this is sourced as a
|
|||
|
library.
|
|||
|
|
|||
|
|
|||
|
_UnProtect_Literal_String_Doc
|
|||
|
|
|||
|
_upls() {
|
|||
|
local IFS=$'x1B' # \ESC character (not required)
|
|||
|
eval echo $@ # Substitution on the glob.
|
|||
|
}
|
|||
|
|
|||
|
# :<<-'_UnProtect_Literal_String_Test'
|
|||
|
# # # Remove the above "# " to disable this code. # # #
|
|||
|
|
|||
|
|
|||
|
_pls() {
|
|||
|
local IFS=$'x1B' # \ESC character (not required)
|
|||
|
echo $'\x27'$@$'\x27' # Hard quoted parameter glob
|
|||
|
}
|
|||
|
|
|||
|
# Declare an array for test values.
|
|||
|
declare -a arrayZ
|
|||
|
|
|||
|
# Assign elements with various types of quotes and escapes.
|
|||
|
arrayZ=( zero "$(_pls 'Hello ${Me}')" 'Hello ${You}' "\'Pass: ${pw}\'" )
|
|||
|
|
|||
|
# Now make an assignment with that result.
|
|||
|
declare -a array2=( ${arrayZ[@]} )
|
|||
|
|
|||
|
# Which yielded:
|
|||
|
# - - Test Three - -
|
|||
|
# Element 0: zero is: 4 long # Our marker element.
|
|||
|
# Element 1: Hello ${Me} is: 11 long # Intended result.
|
|||
|
# Element 2: Hello is: 5 long # ${You} expanded to nothing.
|
|||
|
# Element 3: 'Pass: is: 6 long # Split on the whitespace.
|
|||
|
# Element 4: ' is: 1 long # The end quote is here now.
|
|||
|
|
|||
|
# set -vx
|
|||
|
|
|||
|
# Initialize 'Me' to something for the embedded ${Me} substitution.
|
|||
|
# This needs to be done ONLY just prior to evaluating the
|
|||
|
#+ protected string.
|
|||
|
# (This is why it was protected to begin with.)
|
|||
|
|
|||
|
Me="to the array guy."
|
|||
|
|
|||
|
# Set a string variable destination to the result.
|
|||
|
newVar=$(_upls ${array2[1]})
|
|||
|
|
|||
|
# Show what the contents are.
|
|||
|
echo $newVar
|
|||
|
|
|||
|
# Do we really need a function to do this?
|
|||
|
newerVar=$(eval echo ${array2[1]})
|
|||
|
echo $newerVar
|
|||
|
|
|||
|
# I guess not, but the _upls function gives us a place to hang
|
|||
|
#+ the documentation on.
|
|||
|
# This helps when we forget what a # construction like:
|
|||
|
#+ $(eval echo ... ) means.
|
|||
|
|
|||
|
# What if Me isn't set when the protected string is evaluated?
|
|||
|
unset Me
|
|||
|
newestVar=$(_upls ${array2[1]})
|
|||
|
echo $newestVar
|
|||
|
|
|||
|
# Just gone, no hints, no runs, no errors.
|
|||
|
|
|||
|
# Why in the world?
|
|||
|
# Setting the contents of a string variable containing character
|
|||
|
#+ sequences that have a meaning in Bash is a general problem in
|
|||
|
#+ script programming.
|
|||
|
#
|
|||
|
# This problem is now solved in eight lines of code
|
|||
|
#+ (and four pages of description).
|
|||
|
|
|||
|
# Where is all this going?
|
|||
|
# Dynamic content Web pages as an array of Bash strings.
|
|||
|
# Content set per request by a Bash 'eval' command
|
|||
|
#+ on the stored page template.
|
|||
|
# Not intended to replace PHP, just an interesting thing to do.
|
|||
|
###
|
|||
|
# Don't have a webserver application?
|
|||
|
# No problem, check the example directory of the Bash source;
|
|||
|
#+ there is a Bash script for that also.
|
|||
|
|
|||
|
# _UnProtect_Literal_String_Test
|
|||
|
# # # Remove the above "# " to disable this code. # # #
|
|||
|
|
|||
|
exit 0</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><P
|
|||
|
>This interesting script helps hunt down spammers.</P
|
|||
|
><P
|
|||
|
><A
|
|||
|
NAME="ISSPAMMER2_0"
|
|||
|
></A
|
|||
|
></P
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="ISSPAMMER2"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-28. Spammer Identification</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
|
|||
|
# $Id: is_spammer.bash,v 1.12.2.11 2004/10/01 21:42:33 mszick Exp $
|
|||
|
# Above line is RCS info.
|
|||
|
|
|||
|
# The latest version of this script is available from http://www.morethan.org.
|
|||
|
#
|
|||
|
# Spammer-identification
|
|||
|
# by Michael S. Zick
|
|||
|
# Used in the ABS Guide with permission.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
#######################################################
|
|||
|
# Documentation
|
|||
|
# See also "Quickstart" at end of script.
|
|||
|
#######################################################
|
|||
|
|
|||
|
:<<-'__is_spammer_Doc_'
|
|||
|
|
|||
|
Copyright (c) Michael S. Zick, 2004
|
|||
|
License: Unrestricted reuse in any form, for any purpose.
|
|||
|
Warranty: None -{Its a script; the user is on their own.}-
|
|||
|
|
|||
|
Impatient?
|
|||
|
Application code: goto "# # # Hunt the Spammer' program code # # #"
|
|||
|
Example output: ":<<-'_is_spammer_outputs_'"
|
|||
|
How to use: Enter script name without arguments.
|
|||
|
Or goto "Quickstart" at end of script.
|
|||
|
|
|||
|
Provides
|
|||
|
Given a domain name or IP(v4) address as input:
|
|||
|
|
|||
|
Does an exhaustive set of queries to find the associated
|
|||
|
network resources (short of recursing into TLDs).
|
|||
|
|
|||
|
Checks the IP(v4) addresses found against Blacklist
|
|||
|
nameservers.
|
|||
|
|
|||
|
If found to be a blacklisted IP(v4) address,
|
|||
|
reports the blacklist text records.
|
|||
|
(Usually hyper-links to the specific report.)
|
|||
|
|
|||
|
Requires
|
|||
|
A working Internet connection.
|
|||
|
(Exercise: Add check and/or abort if not on-line when running script.)
|
|||
|
Bash with arrays (2.05b+).
|
|||
|
|
|||
|
The external program 'dig' --
|
|||
|
a utility program provided with the 'bind' set of programs.
|
|||
|
Specifically, the version which is part of Bind series 9.x
|
|||
|
See: http://www.isc.org
|
|||
|
|
|||
|
All usages of 'dig' are limited to wrapper functions,
|
|||
|
which may be rewritten as required.
|
|||
|
See: dig_wrappers.bash for details.
|
|||
|
("Additional documentation" -- below)
|
|||
|
|
|||
|
Usage
|
|||
|
Script requires a single argument, which may be:
|
|||
|
1) A domain name;
|
|||
|
2) An IP(v4) address;
|
|||
|
3) A filename, with one name or address per line.
|
|||
|
|
|||
|
Script accepts an optional second argument, which may be:
|
|||
|
1) A Blacklist server name;
|
|||
|
2) A filename, with one Blacklist server name per line.
|
|||
|
|
|||
|
If the second argument is not provided, the script uses
|
|||
|
a built-in set of (free) Blacklist servers.
|
|||
|
|
|||
|
See also, the Quickstart at the end of this script (after 'exit').
|
|||
|
|
|||
|
Return Codes
|
|||
|
0 - All OK
|
|||
|
1 - Script failure
|
|||
|
2 - Something is Blacklisted
|
|||
|
|
|||
|
Optional environment variables
|
|||
|
SPAMMER_TRACE
|
|||
|
If set to a writable file,
|
|||
|
script will log an execution flow trace.
|
|||
|
|
|||
|
SPAMMER_DATA
|
|||
|
If set to a writable file, script will dump its
|
|||
|
discovered data in the form of GraphViz file.
|
|||
|
See: http://www.research.att.com/sw/tools/graphviz
|
|||
|
|
|||
|
SPAMMER_LIMIT
|
|||
|
Limits the depth of resource tracing.
|
|||
|
|
|||
|
Default is 2 levels.
|
|||
|
|
|||
|
A setting of 0 (zero) means 'unlimited' . . .
|
|||
|
Caution: script might recurse the whole Internet!
|
|||
|
|
|||
|
A limit of 1 or 2 is most useful when processing
|
|||
|
a file of domain names and addresses.
|
|||
|
A higher limit can be useful when hunting spam gangs.
|
|||
|
|
|||
|
|
|||
|
Additional documentation
|
|||
|
Download the archived set of scripts
|
|||
|
explaining and illustrating the function contained within this script.
|
|||
|
http://bash.deta.in/mszick_clf.tar.bz2
|
|||
|
|
|||
|
|
|||
|
Study notes
|
|||
|
This script uses a large number of functions.
|
|||
|
Nearly all general functions have their own example script.
|
|||
|
Each of the example scripts have tutorial level comments.
|
|||
|
|
|||
|
Scripting project
|
|||
|
Add support for IP(v6) addresses.
|
|||
|
IP(v6) addresses are recognized but not processed.
|
|||
|
|
|||
|
Advanced project
|
|||
|
Add the reverse lookup detail to the discovered information.
|
|||
|
|
|||
|
Report the delegation chain and abuse contacts.
|
|||
|
|
|||
|
Modify the GraphViz file output to include the
|
|||
|
newly discovered information.
|
|||
|
|
|||
|
__is_spammer_Doc_
|
|||
|
|
|||
|
#######################################################
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
#### Special IFS settings used for string parsing. ####
|
|||
|
|
|||
|
# Whitespace == :Space:Tab:Line Feed:Carriage Return:
|
|||
|
WSP_IFS=$'\x20'$'\x09'$'\x0A'$'\x0D'
|
|||
|
|
|||
|
# No Whitespace == Line Feed:Carriage Return
|
|||
|
NO_WSP=$'\x0A'$'\x0D'
|
|||
|
|
|||
|
# Field separator for dotted decimal IP addresses
|
|||
|
ADR_IFS=${NO_WSP}'.'
|
|||
|
|
|||
|
# Array to dotted string conversions
|
|||
|
DOT_IFS='.'${WSP_IFS}
|
|||
|
|
|||
|
# # # Pending operations stack machine # # #
|
|||
|
# This set of functions described in func_stack.bash.
|
|||
|
# (See "Additional documentation" above.)
|
|||
|
# # #
|
|||
|
|
|||
|
# Global stack of pending operations.
|
|||
|
declare -f -a _pending_
|
|||
|
# Global sentinel for stack runners
|
|||
|
declare -i _p_ctrl_
|
|||
|
# Global holder for currently executing function
|
|||
|
declare -f _pend_current_
|
|||
|
|
|||
|
# # # Debug version only - remove for regular use # # #
|
|||
|
#
|
|||
|
# The function stored in _pend_hook_ is called
|
|||
|
# immediately before each pending function is
|
|||
|
# evaluated. Stack clean, _pend_current_ set.
|
|||
|
#
|
|||
|
# This thingy demonstrated in pend_hook.bash.
|
|||
|
declare -f _pend_hook_
|
|||
|
# # #
|
|||
|
|
|||
|
# The do nothing function
|
|||
|
pend_dummy() { : ; }
|
|||
|
|
|||
|
# Clear and initialize the function stack.
|
|||
|
pend_init() {
|
|||
|
unset _pending_[@]
|
|||
|
pend_func pend_stop_mark
|
|||
|
_pend_hook_='pend_dummy' # Debug only.
|
|||
|
}
|
|||
|
|
|||
|
# Discard the top function on the stack.
|
|||
|
pend_pop() {
|
|||
|
if [ ${#_pending_[@]} -gt 0 ]
|
|||
|
then
|
|||
|
local -i _top_
|
|||
|
_top_=${#_pending_[@]}-1
|
|||
|
unset _pending_[$_top_]
|
|||
|
fi
|
|||
|
}
|
|||
|
|
|||
|
# pend_func function_name [$(printf '%q\n' arguments)]
|
|||
|
pend_func() {
|
|||
|
local IFS=${NO_WSP}
|
|||
|
set -f
|
|||
|
_pending_[${#_pending_[@]}]=$@
|
|||
|
set +f
|
|||
|
}
|
|||
|
|
|||
|
# The function which stops the release:
|
|||
|
pend_stop_mark() {
|
|||
|
_p_ctrl_=0
|
|||
|
}
|
|||
|
|
|||
|
pend_mark() {
|
|||
|
pend_func pend_stop_mark
|
|||
|
}
|
|||
|
|
|||
|
# Execute functions until 'pend_stop_mark' . . .
|
|||
|
pend_release() {
|
|||
|
local -i _top_ # Declare _top_ as integer.
|
|||
|
_p_ctrl_=${#_pending_[@]}
|
|||
|
while [ ${_p_ctrl_} -gt 0 ]
|
|||
|
do
|
|||
|
_top_=${#_pending_[@]}-1
|
|||
|
_pend_current_=${_pending_[$_top_]}
|
|||
|
unset _pending_[$_top_]
|
|||
|
$_pend_hook_ # Debug only.
|
|||
|
eval $_pend_current_
|
|||
|
done
|
|||
|
}
|
|||
|
|
|||
|
# Drop functions until 'pend_stop_mark' . . .
|
|||
|
pend_drop() {
|
|||
|
local -i _top_
|
|||
|
local _pd_ctrl_=${#_pending_[@]}
|
|||
|
while [ ${_pd_ctrl_} -gt 0 ]
|
|||
|
do
|
|||
|
_top_=$_pd_ctrl_-1
|
|||
|
if [ "${_pending_[$_top_]}" == 'pend_stop_mark' ]
|
|||
|
then
|
|||
|
unset _pending_[$_top_]
|
|||
|
break
|
|||
|
else
|
|||
|
unset _pending_[$_top_]
|
|||
|
_pd_ctrl_=$_top_
|
|||
|
fi
|
|||
|
done
|
|||
|
if [ ${#_pending_[@]} -eq 0 ]
|
|||
|
then
|
|||
|
pend_func pend_stop_mark
|
|||
|
fi
|
|||
|
}
|
|||
|
|
|||
|
#### Array editors ####
|
|||
|
|
|||
|
# This function described in edit_exact.bash.
|
|||
|
# (See "Additional documentation," above.)
|
|||
|
# edit_exact <excludes_array_name> <target_array_name>
|
|||
|
edit_exact() {
|
|||
|
[ $# -eq 2 ] ||
|
|||
|
[ $# -eq 3 ] || return 1
|
|||
|
local -a _ee_Excludes
|
|||
|
local -a _ee_Target
|
|||
|
local _ee_x
|
|||
|
local _ee_t
|
|||
|
local IFS=${NO_WSP}
|
|||
|
set -f
|
|||
|
eval _ee_Excludes=\( \$\{$1\[@\]\} \)
|
|||
|
eval _ee_Target=\( \$\{$2\[@\]\} \)
|
|||
|
local _ee_len=${#_ee_Target[@]} # Original length.
|
|||
|
local _ee_cnt=${#_ee_Excludes[@]} # Exclude list length.
|
|||
|
[ ${_ee_len} -ne 0 ] || return 0 # Can't edit zero length.
|
|||
|
[ ${_ee_cnt} -ne 0 ] || return 0 # Can't edit zero length.
|
|||
|
for (( x = 0; x < ${_ee_cnt} ; x++ ))
|
|||
|
do
|
|||
|
_ee_x=${_ee_Excludes[$x]}
|
|||
|
for (( n = 0 ; n < ${_ee_len} ; n++ ))
|
|||
|
do
|
|||
|
_ee_t=${_ee_Target[$n]}
|
|||
|
if [ x"${_ee_t}" == x"${_ee_x}" ]
|
|||
|
then
|
|||
|
unset _ee_Target[$n] # Discard match.
|
|||
|
[ $# -eq 2 ] && break # If 2 arguments, then done.
|
|||
|
fi
|
|||
|
done
|
|||
|
done
|
|||
|
eval $2=\( \$\{_ee_Target\[@\]\} \)
|
|||
|
set +f
|
|||
|
return 0
|
|||
|
}
|
|||
|
|
|||
|
# This function described in edit_by_glob.bash.
|
|||
|
# edit_by_glob <excludes_array_name> <target_array_name>
|
|||
|
edit_by_glob() {
|
|||
|
[ $# -eq 2 ] ||
|
|||
|
[ $# -eq 3 ] || return 1
|
|||
|
local -a _ebg_Excludes
|
|||
|
local -a _ebg_Target
|
|||
|
local _ebg_x
|
|||
|
local _ebg_t
|
|||
|
local IFS=${NO_WSP}
|
|||
|
set -f
|
|||
|
eval _ebg_Excludes=\( \$\{$1\[@\]\} \)
|
|||
|
eval _ebg_Target=\( \$\{$2\[@\]\} \)
|
|||
|
local _ebg_len=${#_ebg_Target[@]}
|
|||
|
local _ebg_cnt=${#_ebg_Excludes[@]}
|
|||
|
[ ${_ebg_len} -ne 0 ] || return 0
|
|||
|
[ ${_ebg_cnt} -ne 0 ] || return 0
|
|||
|
for (( x = 0; x < ${_ebg_cnt} ; x++ ))
|
|||
|
do
|
|||
|
_ebg_x=${_ebg_Excludes[$x]}
|
|||
|
for (( n = 0 ; n < ${_ebg_len} ; n++ ))
|
|||
|
do
|
|||
|
[ $# -eq 3 ] && _ebg_x=${_ebg_x}'*' # Do prefix edit
|
|||
|
if [ ${_ebg_Target[$n]:=} ] #+ if defined & set.
|
|||
|
then
|
|||
|
_ebg_t=${_ebg_Target[$n]/#${_ebg_x}/}
|
|||
|
[ ${#_ebg_t} -eq 0 ] && unset _ebg_Target[$n]
|
|||
|
fi
|
|||
|
done
|
|||
|
done
|
|||
|
eval $2=\( \$\{_ebg_Target\[@\]\} \)
|
|||
|
set +f
|
|||
|
return 0
|
|||
|
}
|
|||
|
|
|||
|
# This function described in unique_lines.bash.
|
|||
|
# unique_lines <in_name> <out_name>
|
|||
|
unique_lines() {
|
|||
|
[ $# -eq 2 ] || return 1
|
|||
|
local -a _ul_in
|
|||
|
local -a _ul_out
|
|||
|
local -i _ul_cnt
|
|||
|
local -i _ul_pos
|
|||
|
local _ul_tmp
|
|||
|
local IFS=${NO_WSP}
|
|||
|
set -f
|
|||
|
eval _ul_in=\( \$\{$1\[@\]\} \)
|
|||
|
_ul_cnt=${#_ul_in[@]}
|
|||
|
for (( _ul_pos = 0 ; _ul_pos < ${_ul_cnt} ; _ul_pos++ ))
|
|||
|
do
|
|||
|
if [ ${_ul_in[${_ul_pos}]:=} ] # If defined & not empty
|
|||
|
then
|
|||
|
_ul_tmp=${_ul_in[${_ul_pos}]}
|
|||
|
_ul_out[${#_ul_out[@]}]=${_ul_tmp}
|
|||
|
for (( zap = _ul_pos ; zap < ${_ul_cnt} ; zap++ ))
|
|||
|
do
|
|||
|
[ ${_ul_in[${zap}]:=} ] &&
|
|||
|
[ 'x'${_ul_in[${zap}]} == 'x'${_ul_tmp} ] &&
|
|||
|
unset _ul_in[${zap}]
|
|||
|
done
|
|||
|
fi
|
|||
|
done
|
|||
|
eval $2=\( \$\{_ul_out\[@\]\} \)
|
|||
|
set +f
|
|||
|
return 0
|
|||
|
}
|
|||
|
|
|||
|
# This function described in char_convert.bash.
|
|||
|
# to_lower <string>
|
|||
|
to_lower() {
|
|||
|
[ $# -eq 1 ] || return 1
|
|||
|
local _tl_out
|
|||
|
_tl_out=${1//A/a}
|
|||
|
_tl_out=${_tl_out//B/b}
|
|||
|
_tl_out=${_tl_out//C/c}
|
|||
|
_tl_out=${_tl_out//D/d}
|
|||
|
_tl_out=${_tl_out//E/e}
|
|||
|
_tl_out=${_tl_out//F/f}
|
|||
|
_tl_out=${_tl_out//G/g}
|
|||
|
_tl_out=${_tl_out//H/h}
|
|||
|
_tl_out=${_tl_out//I/i}
|
|||
|
_tl_out=${_tl_out//J/j}
|
|||
|
_tl_out=${_tl_out//K/k}
|
|||
|
_tl_out=${_tl_out//L/l}
|
|||
|
_tl_out=${_tl_out//M/m}
|
|||
|
_tl_out=${_tl_out//N/n}
|
|||
|
_tl_out=${_tl_out//O/o}
|
|||
|
_tl_out=${_tl_out//P/p}
|
|||
|
_tl_out=${_tl_out//Q/q}
|
|||
|
_tl_out=${_tl_out//R/r}
|
|||
|
_tl_out=${_tl_out//S/s}
|
|||
|
_tl_out=${_tl_out//T/t}
|
|||
|
_tl_out=${_tl_out//U/u}
|
|||
|
_tl_out=${_tl_out//V/v}
|
|||
|
_tl_out=${_tl_out//W/w}
|
|||
|
_tl_out=${_tl_out//X/x}
|
|||
|
_tl_out=${_tl_out//Y/y}
|
|||
|
_tl_out=${_tl_out//Z/z}
|
|||
|
echo ${_tl_out}
|
|||
|
return 0
|
|||
|
}
|
|||
|
|
|||
|
#### Application helper functions ####
|
|||
|
|
|||
|
# Not everybody uses dots as separators (APNIC, for example).
|
|||
|
# This function described in to_dot.bash
|
|||
|
# to_dot <string>
|
|||
|
to_dot() {
|
|||
|
[ $# -eq 1 ] || return 1
|
|||
|
echo ${1//[#|@|%]/.}
|
|||
|
return 0
|
|||
|
}
|
|||
|
|
|||
|
# This function described in is_number.bash.
|
|||
|
# is_number <input>
|
|||
|
is_number() {
|
|||
|
[ "$#" -eq 1 ] || return 1 # is blank?
|
|||
|
[ x"$1" == 'x0' ] && return 0 # is zero?
|
|||
|
local -i tst
|
|||
|
let tst=$1 2>/dev/null # else is numeric!
|
|||
|
return $?
|
|||
|
}
|
|||
|
|
|||
|
# This function described in is_address.bash.
|
|||
|
# is_address <input>
|
|||
|
is_address() {
|
|||
|
[ $# -eq 1 ] || return 1 # Blank ==> false
|
|||
|
local -a _ia_input
|
|||
|
local IFS=${ADR_IFS}
|
|||
|
_ia_input=( $1 )
|
|||
|
if [ ${#_ia_input[@]} -eq 4 ] &&
|
|||
|
is_number ${_ia_input[0]} &&
|
|||
|
is_number ${_ia_input[1]} &&
|
|||
|
is_number ${_ia_input[2]} &&
|
|||
|
is_number ${_ia_input[3]} &&
|
|||
|
[ ${_ia_input[0]} -lt 256 ] &&
|
|||
|
[ ${_ia_input[1]} -lt 256 ] &&
|
|||
|
[ ${_ia_input[2]} -lt 256 ] &&
|
|||
|
[ ${_ia_input[3]} -lt 256 ]
|
|||
|
then
|
|||
|
return 0
|
|||
|
else
|
|||
|
return 1
|
|||
|
fi
|
|||
|
}
|
|||
|
|
|||
|
# This function described in split_ip.bash.
|
|||
|
# split_ip <IP_address>
|
|||
|
#+ <array_name_norm> [<array_name_rev>]
|
|||
|
split_ip() {
|
|||
|
[ $# -eq 3 ] || # Either three
|
|||
|
[ $# -eq 2 ] || return 1 #+ or two arguments
|
|||
|
local -a _si_input
|
|||
|
local IFS=${ADR_IFS}
|
|||
|
_si_input=( $1 )
|
|||
|
IFS=${WSP_IFS}
|
|||
|
eval $2=\(\ \$\{_si_input\[@\]\}\ \)
|
|||
|
if [ $# -eq 3 ]
|
|||
|
then
|
|||
|
# Build query order array.
|
|||
|
local -a _dns_ip
|
|||
|
_dns_ip[0]=${_si_input[3]}
|
|||
|
_dns_ip[1]=${_si_input[2]}
|
|||
|
_dns_ip[2]=${_si_input[1]}
|
|||
|
_dns_ip[3]=${_si_input[0]}
|
|||
|
eval $3=\(\ \$\{_dns_ip\[@\]\}\ \)
|
|||
|
fi
|
|||
|
return 0
|
|||
|
}
|
|||
|
|
|||
|
# This function described in dot_array.bash.
|
|||
|
# dot_array <array_name>
|
|||
|
dot_array() {
|
|||
|
[ $# -eq 1 ] || return 1 # Single argument required.
|
|||
|
local -a _da_input
|
|||
|
eval _da_input=\(\ \$\{$1\[@\]\}\ \)
|
|||
|
local IFS=${DOT_IFS}
|
|||
|
local _da_output=${_da_input[@]}
|
|||
|
IFS=${WSP_IFS}
|
|||
|
echo ${_da_output}
|
|||
|
return 0
|
|||
|
}
|
|||
|
|
|||
|
# This function described in file_to_array.bash
|
|||
|
# file_to_array <file_name> <line_array_name>
|
|||
|
file_to_array() {
|
|||
|
[ $# -eq 2 ] || return 1 # Two arguments required.
|
|||
|
local IFS=${NO_WSP}
|
|||
|
local -a _fta_tmp_
|
|||
|
_fta_tmp_=( $(cat $1) )
|
|||
|
eval $2=\( \$\{_fta_tmp_\[@\]\} \)
|
|||
|
return 0
|
|||
|
}
|
|||
|
|
|||
|
# Columnized print of an array of multi-field strings.
|
|||
|
# col_print <array_name> <min_space> <
|
|||
|
#+ tab_stop [tab_stops]>
|
|||
|
col_print() {
|
|||
|
[ $# -gt 2 ] || return 0
|
|||
|
local -a _cp_inp
|
|||
|
local -a _cp_spc
|
|||
|
local -a _cp_line
|
|||
|
local _cp_min
|
|||
|
local _cp_mcnt
|
|||
|
local _cp_pos
|
|||
|
local _cp_cnt
|
|||
|
local _cp_tab
|
|||
|
local -i _cp
|
|||
|
local -i _cpf
|
|||
|
local _cp_fld
|
|||
|
# WARNING: FOLLOWING LINE NOT BLANK -- IT IS QUOTED SPACES.
|
|||
|
local _cp_max=' '
|
|||
|
set -f
|
|||
|
local IFS=${NO_WSP}
|
|||
|
eval _cp_inp=\(\ \$\{$1\[@\]\}\ \)
|
|||
|
[ ${#_cp_inp[@]} -gt 0 ] || return 0 # Empty is easy.
|
|||
|
_cp_mcnt=$2
|
|||
|
_cp_min=${_cp_max:1:${_cp_mcnt}}
|
|||
|
shift
|
|||
|
shift
|
|||
|
_cp_cnt=$#
|
|||
|
for (( _cp = 0 ; _cp < _cp_cnt ; _cp++ ))
|
|||
|
do
|
|||
|
_cp_spc[${#_cp_spc[@]}]="${_cp_max:2:$1}" #"
|
|||
|
shift
|
|||
|
done
|
|||
|
_cp_cnt=${#_cp_inp[@]}
|
|||
|
for (( _cp = 0 ; _cp < _cp_cnt ; _cp++ ))
|
|||
|
do
|
|||
|
_cp_pos=1
|
|||
|
IFS=${NO_WSP}$'\x20'
|
|||
|
_cp_line=( ${_cp_inp[${_cp}]} )
|
|||
|
IFS=${NO_WSP}
|
|||
|
for (( _cpf = 0 ; _cpf < ${#_cp_line[@]} ; _cpf++ ))
|
|||
|
do
|
|||
|
_cp_tab=${_cp_spc[${_cpf}]:${_cp_pos}}
|
|||
|
if [ ${#_cp_tab} -lt ${_cp_mcnt} ]
|
|||
|
then
|
|||
|
_cp_tab="${_cp_min}"
|
|||
|
fi
|
|||
|
echo -n "${_cp_tab}"
|
|||
|
(( _cp_pos = ${_cp_pos} + ${#_cp_tab} ))
|
|||
|
_cp_fld="${_cp_line[${_cpf}]}"
|
|||
|
echo -n ${_cp_fld}
|
|||
|
(( _cp_pos = ${_cp_pos} + ${#_cp_fld} ))
|
|||
|
done
|
|||
|
echo
|
|||
|
done
|
|||
|
set +f
|
|||
|
return 0
|
|||
|
}
|
|||
|
|
|||
|
# # # # 'Hunt the Spammer' data flow # # # #
|
|||
|
|
|||
|
# Application return code
|
|||
|
declare -i _hs_RC
|
|||
|
|
|||
|
# Original input, from which IP addresses are removed
|
|||
|
# After which, domain names to check
|
|||
|
declare -a uc_name
|
|||
|
|
|||
|
# Original input IP addresses are moved here
|
|||
|
# After which, IP addresses to check
|
|||
|
declare -a uc_address
|
|||
|
|
|||
|
# Names against which address expansion run
|
|||
|
# Ready for name detail lookup
|
|||
|
declare -a chk_name
|
|||
|
|
|||
|
# Addresses against which name expansion run
|
|||
|
# Ready for address detail lookup
|
|||
|
declare -a chk_address
|
|||
|
|
|||
|
# Recursion is depth-first-by-name.
|
|||
|
# The expand_input_address maintains this list
|
|||
|
#+ to prohibit looking up addresses twice during
|
|||
|
#+ domain name recursion.
|
|||
|
declare -a been_there_addr
|
|||
|
been_there_addr=( '127.0.0.1' ) # Whitelist localhost
|
|||
|
|
|||
|
# Names which we have checked (or given up on)
|
|||
|
declare -a known_name
|
|||
|
|
|||
|
# Addresses which we have checked (or given up on)
|
|||
|
declare -a known_address
|
|||
|
|
|||
|
# List of zero or more Blacklist servers to check.
|
|||
|
# Each 'known_address' will be checked against each server,
|
|||
|
#+ with negative replies and failures suppressed.
|
|||
|
declare -a list_server
|
|||
|
|
|||
|
# Indirection limit - set to zero == no limit
|
|||
|
indirect=${SPAMMER_LIMIT:=2}
|
|||
|
|
|||
|
# # # # 'Hunt the Spammer' information output data # # # #
|
|||
|
|
|||
|
# Any domain name may have multiple IP addresses.
|
|||
|
# Any IP address may have multiple domain names.
|
|||
|
# Therefore, track unique address-name pairs.
|
|||
|
declare -a known_pair
|
|||
|
declare -a reverse_pair
|
|||
|
|
|||
|
# In addition to the data flow variables; known_address
|
|||
|
#+ known_name and list_server, the following are output to the
|
|||
|
#+ external graphics interface file.
|
|||
|
|
|||
|
# Authority chain, parent -> SOA fields.
|
|||
|
declare -a auth_chain
|
|||
|
|
|||
|
# Reference chain, parent name -> child name
|
|||
|
declare -a ref_chain
|
|||
|
|
|||
|
# DNS chain - domain name -> address
|
|||
|
declare -a name_address
|
|||
|
|
|||
|
# Name and service pairs - domain name -> service
|
|||
|
declare -a name_srvc
|
|||
|
|
|||
|
# Name and resource pairs - domain name -> Resource Record
|
|||
|
declare -a name_resource
|
|||
|
|
|||
|
# Parent and Child pairs - parent name -> child name
|
|||
|
# This MAY NOT be the same as the ref_chain followed!
|
|||
|
declare -a parent_child
|
|||
|
|
|||
|
# Address and Blacklist hit pairs - address->server
|
|||
|
declare -a address_hits
|
|||
|
|
|||
|
# Dump interface file data
|
|||
|
declare -f _dot_dump
|
|||
|
_dot_dump=pend_dummy # Initially a no-op
|
|||
|
|
|||
|
# Data dump is enabled by setting the environment variable SPAMMER_DATA
|
|||
|
#+ to the name of a writable file.
|
|||
|
declare _dot_file
|
|||
|
|
|||
|
# Helper function for the dump-to-dot-file function
|
|||
|
# dump_to_dot <array_name> <prefix>
|
|||
|
dump_to_dot() {
|
|||
|
local -a _dda_tmp
|
|||
|
local -i _dda_cnt
|
|||
|
local _dda_form=' '${2}'%04u %s\n'
|
|||
|
local IFS=${NO_WSP}
|
|||
|
eval _dda_tmp=\(\ \$\{$1\[@\]\}\ \)
|
|||
|
_dda_cnt=${#_dda_tmp[@]}
|
|||
|
if [ ${_dda_cnt} -gt 0 ]
|
|||
|
then
|
|||
|
for (( _dda = 0 ; _dda < _dda_cnt ; _dda++ ))
|
|||
|
do
|
|||
|
printf "${_dda_form}" \
|
|||
|
"${_dda}" "${_dda_tmp[${_dda}]}" >>${_dot_file}
|
|||
|
done
|
|||
|
fi
|
|||
|
}
|
|||
|
|
|||
|
# Which will also set _dot_dump to this function . . .
|
|||
|
dump_dot() {
|
|||
|
local -i _dd_cnt
|
|||
|
echo '# Data vintage: '$(date -R) >${_dot_file}
|
|||
|
echo '# ABS Guide: is_spammer.bash; v2, 2004-msz' >>${_dot_file}
|
|||
|
echo >>${_dot_file}
|
|||
|
echo 'digraph G {' >>${_dot_file}
|
|||
|
|
|||
|
if [ ${#known_name[@]} -gt 0 ]
|
|||
|
then
|
|||
|
echo >>${_dot_file}
|
|||
|
echo '# Known domain name nodes' >>${_dot_file}
|
|||
|
_dd_cnt=${#known_name[@]}
|
|||
|
for (( _dd = 0 ; _dd < _dd_cnt ; _dd++ ))
|
|||
|
do
|
|||
|
printf ' N%04u [label="%s"] ;\n' \
|
|||
|
"${_dd}" "${known_name[${_dd}]}" >>${_dot_file}
|
|||
|
done
|
|||
|
fi
|
|||
|
|
|||
|
if [ ${#known_address[@]} -gt 0 ]
|
|||
|
then
|
|||
|
echo >>${_dot_file}
|
|||
|
echo '# Known address nodes' >>${_dot_file}
|
|||
|
_dd_cnt=${#known_address[@]}
|
|||
|
for (( _dd = 0 ; _dd < _dd_cnt ; _dd++ ))
|
|||
|
do
|
|||
|
printf ' A%04u [label="%s"] ;\n' \
|
|||
|
"${_dd}" "${known_address[${_dd}]}" >>${_dot_file}
|
|||
|
done
|
|||
|
fi
|
|||
|
|
|||
|
echo >>${_dot_file}
|
|||
|
echo '/*' >>${_dot_file}
|
|||
|
echo ' * Known relationships :: User conversion to' >>${_dot_file}
|
|||
|
echo ' * graphic form by hand or program required.' >>${_dot_file}
|
|||
|
echo ' *' >>${_dot_file}
|
|||
|
|
|||
|
if [ ${#auth_chain[@]} -gt 0 ]
|
|||
|
then
|
|||
|
echo >>${_dot_file}
|
|||
|
echo '# Authority ref. edges followed & field source.' >>${_dot_file}
|
|||
|
dump_to_dot auth_chain AC
|
|||
|
fi
|
|||
|
|
|||
|
if [ ${#ref_chain[@]} -gt 0 ]
|
|||
|
then
|
|||
|
echo >>${_dot_file}
|
|||
|
echo '# Name ref. edges followed and field source.' >>${_dot_file}
|
|||
|
dump_to_dot ref_chain RC
|
|||
|
fi
|
|||
|
|
|||
|
if [ ${#name_address[@]} -gt 0 ]
|
|||
|
then
|
|||
|
echo >>${_dot_file}
|
|||
|
echo '# Known name->address edges' >>${_dot_file}
|
|||
|
dump_to_dot name_address NA
|
|||
|
fi
|
|||
|
|
|||
|
if [ ${#name_srvc[@]} -gt 0 ]
|
|||
|
then
|
|||
|
echo >>${_dot_file}
|
|||
|
echo '# Known name->service edges' >>${_dot_file}
|
|||
|
dump_to_dot name_srvc NS
|
|||
|
fi
|
|||
|
|
|||
|
if [ ${#name_resource[@]} -gt 0 ]
|
|||
|
then
|
|||
|
echo >>${_dot_file}
|
|||
|
echo '# Known name->resource edges' >>${_dot_file}
|
|||
|
dump_to_dot name_resource NR
|
|||
|
fi
|
|||
|
|
|||
|
if [ ${#parent_child[@]} -gt 0 ]
|
|||
|
then
|
|||
|
echo >>${_dot_file}
|
|||
|
echo '# Known parent->child edges' >>${_dot_file}
|
|||
|
dump_to_dot parent_child PC
|
|||
|
fi
|
|||
|
|
|||
|
if [ ${#list_server[@]} -gt 0 ]
|
|||
|
then
|
|||
|
echo >>${_dot_file}
|
|||
|
echo '# Known Blacklist nodes' >>${_dot_file}
|
|||
|
_dd_cnt=${#list_server[@]}
|
|||
|
for (( _dd = 0 ; _dd < _dd_cnt ; _dd++ ))
|
|||
|
do
|
|||
|
printf ' LS%04u [label="%s"] ;\n' \
|
|||
|
"${_dd}" "${list_server[${_dd}]}" >>${_dot_file}
|
|||
|
done
|
|||
|
fi
|
|||
|
|
|||
|
unique_lines address_hits address_hits
|
|||
|
if [ ${#address_hits[@]} -gt 0 ]
|
|||
|
then
|
|||
|
echo >>${_dot_file}
|
|||
|
echo '# Known address->Blacklist_hit edges' >>${_dot_file}
|
|||
|
echo '# CAUTION: dig warnings can trigger false hits.' >>${_dot_file}
|
|||
|
dump_to_dot address_hits AH
|
|||
|
fi
|
|||
|
echo >>${_dot_file}
|
|||
|
echo ' *' >>${_dot_file}
|
|||
|
echo ' * That is a lot of relationships. Happy graphing.' >>${_dot_file}
|
|||
|
echo ' */' >>${_dot_file}
|
|||
|
echo '}' >>${_dot_file}
|
|||
|
return 0
|
|||
|
}
|
|||
|
|
|||
|
# # # # 'Hunt the Spammer' execution flow # # # #
|
|||
|
|
|||
|
# Execution trace is enabled by setting the
|
|||
|
#+ environment variable SPAMMER_TRACE to the name of a writable file.
|
|||
|
declare -a _trace_log
|
|||
|
declare _log_file
|
|||
|
|
|||
|
# Function to fill the trace log
|
|||
|
trace_logger() {
|
|||
|
_trace_log[${#_trace_log[@]}]=${_pend_current_}
|
|||
|
}
|
|||
|
|
|||
|
# Dump trace log to file function variable.
|
|||
|
declare -f _log_dump
|
|||
|
_log_dump=pend_dummy # Initially a no-op.
|
|||
|
|
|||
|
# Dump the trace log to a file.
|
|||
|
dump_log() {
|
|||
|
local -i _dl_cnt
|
|||
|
_dl_cnt=${#_trace_log[@]}
|
|||
|
for (( _dl = 0 ; _dl < _dl_cnt ; _dl++ ))
|
|||
|
do
|
|||
|
echo ${_trace_log[${_dl}]} >> ${_log_file}
|
|||
|
done
|
|||
|
_dl_cnt=${#_pending_[@]}
|
|||
|
if [ ${_dl_cnt} -gt 0 ]
|
|||
|
then
|
|||
|
_dl_cnt=${_dl_cnt}-1
|
|||
|
echo '# # # Operations stack not empty # # #' >> ${_log_file}
|
|||
|
for (( _dl = ${_dl_cnt} ; _dl >= 0 ; _dl-- ))
|
|||
|
do
|
|||
|
echo ${_pending_[${_dl}]} >> ${_log_file}
|
|||
|
done
|
|||
|
fi
|
|||
|
}
|
|||
|
|
|||
|
# # # Utility program 'dig' wrappers # # #
|
|||
|
#
|
|||
|
# These wrappers are derived from the
|
|||
|
#+ examples shown in dig_wrappers.bash.
|
|||
|
#
|
|||
|
# The major difference is these return
|
|||
|
#+ their results as a list in an array.
|
|||
|
#
|
|||
|
# See dig_wrappers.bash for details and
|
|||
|
#+ use that script to develop any changes.
|
|||
|
#
|
|||
|
# # #
|
|||
|
|
|||
|
# Short form answer: 'dig' parses answer.
|
|||
|
|
|||
|
# Forward lookup :: Name -> Address
|
|||
|
# short_fwd <domain_name> <array_name>
|
|||
|
short_fwd() {
|
|||
|
local -a _sf_reply
|
|||
|
local -i _sf_rc
|
|||
|
local -i _sf_cnt
|
|||
|
IFS=${NO_WSP}
|
|||
|
echo -n '.'
|
|||
|
# echo 'sfwd: '${1}
|
|||
|
_sf_reply=( $(dig +short ${1} -c in -t a 2>/dev/null) )
|
|||
|
_sf_rc=$?
|
|||
|
if [ ${_sf_rc} -ne 0 ]
|
|||
|
then
|
|||
|
_trace_log[${#_trace_log[@]}]='## Lookup error '${_sf_rc}' on '${1}' ##'
|
|||
|
# [ ${_sf_rc} -ne 9 ] && pend_drop
|
|||
|
return ${_sf_rc}
|
|||
|
else
|
|||
|
# Some versions of 'dig' return warnings on stdout.
|
|||
|
_sf_cnt=${#_sf_reply[@]}
|
|||
|
for (( _sf = 0 ; _sf < ${_sf_cnt} ; _sf++ ))
|
|||
|
do
|
|||
|
[ 'x'${_sf_reply[${_sf}]:0:2} == 'x;;' ] &&
|
|||
|
unset _sf_reply[${_sf}]
|
|||
|
done
|
|||
|
eval $2=\( \$\{_sf_reply\[@\]\} \)
|
|||
|
fi
|
|||
|
return 0
|
|||
|
}
|
|||
|
|
|||
|
# Reverse lookup :: Address -> Name
|
|||
|
# short_rev <ip_address> <array_name>
|
|||
|
short_rev() {
|
|||
|
local -a _sr_reply
|
|||
|
local -i _sr_rc
|
|||
|
local -i _sr_cnt
|
|||
|
IFS=${NO_WSP}
|
|||
|
echo -n '.'
|
|||
|
# echo 'srev: '${1}
|
|||
|
_sr_reply=( $(dig +short -x ${1} 2>/dev/null) )
|
|||
|
_sr_rc=$?
|
|||
|
if [ ${_sr_rc} -ne 0 ]
|
|||
|
then
|
|||
|
_trace_log[${#_trace_log[@]}]='## Lookup error '${_sr_rc}' on '${1}' ##'
|
|||
|
# [ ${_sr_rc} -ne 9 ] && pend_drop
|
|||
|
return ${_sr_rc}
|
|||
|
else
|
|||
|
# Some versions of 'dig' return warnings on stdout.
|
|||
|
_sr_cnt=${#_sr_reply[@]}
|
|||
|
for (( _sr = 0 ; _sr < ${_sr_cnt} ; _sr++ ))
|
|||
|
do
|
|||
|
[ 'x'${_sr_reply[${_sr}]:0:2} == 'x;;' ] &&
|
|||
|
unset _sr_reply[${_sr}]
|
|||
|
done
|
|||
|
eval $2=\( \$\{_sr_reply\[@\]\} \)
|
|||
|
fi
|
|||
|
return 0
|
|||
|
}
|
|||
|
|
|||
|
# Special format lookup used to query blacklist servers.
|
|||
|
# short_text <ip_address> <array_name>
|
|||
|
short_text() {
|
|||
|
local -a _st_reply
|
|||
|
local -i _st_rc
|
|||
|
local -i _st_cnt
|
|||
|
IFS=${NO_WSP}
|
|||
|
# echo 'stxt: '${1}
|
|||
|
_st_reply=( $(dig +short ${1} -c in -t txt 2>/dev/null) )
|
|||
|
_st_rc=$?
|
|||
|
if [ ${_st_rc} -ne 0 ]
|
|||
|
then
|
|||
|
_trace_log[${#_trace_log[@]}]='##Text lookup error '${_st_rc}' on '${1}'##'
|
|||
|
# [ ${_st_rc} -ne 9 ] && pend_drop
|
|||
|
return ${_st_rc}
|
|||
|
else
|
|||
|
# Some versions of 'dig' return warnings on stdout.
|
|||
|
_st_cnt=${#_st_reply[@]}
|
|||
|
for (( _st = 0 ; _st < ${#_st_cnt} ; _st++ ))
|
|||
|
do
|
|||
|
[ 'x'${_st_reply[${_st}]:0:2} == 'x;;' ] &&
|
|||
|
unset _st_reply[${_st}]
|
|||
|
done
|
|||
|
eval $2=\( \$\{_st_reply\[@\]\} \)
|
|||
|
fi
|
|||
|
return 0
|
|||
|
}
|
|||
|
|
|||
|
# The long forms, a.k.a., the parse it yourself versions
|
|||
|
|
|||
|
# RFC 2782 Service lookups
|
|||
|
# dig +noall +nofail +answer _ldap._tcp.openldap.org -t srv
|
|||
|
# _<service>._<protocol>.<domain_name>
|
|||
|
# _ldap._tcp.openldap.org. 3600 IN SRV 0 0 389 ldap.openldap.org.
|
|||
|
# domain TTL Class SRV Priority Weight Port Target
|
|||
|
|
|||
|
# Forward lookup :: Name -> poor man's zone transfer
|
|||
|
# long_fwd <domain_name> <array_name>
|
|||
|
long_fwd() {
|
|||
|
local -a _lf_reply
|
|||
|
local -i _lf_rc
|
|||
|
local -i _lf_cnt
|
|||
|
IFS=${NO_WSP}
|
|||
|
echo -n ':'
|
|||
|
# echo 'lfwd: '${1}
|
|||
|
_lf_reply=( $(
|
|||
|
dig +noall +nofail +answer +authority +additional \
|
|||
|
${1} -t soa ${1} -t mx ${1} -t any 2>/dev/null) )
|
|||
|
_lf_rc=$?
|
|||
|
if [ ${_lf_rc} -ne 0 ]
|
|||
|
then
|
|||
|
_trace_log[${#_trace_log[@]}]='# Zone lookup err '${_lf_rc}' on '${1}' #'
|
|||
|
# [ ${_lf_rc} -ne 9 ] && pend_drop
|
|||
|
return ${_lf_rc}
|
|||
|
else
|
|||
|
# Some versions of 'dig' return warnings on stdout.
|
|||
|
_lf_cnt=${#_lf_reply[@]}
|
|||
|
for (( _lf = 0 ; _lf < ${_lf_cnt} ; _lf++ ))
|
|||
|
do
|
|||
|
[ 'x'${_lf_reply[${_lf}]:0:2} == 'x;;' ] &&
|
|||
|
unset _lf_reply[${_lf}]
|
|||
|
done
|
|||
|
eval $2=\( \$\{_lf_reply\[@\]\} \)
|
|||
|
fi
|
|||
|
return 0
|
|||
|
}
|
|||
|
# The reverse lookup domain name corresponding to the IPv6 address:
|
|||
|
# 4321:0:1:2:3:4:567:89ab
|
|||
|
# would be (nibble, I.E: Hexdigit) reversed:
|
|||
|
# b.a.9.8.7.6.5.0.4.0.0.0.3.0.0.0.2.0.0.0.1.0.0.0.0.0.0.0.1.2.3.4.IP6.ARPA.
|
|||
|
|
|||
|
# Reverse lookup :: Address -> poor man's delegation chain
|
|||
|
# long_rev <rev_ip_address> <array_name>
|
|||
|
long_rev() {
|
|||
|
local -a _lr_reply
|
|||
|
local -i _lr_rc
|
|||
|
local -i _lr_cnt
|
|||
|
local _lr_dns
|
|||
|
_lr_dns=${1}'.in-addr.arpa.'
|
|||
|
IFS=${NO_WSP}
|
|||
|
echo -n ':'
|
|||
|
# echo 'lrev: '${1}
|
|||
|
_lr_reply=( $(
|
|||
|
dig +noall +nofail +answer +authority +additional \
|
|||
|
${_lr_dns} -t soa ${_lr_dns} -t any 2>/dev/null) )
|
|||
|
_lr_rc=$?
|
|||
|
if [ ${_lr_rc} -ne 0 ]
|
|||
|
then
|
|||
|
_trace_log[${#_trace_log[@]}]='# Deleg lkp error '${_lr_rc}' on '${1}' #'
|
|||
|
# [ ${_lr_rc} -ne 9 ] && pend_drop
|
|||
|
return ${_lr_rc}
|
|||
|
else
|
|||
|
# Some versions of 'dig' return warnings on stdout.
|
|||
|
_lr_cnt=${#_lr_reply[@]}
|
|||
|
for (( _lr = 0 ; _lr < ${_lr_cnt} ; _lr++ ))
|
|||
|
do
|
|||
|
[ 'x'${_lr_reply[${_lr}]:0:2} == 'x;;' ] &&
|
|||
|
unset _lr_reply[${_lr}]
|
|||
|
done
|
|||
|
eval $2=\( \$\{_lr_reply\[@\]\} \)
|
|||
|
fi
|
|||
|
return 0
|
|||
|
}
|
|||
|
|
|||
|
# # # Application specific functions # # #
|
|||
|
|
|||
|
# Mung a possible name; suppresses root and TLDs.
|
|||
|
# name_fixup <string>
|
|||
|
name_fixup(){
|
|||
|
local -a _nf_tmp
|
|||
|
local -i _nf_end
|
|||
|
local _nf_str
|
|||
|
local IFS
|
|||
|
_nf_str=$(to_lower ${1})
|
|||
|
_nf_str=$(to_dot ${_nf_str})
|
|||
|
_nf_end=${#_nf_str}-1
|
|||
|
[ ${_nf_str:${_nf_end}} != '.' ] &&
|
|||
|
_nf_str=${_nf_str}'.'
|
|||
|
IFS=${ADR_IFS}
|
|||
|
_nf_tmp=( ${_nf_str} )
|
|||
|
IFS=${WSP_IFS}
|
|||
|
_nf_end=${#_nf_tmp[@]}
|
|||
|
case ${_nf_end} in
|
|||
|
0) # No dots, only dots.
|
|||
|
echo
|
|||
|
return 1
|
|||
|
;;
|
|||
|
1) # Only a TLD.
|
|||
|
echo
|
|||
|
return 1
|
|||
|
;;
|
|||
|
2) # Maybe okay.
|
|||
|
echo ${_nf_str}
|
|||
|
return 0
|
|||
|
# Needs a lookup table?
|
|||
|
if [ ${#_nf_tmp[1]} -eq 2 ]
|
|||
|
then # Country coded TLD.
|
|||
|
echo
|
|||
|
return 1
|
|||
|
else
|
|||
|
echo ${_nf_str}
|
|||
|
return 0
|
|||
|
fi
|
|||
|
;;
|
|||
|
esac
|
|||
|
echo ${_nf_str}
|
|||
|
return 0
|
|||
|
}
|
|||
|
|
|||
|
# Grope and mung original input(s).
|
|||
|
split_input() {
|
|||
|
[ ${#uc_name[@]} -gt 0 ] || return 0
|
|||
|
local -i _si_cnt
|
|||
|
local -i _si_len
|
|||
|
local _si_str
|
|||
|
unique_lines uc_name uc_name
|
|||
|
_si_cnt=${#uc_name[@]}
|
|||
|
for (( _si = 0 ; _si < _si_cnt ; _si++ ))
|
|||
|
do
|
|||
|
_si_str=${uc_name[$_si]}
|
|||
|
if is_address ${_si_str}
|
|||
|
then
|
|||
|
uc_address[${#uc_address[@]}]=${_si_str}
|
|||
|
unset uc_name[$_si]
|
|||
|
else
|
|||
|
if ! uc_name[$_si]=$(name_fixup ${_si_str})
|
|||
|
then
|
|||
|
unset ucname[$_si]
|
|||
|
fi
|
|||
|
fi
|
|||
|
done
|
|||
|
uc_name=( ${uc_name[@]} )
|
|||
|
_si_cnt=${#uc_name[@]}
|
|||
|
_trace_log[${#_trace_log[@]}]='#Input '${_si_cnt}' unchkd name input(s).#'
|
|||
|
_si_cnt=${#uc_address[@]}
|
|||
|
_trace_log[${#_trace_log[@]}]='#Input '${_si_cnt}' unchkd addr input(s).#'
|
|||
|
return 0
|
|||
|
}
|
|||
|
|
|||
|
# # # Discovery functions -- recursively interlocked by external data # # #
|
|||
|
# # # The leading 'if list is empty; return 0' in each is required. # # #
|
|||
|
|
|||
|
# Recursion limiter
|
|||
|
# limit_chk() <next_level>
|
|||
|
limit_chk() {
|
|||
|
local -i _lc_lmt
|
|||
|
# Check indirection limit.
|
|||
|
if [ ${indirect} -eq 0 ] || [ $# -eq 0 ]
|
|||
|
then
|
|||
|
# The 'do-forever' choice
|
|||
|
echo 1 # Any value will do.
|
|||
|
return 0 # OK to continue.
|
|||
|
else
|
|||
|
# Limiting is in effect.
|
|||
|
if [ ${indirect} -lt ${1} ]
|
|||
|
then
|
|||
|
echo ${1} # Whatever.
|
|||
|
return 1 # Stop here.
|
|||
|
else
|
|||
|
_lc_lmt=${1}+1 # Bump the given limit.
|
|||
|
echo ${_lc_lmt} # Echo it.
|
|||
|
return 0 # OK to continue.
|
|||
|
fi
|
|||
|
fi
|
|||
|
}
|
|||
|
|
|||
|
# For each name in uc_name:
|
|||
|
# Move name to chk_name.
|
|||
|
# Add addresses to uc_address.
|
|||
|
# Pend expand_input_address.
|
|||
|
# Repeat until nothing new found.
|
|||
|
# expand_input_name <indirection_limit>
|
|||
|
expand_input_name() {
|
|||
|
[ ${#uc_name[@]} -gt 0 ] || return 0
|
|||
|
local -a _ein_addr
|
|||
|
local -a _ein_new
|
|||
|
local -i _ucn_cnt
|
|||
|
local -i _ein_cnt
|
|||
|
local _ein_tst
|
|||
|
_ucn_cnt=${#uc_name[@]}
|
|||
|
|
|||
|
if ! _ein_cnt=$(limit_chk ${1})
|
|||
|
then
|
|||
|
return 0
|
|||
|
fi
|
|||
|
|
|||
|
for (( _ein = 0 ; _ein < _ucn_cnt ; _ein++ ))
|
|||
|
do
|
|||
|
if short_fwd ${uc_name[${_ein}]} _ein_new
|
|||
|
then
|
|||
|
for (( _ein_cnt = 0 ; _ein_cnt < ${#_ein_new[@]}; _ein_cnt++ ))
|
|||
|
do
|
|||
|
_ein_tst=${_ein_new[${_ein_cnt}]}
|
|||
|
if is_address ${_ein_tst}
|
|||
|
then
|
|||
|
_ein_addr[${#_ein_addr[@]}]=${_ein_tst}
|
|||
|
fi
|
|||
|
done
|
|||
|
fi
|
|||
|
done
|
|||
|
unique_lines _ein_addr _ein_addr # Scrub duplicates.
|
|||
|
edit_exact chk_address _ein_addr # Scrub pending detail.
|
|||
|
edit_exact known_address _ein_addr # Scrub already detailed.
|
|||
|
if [ ${#_ein_addr[@]} -gt 0 ] # Anything new?
|
|||
|
then
|
|||
|
uc_address=( ${uc_address[@]} ${_ein_addr[@]} )
|
|||
|
pend_func expand_input_address ${1}
|
|||
|
_trace_log[${#_trace_log[@]}]='#Add '${#_ein_addr[@]}' unchkd addr inp.#'
|
|||
|
fi
|
|||
|
edit_exact chk_name uc_name # Scrub pending detail.
|
|||
|
edit_exact known_name uc_name # Scrub already detailed.
|
|||
|
if [ ${#uc_name[@]} -gt 0 ]
|
|||
|
then
|
|||
|
chk_name=( ${chk_name[@]} ${uc_name[@]} )
|
|||
|
pend_func detail_each_name ${1}
|
|||
|
fi
|
|||
|
unset uc_name[@]
|
|||
|
return 0
|
|||
|
}
|
|||
|
|
|||
|
# For each address in uc_address:
|
|||
|
# Move address to chk_address.
|
|||
|
# Add names to uc_name.
|
|||
|
# Pend expand_input_name.
|
|||
|
# Repeat until nothing new found.
|
|||
|
# expand_input_address <indirection_limit>
|
|||
|
expand_input_address() {
|
|||
|
[ ${#uc_address[@]} -gt 0 ] || return 0
|
|||
|
local -a _eia_addr
|
|||
|
local -a _eia_name
|
|||
|
local -a _eia_new
|
|||
|
local -i _uca_cnt
|
|||
|
local -i _eia_cnt
|
|||
|
local _eia_tst
|
|||
|
unique_lines uc_address _eia_addr
|
|||
|
unset uc_address[@]
|
|||
|
edit_exact been_there_addr _eia_addr
|
|||
|
_uca_cnt=${#_eia_addr[@]}
|
|||
|
[ ${_uca_cnt} -gt 0 ] &&
|
|||
|
been_there_addr=( ${been_there_addr[@]} ${_eia_addr[@]} )
|
|||
|
|
|||
|
for (( _eia = 0 ; _eia < _uca_cnt ; _eia++ ))
|
|||
|
do
|
|||
|
if short_rev ${_eia_addr[${_eia}]} _eia_new
|
|||
|
then
|
|||
|
for (( _eia_cnt = 0 ; _eia_cnt < ${#_eia_new[@]} ; _eia_cnt++ ))
|
|||
|
do
|
|||
|
_eia_tst=${_eia_new[${_eia_cnt}]}
|
|||
|
if _eia_tst=$(name_fixup ${_eia_tst})
|
|||
|
then
|
|||
|
_eia_name[${#_eia_name[@]}]=${_eia_tst}
|
|||
|
fi
|
|||
|
done
|
|||
|
fi
|
|||
|
done
|
|||
|
unique_lines _eia_name _eia_name # Scrub duplicates.
|
|||
|
edit_exact chk_name _eia_name # Scrub pending detail.
|
|||
|
edit_exact known_name _eia_name # Scrub already detailed.
|
|||
|
if [ ${#_eia_name[@]} -gt 0 ] # Anything new?
|
|||
|
then
|
|||
|
uc_name=( ${uc_name[@]} ${_eia_name[@]} )
|
|||
|
pend_func expand_input_name ${1}
|
|||
|
_trace_log[${#_trace_log[@]}]='#Add '${#_eia_name[@]}' unchkd name inp.#'
|
|||
|
fi
|
|||
|
edit_exact chk_address _eia_addr # Scrub pending detail.
|
|||
|
edit_exact known_address _eia_addr # Scrub already detailed.
|
|||
|
if [ ${#_eia_addr[@]} -gt 0 ] # Anything new?
|
|||
|
then
|
|||
|
chk_address=( ${chk_address[@]} ${_eia_addr[@]} )
|
|||
|
pend_func detail_each_address ${1}
|
|||
|
fi
|
|||
|
return 0
|
|||
|
}
|
|||
|
|
|||
|
# The parse-it-yourself zone reply.
|
|||
|
# The input is the chk_name list.
|
|||
|
# detail_each_name <indirection_limit>
|
|||
|
detail_each_name() {
|
|||
|
[ ${#chk_name[@]} -gt 0 ] || return 0
|
|||
|
local -a _den_chk # Names to check
|
|||
|
local -a _den_name # Names found here
|
|||
|
local -a _den_address # Addresses found here
|
|||
|
local -a _den_pair # Pairs found here
|
|||
|
local -a _den_rev # Reverse pairs found here
|
|||
|
local -a _den_tmp # Line being parsed
|
|||
|
local -a _den_auth # SOA contact being parsed
|
|||
|
local -a _den_new # The zone reply
|
|||
|
local -a _den_pc # Parent-Child gets big fast
|
|||
|
local -a _den_ref # So does reference chain
|
|||
|
local -a _den_nr # Name-Resource can be big
|
|||
|
local -a _den_na # Name-Address
|
|||
|
local -a _den_ns # Name-Service
|
|||
|
local -a _den_achn # Chain of Authority
|
|||
|
local -i _den_cnt # Count of names to detail
|
|||
|
local -i _den_lmt # Indirection limit
|
|||
|
local _den_who # Named being processed
|
|||
|
local _den_rec # Record type being processed
|
|||
|
local _den_cont # Contact domain
|
|||
|
local _den_str # Fixed up name string
|
|||
|
local _den_str2 # Fixed up reverse
|
|||
|
local IFS=${WSP_IFS}
|
|||
|
|
|||
|
# Local, unique copy of names to check
|
|||
|
unique_lines chk_name _den_chk
|
|||
|
unset chk_name[@] # Done with globals.
|
|||
|
|
|||
|
# Less any names already known
|
|||
|
edit_exact known_name _den_chk
|
|||
|
_den_cnt=${#_den_chk[@]}
|
|||
|
|
|||
|
# If anything left, add to known_name.
|
|||
|
[ ${_den_cnt} -gt 0 ] &&
|
|||
|
known_name=( ${known_name[@]} ${_den_chk[@]} )
|
|||
|
|
|||
|
# for the list of (previously) unknown names . . .
|
|||
|
for (( _den = 0 ; _den < _den_cnt ; _den++ ))
|
|||
|
do
|
|||
|
_den_who=${_den_chk[${_den}]}
|
|||
|
if long_fwd ${_den_who} _den_new
|
|||
|
then
|
|||
|
unique_lines _den_new _den_new
|
|||
|
if [ ${#_den_new[@]} -eq 0 ]
|
|||
|
then
|
|||
|
_den_pair[${#_den_pair[@]}]='0.0.0.0 '${_den_who}
|
|||
|
fi
|
|||
|
|
|||
|
# Parse each line in the reply.
|
|||
|
for (( _line = 0 ; _line < ${#_den_new[@]} ; _line++ ))
|
|||
|
do
|
|||
|
IFS=${NO_WSP}$'\x09'$'\x20'
|
|||
|
_den_tmp=( ${_den_new[${_line}]} )
|
|||
|
IFS=${WSP_IFS}
|
|||
|
# If usable record and not a warning message . . .
|
|||
|
if [ ${#_den_tmp[@]} -gt 4 ] && [ 'x'${_den_tmp[0]} != 'x;;' ]
|
|||
|
then
|
|||
|
_den_rec=${_den_tmp[3]}
|
|||
|
_den_nr[${#_den_nr[@]}]=${_den_who}' '${_den_rec}
|
|||
|
# Begin at RFC1033 (+++)
|
|||
|
case ${_den_rec} in
|
|||
|
|
|||
|
#<name> [<ttl>] [<class>] SOA <origin> <person>
|
|||
|
SOA) # Start Of Authority
|
|||
|
if _den_str=$(name_fixup ${_den_tmp[0]})
|
|||
|
then
|
|||
|
_den_name[${#_den_name[@]}]=${_den_str}
|
|||
|
_den_achn[${#_den_achn[@]}]=${_den_who}' '${_den_str}' SOA'
|
|||
|
# SOA origin -- domain name of master zone record
|
|||
|
if _den_str2=$(name_fixup ${_den_tmp[4]})
|
|||
|
then
|
|||
|
_den_name[${#_den_name[@]}]=${_den_str2}
|
|||
|
_den_achn[${#_den_achn[@]}]=${_den_who}' '${_den_str2}' SOA.O'
|
|||
|
fi
|
|||
|
# Responsible party e-mail address (possibly bogus).
|
|||
|
# Possibility of first.last@domain.name ignored.
|
|||
|
set -f
|
|||
|
if _den_str2=$(name_fixup ${_den_tmp[5]})
|
|||
|
then
|
|||
|
IFS=${ADR_IFS}
|
|||
|
_den_auth=( ${_den_str2} )
|
|||
|
IFS=${WSP_IFS}
|
|||
|
if [ ${#_den_auth[@]} -gt 2 ]
|
|||
|
then
|
|||
|
_den_cont=${_den_auth[1]}
|
|||
|
for (( _auth = 2 ; _auth < ${#_den_auth[@]} ; _auth++ ))
|
|||
|
do
|
|||
|
_den_cont=${_den_cont}'.'${_den_auth[${_auth}]}
|
|||
|
done
|
|||
|
_den_name[${#_den_name[@]}]=${_den_cont}'.'
|
|||
|
_den_achn[${#_den_achn[@]}]=${_den_who}' '${_den_cont}'. SOA.C'
|
|||
|
fi
|
|||
|
fi
|
|||
|
set +f
|
|||
|
fi
|
|||
|
;;
|
|||
|
|
|||
|
|
|||
|
A) # IP(v4) Address Record
|
|||
|
if _den_str=$(name_fixup ${_den_tmp[0]})
|
|||
|
then
|
|||
|
_den_name[${#_den_name[@]}]=${_den_str}
|
|||
|
_den_pair[${#_den_pair[@]}]=${_den_tmp[4]}' '${_den_str}
|
|||
|
_den_na[${#_den_na[@]}]=${_den_str}' '${_den_tmp[4]}
|
|||
|
_den_ref[${#_den_ref[@]}]=${_den_who}' '${_den_str}' A'
|
|||
|
else
|
|||
|
_den_pair[${#_den_pair[@]}]=${_den_tmp[4]}' unknown.domain'
|
|||
|
_den_na[${#_den_na[@]}]='unknown.domain '${_den_tmp[4]}
|
|||
|
_den_ref[${#_den_ref[@]}]=${_den_who}' unknown.domain A'
|
|||
|
fi
|
|||
|
_den_address[${#_den_address[@]}]=${_den_tmp[4]}
|
|||
|
_den_pc[${#_den_pc[@]}]=${_den_who}' '${_den_tmp[4]}
|
|||
|
;;
|
|||
|
|
|||
|
NS) # Name Server Record
|
|||
|
# Domain name being serviced (may be other than current)
|
|||
|
if _den_str=$(name_fixup ${_den_tmp[0]})
|
|||
|
then
|
|||
|
_den_name[${#_den_name[@]}]=${_den_str}
|
|||
|
_den_ref[${#_den_ref[@]}]=${_den_who}' '${_den_str}' NS'
|
|||
|
|
|||
|
# Domain name of service provider
|
|||
|
if _den_str2=$(name_fixup ${_den_tmp[4]})
|
|||
|
then
|
|||
|
_den_name[${#_den_name[@]}]=${_den_str2}
|
|||
|
_den_ref[${#_den_ref[@]}]=${_den_who}' '${_den_str2}' NSH'
|
|||
|
_den_ns[${#_den_ns[@]}]=${_den_str2}' NS'
|
|||
|
_den_pc[${#_den_pc[@]}]=${_den_str}' '${_den_str2}
|
|||
|
fi
|
|||
|
fi
|
|||
|
;;
|
|||
|
|
|||
|
MX) # Mail Server Record
|
|||
|
# Domain name being serviced (wildcards not handled here)
|
|||
|
if _den_str=$(name_fixup ${_den_tmp[0]})
|
|||
|
then
|
|||
|
_den_name[${#_den_name[@]}]=${_den_str}
|
|||
|
_den_ref[${#_den_ref[@]}]=${_den_who}' '${_den_str}' MX'
|
|||
|
fi
|
|||
|
# Domain name of service provider
|
|||
|
if _den_str=$(name_fixup ${_den_tmp[5]})
|
|||
|
then
|
|||
|
_den_name[${#_den_name[@]}]=${_den_str}
|
|||
|
_den_ref[${#_den_ref[@]}]=${_den_who}' '${_den_str}' MXH'
|
|||
|
_den_ns[${#_den_ns[@]}]=${_den_str}' MX'
|
|||
|
_den_pc[${#_den_pc[@]}]=${_den_who}' '${_den_str}
|
|||
|
fi
|
|||
|
;;
|
|||
|
|
|||
|
PTR) # Reverse address record
|
|||
|
# Special name
|
|||
|
if _den_str=$(name_fixup ${_den_tmp[0]})
|
|||
|
then
|
|||
|
_den_ref[${#_den_ref[@]}]=${_den_who}' '${_den_str}' PTR'
|
|||
|
# Host name (not a CNAME)
|
|||
|
if _den_str2=$(name_fixup ${_den_tmp[4]})
|
|||
|
then
|
|||
|
_den_rev[${#_den_rev[@]}]=${_den_str}' '${_den_str2}
|
|||
|
_den_ref[${#_den_ref[@]}]=${_den_who}' '${_den_str2}' PTRH'
|
|||
|
_den_pc[${#_den_pc[@]}]=${_den_who}' '${_den_str}
|
|||
|
fi
|
|||
|
fi
|
|||
|
;;
|
|||
|
|
|||
|
AAAA) # IP(v6) Address Record
|
|||
|
if _den_str=$(name_fixup ${_den_tmp[0]})
|
|||
|
then
|
|||
|
_den_name[${#_den_name[@]}]=${_den_str}
|
|||
|
_den_pair[${#_den_pair[@]}]=${_den_tmp[4]}' '${_den_str}
|
|||
|
_den_na[${#_den_na[@]}]=${_den_str}' '${_den_tmp[4]}
|
|||
|
_den_ref[${#_den_ref[@]}]=${_den_who}' '${_den_str}' AAAA'
|
|||
|
else
|
|||
|
_den_pair[${#_den_pair[@]}]=${_den_tmp[4]}' unknown.domain'
|
|||
|
_den_na[${#_den_na[@]}]='unknown.domain '${_den_tmp[4]}
|
|||
|
_den_ref[${#_den_ref[@]}]=${_den_who}' unknown.domain'
|
|||
|
fi
|
|||
|
# No processing for IPv6 addresses
|
|||
|
_den_pc[${#_den_pc[@]}]=${_den_who}' '${_den_tmp[4]}
|
|||
|
;;
|
|||
|
|
|||
|
CNAME) # Alias name record
|
|||
|
# Nickname
|
|||
|
if _den_str=$(name_fixup ${_den_tmp[0]})
|
|||
|
then
|
|||
|
_den_name[${#_den_name[@]}]=${_den_str}
|
|||
|
_den_ref[${#_den_ref[@]}]=${_den_who}' '${_den_str}' CNAME'
|
|||
|
_den_pc[${#_den_pc[@]}]=${_den_who}' '${_den_str}
|
|||
|
fi
|
|||
|
# Hostname
|
|||
|
if _den_str=$(name_fixup ${_den_tmp[4]})
|
|||
|
then
|
|||
|
_den_name[${#_den_name[@]}]=${_den_str}
|
|||
|
_den_ref[${#_den_ref[@]}]=${_den_who}' '${_den_str}' CHOST'
|
|||
|
_den_pc[${#_den_pc[@]}]=${_den_who}' '${_den_str}
|
|||
|
fi
|
|||
|
;;
|
|||
|
# TXT)
|
|||
|
# ;;
|
|||
|
esac
|
|||
|
fi
|
|||
|
done
|
|||
|
else # Lookup error == 'A' record 'unknown address'
|
|||
|
_den_pair[${#_den_pair[@]}]='0.0.0.0 '${_den_who}
|
|||
|
fi
|
|||
|
done
|
|||
|
|
|||
|
# Control dot array growth.
|
|||
|
unique_lines _den_achn _den_achn # Works best, all the same.
|
|||
|
edit_exact auth_chain _den_achn # Works best, unique items.
|
|||
|
if [ ${#_den_achn[@]} -gt 0 ]
|
|||
|
then
|
|||
|
IFS=${NO_WSP}
|
|||
|
auth_chain=( ${auth_chain[@]} ${_den_achn[@]} )
|
|||
|
IFS=${WSP_IFS}
|
|||
|
fi
|
|||
|
|
|||
|
unique_lines _den_ref _den_ref # Works best, all the same.
|
|||
|
edit_exact ref_chain _den_ref # Works best, unique items.
|
|||
|
if [ ${#_den_ref[@]} -gt 0 ]
|
|||
|
then
|
|||
|
IFS=${NO_WSP}
|
|||
|
ref_chain=( ${ref_chain[@]} ${_den_ref[@]} )
|
|||
|
IFS=${WSP_IFS}
|
|||
|
fi
|
|||
|
|
|||
|
unique_lines _den_na _den_na
|
|||
|
edit_exact name_address _den_na
|
|||
|
if [ ${#_den_na[@]} -gt 0 ]
|
|||
|
then
|
|||
|
IFS=${NO_WSP}
|
|||
|
name_address=( ${name_address[@]} ${_den_na[@]} )
|
|||
|
IFS=${WSP_IFS}
|
|||
|
fi
|
|||
|
|
|||
|
unique_lines _den_ns _den_ns
|
|||
|
edit_exact name_srvc _den_ns
|
|||
|
if [ ${#_den_ns[@]} -gt 0 ]
|
|||
|
then
|
|||
|
IFS=${NO_WSP}
|
|||
|
name_srvc=( ${name_srvc[@]} ${_den_ns[@]} )
|
|||
|
IFS=${WSP_IFS}
|
|||
|
fi
|
|||
|
|
|||
|
unique_lines _den_nr _den_nr
|
|||
|
edit_exact name_resource _den_nr
|
|||
|
if [ ${#_den_nr[@]} -gt 0 ]
|
|||
|
then
|
|||
|
IFS=${NO_WSP}
|
|||
|
name_resource=( ${name_resource[@]} ${_den_nr[@]} )
|
|||
|
IFS=${WSP_IFS}
|
|||
|
fi
|
|||
|
|
|||
|
unique_lines _den_pc _den_pc
|
|||
|
edit_exact parent_child _den_pc
|
|||
|
if [ ${#_den_pc[@]} -gt 0 ]
|
|||
|
then
|
|||
|
IFS=${NO_WSP}
|
|||
|
parent_child=( ${parent_child[@]} ${_den_pc[@]} )
|
|||
|
IFS=${WSP_IFS}
|
|||
|
fi
|
|||
|
|
|||
|
# Update list known_pair (Address and Name).
|
|||
|
unique_lines _den_pair _den_pair
|
|||
|
edit_exact known_pair _den_pair
|
|||
|
if [ ${#_den_pair[@]} -gt 0 ] # Anything new?
|
|||
|
then
|
|||
|
IFS=${NO_WSP}
|
|||
|
known_pair=( ${known_pair[@]} ${_den_pair[@]} )
|
|||
|
IFS=${WSP_IFS}
|
|||
|
fi
|
|||
|
|
|||
|
# Update list of reverse pairs.
|
|||
|
unique_lines _den_rev _den_rev
|
|||
|
edit_exact reverse_pair _den_rev
|
|||
|
if [ ${#_den_rev[@]} -gt 0 ] # Anything new?
|
|||
|
then
|
|||
|
IFS=${NO_WSP}
|
|||
|
reverse_pair=( ${reverse_pair[@]} ${_den_rev[@]} )
|
|||
|
IFS=${WSP_IFS}
|
|||
|
fi
|
|||
|
|
|||
|
# Check indirection limit -- give up if reached.
|
|||
|
if ! _den_lmt=$(limit_chk ${1})
|
|||
|
then
|
|||
|
return 0
|
|||
|
fi
|
|||
|
|
|||
|
# Execution engine is LIFO. Order of pend operations is important.
|
|||
|
# Did we define any new addresses?
|
|||
|
unique_lines _den_address _den_address # Scrub duplicates.
|
|||
|
edit_exact known_address _den_address # Scrub already processed.
|
|||
|
edit_exact un_address _den_address # Scrub already waiting.
|
|||
|
if [ ${#_den_address[@]} -gt 0 ] # Anything new?
|
|||
|
then
|
|||
|
uc_address=( ${uc_address[@]} ${_den_address[@]} )
|
|||
|
pend_func expand_input_address ${_den_lmt}
|
|||
|
_trace_log[${#_trace_log[@]}]='# Add '${#_den_address[@]}' unchkd addr. #'
|
|||
|
fi
|
|||
|
|
|||
|
# Did we find any new names?
|
|||
|
unique_lines _den_name _den_name # Scrub duplicates.
|
|||
|
edit_exact known_name _den_name # Scrub already processed.
|
|||
|
edit_exact uc_name _den_name # Scrub already waiting.
|
|||
|
if [ ${#_den_name[@]} -gt 0 ] # Anything new?
|
|||
|
then
|
|||
|
uc_name=( ${uc_name[@]} ${_den_name[@]} )
|
|||
|
pend_func expand_input_name ${_den_lmt}
|
|||
|
_trace_log[${#_trace_log[@]}]='#Added '${#_den_name[@]}' unchkd name#'
|
|||
|
fi
|
|||
|
return 0
|
|||
|
}
|
|||
|
|
|||
|
# The parse-it-yourself delegation reply
|
|||
|
# Input is the chk_address list.
|
|||
|
# detail_each_address <indirection_limit>
|
|||
|
detail_each_address() {
|
|||
|
[ ${#chk_address[@]} -gt 0 ] || return 0
|
|||
|
unique_lines chk_address chk_address
|
|||
|
edit_exact known_address chk_address
|
|||
|
if [ ${#chk_address[@]} -gt 0 ]
|
|||
|
then
|
|||
|
known_address=( ${known_address[@]} ${chk_address[@]} )
|
|||
|
unset chk_address[@]
|
|||
|
fi
|
|||
|
return 0
|
|||
|
}
|
|||
|
|
|||
|
# # # Application specific output functions # # #
|
|||
|
|
|||
|
# Pretty print the known pairs.
|
|||
|
report_pairs() {
|
|||
|
echo
|
|||
|
echo 'Known network pairs.'
|
|||
|
col_print known_pair 2 5 30
|
|||
|
|
|||
|
if [ ${#auth_chain[@]} -gt 0 ]
|
|||
|
then
|
|||
|
echo
|
|||
|
echo 'Known chain of authority.'
|
|||
|
col_print auth_chain 2 5 30 55
|
|||
|
fi
|
|||
|
|
|||
|
if [ ${#reverse_pair[@]} -gt 0 ]
|
|||
|
then
|
|||
|
echo
|
|||
|
echo 'Known reverse pairs.'
|
|||
|
col_print reverse_pair 2 5 55
|
|||
|
fi
|
|||
|
return 0
|
|||
|
}
|
|||
|
|
|||
|
# Check an address against the list of blacklist servers.
|
|||
|
# A good place to capture for GraphViz: address->status(server(reports))
|
|||
|
# check_lists <ip_address>
|
|||
|
check_lists() {
|
|||
|
[ $# -eq 1 ] || return 1
|
|||
|
local -a _cl_fwd_addr
|
|||
|
local -a _cl_rev_addr
|
|||
|
local -a _cl_reply
|
|||
|
local -i _cl_rc
|
|||
|
local -i _ls_cnt
|
|||
|
local _cl_dns_addr
|
|||
|
local _cl_lkup
|
|||
|
|
|||
|
split_ip ${1} _cl_fwd_addr _cl_rev_addr
|
|||
|
_cl_dns_addr=$(dot_array _cl_rev_addr)'.'
|
|||
|
_ls_cnt=${#list_server[@]}
|
|||
|
echo ' Checking address '${1}
|
|||
|
for (( _cl = 0 ; _cl < _ls_cnt ; _cl++ ))
|
|||
|
do
|
|||
|
_cl_lkup=${_cl_dns_addr}${list_server[${_cl}]}
|
|||
|
if short_text ${_cl_lkup} _cl_reply
|
|||
|
then
|
|||
|
if [ ${#_cl_reply[@]} -gt 0 ]
|
|||
|
then
|
|||
|
echo ' Records from '${list_server[${_cl}]}
|
|||
|
address_hits[${#address_hits[@]}]=${1}' '${list_server[${_cl}]}
|
|||
|
_hs_RC=2
|
|||
|
for (( _clr = 0 ; _clr < ${#_cl_reply[@]} ; _clr++ ))
|
|||
|
do
|
|||
|
echo ' '${_cl_reply[${_clr}]}
|
|||
|
done
|
|||
|
fi
|
|||
|
fi
|
|||
|
done
|
|||
|
return 0
|
|||
|
}
|
|||
|
|
|||
|
# # # The usual application glue # # #
|
|||
|
|
|||
|
# Who did it?
|
|||
|
credits() {
|
|||
|
echo
|
|||
|
echo 'Advanced Bash Scripting Guide: is_spammer.bash, v2, 2004-msz'
|
|||
|
}
|
|||
|
|
|||
|
# How to use it?
|
|||
|
# (See also, "Quickstart" at end of script.)
|
|||
|
usage() {
|
|||
|
cat <<-'_usage_statement_'
|
|||
|
The script is_spammer.bash requires either one or two arguments.
|
|||
|
|
|||
|
arg 1) May be one of:
|
|||
|
a) A domain name
|
|||
|
b) An IPv4 address
|
|||
|
c) The name of a file with any mix of names
|
|||
|
and addresses, one per line.
|
|||
|
|
|||
|
arg 2) May be one of:
|
|||
|
a) A Blacklist server domain name
|
|||
|
b) The name of a file with Blacklist server
|
|||
|
domain names, one per line.
|
|||
|
c) If not present, a default list of (free)
|
|||
|
Blacklist servers is used.
|
|||
|
d) If a filename of an empty, readable, file
|
|||
|
is given,
|
|||
|
Blacklist server lookup is disabled.
|
|||
|
|
|||
|
All script output is written to stdout.
|
|||
|
|
|||
|
Return codes: 0 -> All OK, 1 -> Script failure,
|
|||
|
2 -> Something is Blacklisted.
|
|||
|
|
|||
|
Requires the external program 'dig' from the 'bind-9'
|
|||
|
set of DNS programs. See: http://www.isc.org
|
|||
|
|
|||
|
The domain name lookup depth limit defaults to 2 levels.
|
|||
|
Set the environment variable SPAMMER_LIMIT to change.
|
|||
|
SPAMMER_LIMIT=0 means 'unlimited'
|
|||
|
|
|||
|
Limit may also be set on the command-line.
|
|||
|
If arg#1 is an integer, the limit is set to that value
|
|||
|
and then the above argument rules are applied.
|
|||
|
|
|||
|
Setting the environment variable 'SPAMMER_DATA' to a filename
|
|||
|
will cause the script to write a GraphViz graphic file.
|
|||
|
|
|||
|
For the development version;
|
|||
|
Setting the environment variable 'SPAMMER_TRACE' to a filename
|
|||
|
will cause the execution engine to log a function call trace.
|
|||
|
|
|||
|
_usage_statement_
|
|||
|
}
|
|||
|
|
|||
|
# The default list of Blacklist servers:
|
|||
|
# Many choices, see: http://www.spews.org/lists.html
|
|||
|
|
|||
|
declare -a default_servers
|
|||
|
# See: http://www.spamhaus.org (Conservative, well maintained)
|
|||
|
default_servers[0]='sbl-xbl.spamhaus.org'
|
|||
|
# See: http://ordb.org (Open mail relays)
|
|||
|
default_servers[1]='relays.ordb.org'
|
|||
|
# See: http://www.spamcop.net/ (You can report spammers here)
|
|||
|
default_servers[2]='bl.spamcop.net'
|
|||
|
# See: http://www.spews.org (An 'early detect' system)
|
|||
|
default_servers[3]='l2.spews.dnsbl.sorbs.net'
|
|||
|
# See: http://www.dnsbl.us.sorbs.net/using.shtml
|
|||
|
default_servers[4]='dnsbl.sorbs.net'
|
|||
|
# See: http://dsbl.org/usage (Various mail relay lists)
|
|||
|
default_servers[5]='list.dsbl.org'
|
|||
|
default_servers[6]='multihop.dsbl.org'
|
|||
|
default_servers[7]='unconfirmed.dsbl.org'
|
|||
|
|
|||
|
# User input argument #1
|
|||
|
setup_input() {
|
|||
|
if [ -e ${1} ] && [ -r ${1} ] # Name of readable file
|
|||
|
then
|
|||
|
file_to_array ${1} uc_name
|
|||
|
echo 'Using filename >'${1}'< as input.'
|
|||
|
else
|
|||
|
if is_address ${1} # IP address?
|
|||
|
then
|
|||
|
uc_address=( ${1} )
|
|||
|
echo 'Starting with address >'${1}'<'
|
|||
|
else # Must be a name.
|
|||
|
uc_name=( ${1} )
|
|||
|
echo 'Starting with domain name >'${1}'<'
|
|||
|
fi
|
|||
|
fi
|
|||
|
return 0
|
|||
|
}
|
|||
|
|
|||
|
# User input argument #2
|
|||
|
setup_servers() {
|
|||
|
if [ -e ${1} ] && [ -r ${1} ] # Name of a readable file
|
|||
|
then
|
|||
|
file_to_array ${1} list_server
|
|||
|
echo 'Using filename >'${1}'< as blacklist server list.'
|
|||
|
else
|
|||
|
list_server=( ${1} )
|
|||
|
echo 'Using blacklist server >'${1}'<'
|
|||
|
fi
|
|||
|
return 0
|
|||
|
}
|
|||
|
|
|||
|
# User environment variable SPAMMER_TRACE
|
|||
|
live_log_die() {
|
|||
|
if [ ${SPAMMER_TRACE:=} ] # Wants trace log?
|
|||
|
then
|
|||
|
if [ ! -e ${SPAMMER_TRACE} ]
|
|||
|
then
|
|||
|
if ! touch ${SPAMMER_TRACE} 2>/dev/null
|
|||
|
then
|
|||
|
pend_func echo $(printf '%q\n' \
|
|||
|
'Unable to create log file >'${SPAMMER_TRACE}'<')
|
|||
|
pend_release
|
|||
|
exit 1
|
|||
|
fi
|
|||
|
_log_file=${SPAMMER_TRACE}
|
|||
|
_pend_hook_=trace_logger
|
|||
|
_log_dump=dump_log
|
|||
|
else
|
|||
|
if [ ! -w ${SPAMMER_TRACE} ]
|
|||
|
then
|
|||
|
pend_func echo $(printf '%q\n' \
|
|||
|
'Unable to write log file >'${SPAMMER_TRACE}'<')
|
|||
|
pend_release
|
|||
|
exit 1
|
|||
|
fi
|
|||
|
_log_file=${SPAMMER_TRACE}
|
|||
|
echo '' > ${_log_file}
|
|||
|
_pend_hook_=trace_logger
|
|||
|
_log_dump=dump_log
|
|||
|
fi
|
|||
|
fi
|
|||
|
return 0
|
|||
|
}
|
|||
|
|
|||
|
# User environment variable SPAMMER_DATA
|
|||
|
data_capture() {
|
|||
|
if [ ${SPAMMER_DATA:=} ] # Wants a data dump?
|
|||
|
then
|
|||
|
if [ ! -e ${SPAMMER_DATA} ]
|
|||
|
then
|
|||
|
if ! touch ${SPAMMER_DATA} 2>/dev/null
|
|||
|
then
|
|||
|
pend_func echo $(printf '%q]n' \
|
|||
|
'Unable to create data output file >'${SPAMMER_DATA}'<')
|
|||
|
pend_release
|
|||
|
exit 1
|
|||
|
fi
|
|||
|
_dot_file=${SPAMMER_DATA}
|
|||
|
_dot_dump=dump_dot
|
|||
|
else
|
|||
|
if [ ! -w ${SPAMMER_DATA} ]
|
|||
|
then
|
|||
|
pend_func echo $(printf '%q\n' \
|
|||
|
'Unable to write data output file >'${SPAMMER_DATA}'<')
|
|||
|
pend_release
|
|||
|
exit 1
|
|||
|
fi
|
|||
|
_dot_file=${SPAMMER_DATA}
|
|||
|
_dot_dump=dump_dot
|
|||
|
fi
|
|||
|
fi
|
|||
|
return 0
|
|||
|
}
|
|||
|
|
|||
|
# Grope user specified arguments.
|
|||
|
do_user_args() {
|
|||
|
if [ $# -gt 0 ] && is_number $1
|
|||
|
then
|
|||
|
indirect=$1
|
|||
|
shift
|
|||
|
fi
|
|||
|
|
|||
|
case $# in # Did user treat us well?
|
|||
|
1)
|
|||
|
if ! setup_input $1 # Needs error checking.
|
|||
|
then
|
|||
|
pend_release
|
|||
|
$_log_dump
|
|||
|
exit 1
|
|||
|
fi
|
|||
|
list_server=( ${default_servers[@]} )
|
|||
|
_list_cnt=${#list_server[@]}
|
|||
|
echo 'Using default blacklist server list.'
|
|||
|
echo 'Search depth limit: '${indirect}
|
|||
|
;;
|
|||
|
2)
|
|||
|
if ! setup_input $1 # Needs error checking.
|
|||
|
then
|
|||
|
pend_release
|
|||
|
$_log_dump
|
|||
|
exit 1
|
|||
|
fi
|
|||
|
if ! setup_servers $2 # Needs error checking.
|
|||
|
then
|
|||
|
pend_release
|
|||
|
$_log_dump
|
|||
|
exit 1
|
|||
|
fi
|
|||
|
echo 'Search depth limit: '${indirect}
|
|||
|
;;
|
|||
|
*)
|
|||
|
pend_func usage
|
|||
|
pend_release
|
|||
|
$_log_dump
|
|||
|
exit 1
|
|||
|
;;
|
|||
|
esac
|
|||
|
return 0
|
|||
|
}
|
|||
|
|
|||
|
# A general purpose debug tool.
|
|||
|
# list_array <array_name>
|
|||
|
list_array() {
|
|||
|
[ $# -eq 1 ] || return 1 # One argument required.
|
|||
|
|
|||
|
local -a _la_lines
|
|||
|
set -f
|
|||
|
local IFS=${NO_WSP}
|
|||
|
eval _la_lines=\(\ \$\{$1\[@\]\}\ \)
|
|||
|
echo
|
|||
|
echo "Element count "${#_la_lines[@]}" array "${1}
|
|||
|
local _ln_cnt=${#_la_lines[@]}
|
|||
|
|
|||
|
for (( _i = 0; _i < ${_ln_cnt}; _i++ ))
|
|||
|
do
|
|||
|
echo 'Element '$_i' >'${_la_lines[$_i]}'<'
|
|||
|
done
|
|||
|
set +f
|
|||
|
return 0
|
|||
|
}
|
|||
|
|
|||
|
# # # 'Hunt the Spammer' program code # # #
|
|||
|
pend_init # Ready stack engine.
|
|||
|
pend_func credits # Last thing to print.
|
|||
|
|
|||
|
# # # Deal with user # # #
|
|||
|
live_log_die # Setup debug trace log.
|
|||
|
data_capture # Setup data capture file.
|
|||
|
echo
|
|||
|
do_user_args $@
|
|||
|
|
|||
|
# # # Haven't exited yet - There is some hope # # #
|
|||
|
# Discovery group - Execution engine is LIFO - pend
|
|||
|
# in reverse order of execution.
|
|||
|
_hs_RC=0 # Hunt the Spammer return code
|
|||
|
pend_mark
|
|||
|
pend_func report_pairs # Report name-address pairs.
|
|||
|
|
|||
|
# The two detail_* are mutually recursive functions.
|
|||
|
# They also pend expand_* functions as required.
|
|||
|
# These two (the last of ???) exit the recursion.
|
|||
|
pend_func detail_each_address # Get all resources of addresses.
|
|||
|
pend_func detail_each_name # Get all resources of names.
|
|||
|
|
|||
|
# The two expand_* are mutually recursive functions,
|
|||
|
#+ which pend additional detail_* functions as required.
|
|||
|
pend_func expand_input_address 1 # Expand input names by address.
|
|||
|
pend_func expand_input_name 1 # #xpand input addresses by name.
|
|||
|
|
|||
|
# Start with a unique set of names and addresses.
|
|||
|
pend_func unique_lines uc_address uc_address
|
|||
|
pend_func unique_lines uc_name uc_name
|
|||
|
|
|||
|
# Separate mixed input of names and addresses.
|
|||
|
pend_func split_input
|
|||
|
pend_release
|
|||
|
|
|||
|
# # # Pairs reported -- Unique list of IP addresses found
|
|||
|
echo
|
|||
|
_ip_cnt=${#known_address[@]}
|
|||
|
if [ ${#list_server[@]} -eq 0 ]
|
|||
|
then
|
|||
|
echo 'Blacklist server list empty, none checked.'
|
|||
|
else
|
|||
|
if [ ${_ip_cnt} -eq 0 ]
|
|||
|
then
|
|||
|
echo 'Known address list empty, none checked.'
|
|||
|
else
|
|||
|
_ip_cnt=${_ip_cnt}-1 # Start at top.
|
|||
|
echo 'Checking Blacklist servers.'
|
|||
|
for (( _ip = _ip_cnt ; _ip >= 0 ; _ip-- ))
|
|||
|
do
|
|||
|
pend_func check_lists $( printf '%q\n' ${known_address[$_ip]} )
|
|||
|
done
|
|||
|
fi
|
|||
|
fi
|
|||
|
pend_release
|
|||
|
$_dot_dump # Graphics file dump
|
|||
|
$_log_dump # Execution trace
|
|||
|
echo
|
|||
|
|
|||
|
|
|||
|
##############################
|
|||
|
# Example output from script #
|
|||
|
##############################
|
|||
|
:<<-'_is_spammer_outputs_'
|
|||
|
|
|||
|
./is_spammer.bash 0 web4.alojamentos7.com
|
|||
|
|
|||
|
Starting with domain name >web4.alojamentos7.com<
|
|||
|
Using default blacklist server list.
|
|||
|
Search depth limit: 0
|
|||
|
.:....::::...:::...:::.......::..::...:::.......::
|
|||
|
Known network pairs.
|
|||
|
66.98.208.97 web4.alojamentos7.com.
|
|||
|
66.98.208.97 ns1.alojamentos7.com.
|
|||
|
69.56.202.147 ns2.alojamentos.ws.
|
|||
|
66.98.208.97 alojamentos7.com.
|
|||
|
66.98.208.97 web.alojamentos7.com.
|
|||
|
69.56.202.146 ns1.alojamentos.ws.
|
|||
|
69.56.202.146 alojamentos.ws.
|
|||
|
66.235.180.113 ns1.alojamentos.org.
|
|||
|
66.235.181.192 ns2.alojamentos.org.
|
|||
|
66.235.180.113 alojamentos.org.
|
|||
|
66.235.180.113 web6.alojamentos.org.
|
|||
|
216.234.234.30 ns1.theplanet.com.
|
|||
|
12.96.160.115 ns2.theplanet.com.
|
|||
|
216.185.111.52 mail1.theplanet.com.
|
|||
|
69.56.141.4 spooling.theplanet.com.
|
|||
|
216.185.111.40 theplanet.com.
|
|||
|
216.185.111.40 www.theplanet.com.
|
|||
|
216.185.111.52 mail.theplanet.com.
|
|||
|
|
|||
|
Checking Blacklist servers.
|
|||
|
Checking address 66.98.208.97
|
|||
|
Records from dnsbl.sorbs.net
|
|||
|
"Spam Received See: http://www.dnsbl.sorbs.net/lookup.shtml?66.98.208.97"
|
|||
|
Checking address 69.56.202.147
|
|||
|
Checking address 69.56.202.146
|
|||
|
Checking address 66.235.180.113
|
|||
|
Checking address 66.235.181.192
|
|||
|
Checking address 216.185.111.40
|
|||
|
Checking address 216.234.234.30
|
|||
|
Checking address 12.96.160.115
|
|||
|
Checking address 216.185.111.52
|
|||
|
Checking address 69.56.141.4
|
|||
|
|
|||
|
Advanced Bash Scripting Guide: is_spammer.bash, v2, 2004-msz
|
|||
|
|
|||
|
_is_spammer_outputs_
|
|||
|
|
|||
|
exit ${_hs_RC}
|
|||
|
|
|||
|
####################################################
|
|||
|
# The script ignores everything from here on down #
|
|||
|
#+ because of the 'exit' command, just above. #
|
|||
|
####################################################
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Quickstart
|
|||
|
==========
|
|||
|
|
|||
|
Prerequisites
|
|||
|
|
|||
|
Bash version 2.05b or 3.00 (bash --version)
|
|||
|
A version of Bash which supports arrays. Array
|
|||
|
support is included by default Bash configurations.
|
|||
|
|
|||
|
'dig,' version 9.x.x (dig $HOSTNAME, see first line of output)
|
|||
|
A version of dig which supports the +short options.
|
|||
|
See: dig_wrappers.bash for details.
|
|||
|
|
|||
|
|
|||
|
Optional Prerequisites
|
|||
|
|
|||
|
'named,' a local DNS caching program. Any flavor will do.
|
|||
|
Do twice: dig $HOSTNAME
|
|||
|
Check near bottom of output for: SERVER: 127.0.0.1#53
|
|||
|
That means you have one running.
|
|||
|
|
|||
|
|
|||
|
Optional Graphics Support
|
|||
|
|
|||
|
'date,' a standard *nix thing. (date -R)
|
|||
|
|
|||
|
dot Program to convert graphic description file to a
|
|||
|
diagram. (dot -V)
|
|||
|
A part of the Graph-Viz set of programs.
|
|||
|
See: [http://www.research.att.com/sw/tools/graphviz||GraphViz]
|
|||
|
|
|||
|
'dotty,' a visual editor for graphic description files.
|
|||
|
Also a part of the Graph-Viz set of programs.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Quick Start
|
|||
|
|
|||
|
In the same directory as the is_spammer.bash script;
|
|||
|
Do: ./is_spammer.bash
|
|||
|
|
|||
|
Usage Details
|
|||
|
|
|||
|
1. Blacklist server choices.
|
|||
|
|
|||
|
(a) To use default, built-in list: Do nothing.
|
|||
|
|
|||
|
(b) To use your own list:
|
|||
|
|
|||
|
i. Create a file with a single Blacklist server
|
|||
|
domain name per line.
|
|||
|
|
|||
|
ii. Provide that filename as the last argument to
|
|||
|
the script.
|
|||
|
|
|||
|
(c) To use a single Blacklist server: Last argument
|
|||
|
to the script.
|
|||
|
|
|||
|
(d) To disable Blacklist lookups:
|
|||
|
|
|||
|
i. Create an empty file (touch spammer.nul)
|
|||
|
Your choice of filename.
|
|||
|
|
|||
|
ii. Provide the filename of that empty file as the
|
|||
|
last argument to the script.
|
|||
|
|
|||
|
2. Search depth limit.
|
|||
|
|
|||
|
(a) To use the default value of 2: Do nothing.
|
|||
|
|
|||
|
(b) To set a different limit:
|
|||
|
A limit of 0 means: no limit.
|
|||
|
|
|||
|
i. export SPAMMER_LIMIT=1
|
|||
|
or whatever limit you want.
|
|||
|
|
|||
|
ii. OR provide the desired limit as the first
|
|||
|
argument to the script.
|
|||
|
|
|||
|
3. Optional execution trace log.
|
|||
|
|
|||
|
(a) To use the default setting of no log output: Do nothing.
|
|||
|
|
|||
|
(b) To write an execution trace log:
|
|||
|
export SPAMMER_TRACE=spammer.log
|
|||
|
or whatever filename you want.
|
|||
|
|
|||
|
4. Optional graphic description file.
|
|||
|
|
|||
|
(a) To use the default setting of no graphic file: Do nothing.
|
|||
|
|
|||
|
(b) To write a Graph-Viz graphic description file:
|
|||
|
export SPAMMER_DATA=spammer.dot
|
|||
|
or whatever filename you want.
|
|||
|
|
|||
|
5. Where to start the search.
|
|||
|
|
|||
|
(a) Starting with a single domain name:
|
|||
|
|
|||
|
i. Without a command-line search limit: First
|
|||
|
argument to script.
|
|||
|
|
|||
|
ii. With a command-line search limit: Second
|
|||
|
argument to script.
|
|||
|
|
|||
|
(b) Starting with a single IP address:
|
|||
|
|
|||
|
i. Without a command-line search limit: First
|
|||
|
argument to script.
|
|||
|
|
|||
|
ii. With a command-line search limit: Second
|
|||
|
argument to script.
|
|||
|
|
|||
|
(c) Starting with (mixed) multiple name(s) and/or address(es):
|
|||
|
Create a file with one name or address per line.
|
|||
|
Your choice of filename.
|
|||
|
|
|||
|
i. Without a command-line search limit: Filename as
|
|||
|
first argument to script.
|
|||
|
|
|||
|
ii. With a command-line search limit: Filename as
|
|||
|
second argument to script.
|
|||
|
|
|||
|
6. What to do with the display output.
|
|||
|
|
|||
|
(a) To view display output on screen: Do nothing.
|
|||
|
|
|||
|
(b) To save display output to a file: Redirect stdout to a filename.
|
|||
|
|
|||
|
(c) To discard display output: Redirect stdout to /dev/null.
|
|||
|
|
|||
|
7. Temporary end of decision making.
|
|||
|
press RETURN
|
|||
|
wait (optionally, watch the dots and colons).
|
|||
|
|
|||
|
8. Optionally check the return code.
|
|||
|
|
|||
|
(a) Return code 0: All OK
|
|||
|
|
|||
|
(b) Return code 1: Script setup failure
|
|||
|
|
|||
|
(c) Return code 2: Something was blacklisted.
|
|||
|
|
|||
|
9. Where is my graph (diagram)?
|
|||
|
|
|||
|
The script does not directly produce a graph (diagram).
|
|||
|
It only produces a graphic description file. You can
|
|||
|
process the graphic descriptor file that was output
|
|||
|
with the 'dot' program.
|
|||
|
|
|||
|
Until you edit that descriptor file, to describe the
|
|||
|
relationships you want shown, all that you will get is
|
|||
|
a bunch of labeled name and address nodes.
|
|||
|
|
|||
|
All of the script's discovered relationships are within
|
|||
|
a comment block in the graphic descriptor file, each
|
|||
|
with a descriptive heading.
|
|||
|
|
|||
|
The editing required to draw a line between a pair of
|
|||
|
nodes from the information in the descriptor file may
|
|||
|
be done with a text editor.
|
|||
|
|
|||
|
Given these lines somewhere in the descriptor file:
|
|||
|
|
|||
|
# Known domain name nodes
|
|||
|
|
|||
|
N0000 [label="guardproof.info."] ;
|
|||
|
|
|||
|
N0002 [label="third.guardproof.info."] ;
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# Known address nodes
|
|||
|
|
|||
|
A0000 [label="61.141.32.197"] ;
|
|||
|
|
|||
|
|
|||
|
|
|||
|
/*
|
|||
|
|
|||
|
# Known name->address edges
|
|||
|
|
|||
|
NA0000 third.guardproof.info. 61.141.32.197
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# Known parent->child edges
|
|||
|
|
|||
|
PC0000 guardproof.info. third.guardproof.info.
|
|||
|
|
|||
|
*/
|
|||
|
|
|||
|
Turn that into the following lines by substituting node
|
|||
|
identifiers into the relationships:
|
|||
|
|
|||
|
# Known domain name nodes
|
|||
|
|
|||
|
N0000 [label="guardproof.info."] ;
|
|||
|
|
|||
|
N0002 [label="third.guardproof.info."] ;
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# Known address nodes
|
|||
|
|
|||
|
A0000 [label="61.141.32.197"] ;
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# PC0000 guardproof.info. third.guardproof.info.
|
|||
|
|
|||
|
N0000->N0002 ;
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# NA0000 third.guardproof.info. 61.141.32.197
|
|||
|
|
|||
|
N0002->A0000 ;
|
|||
|
|
|||
|
|
|||
|
|
|||
|
/*
|
|||
|
|
|||
|
# Known name->address edges
|
|||
|
|
|||
|
NA0000 third.guardproof.info. 61.141.32.197
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# Known parent->child edges
|
|||
|
|
|||
|
PC0000 guardproof.info. third.guardproof.info.
|
|||
|
|
|||
|
*/
|
|||
|
|
|||
|
Process that with the 'dot' program, and you have your
|
|||
|
first network diagram.
|
|||
|
|
|||
|
In addition to the conventional graphic edges, the
|
|||
|
descriptor file includes similar format pair-data that
|
|||
|
describes services, zone records (sub-graphs?),
|
|||
|
blacklisted addresses, and other things which might be
|
|||
|
interesting to include in your graph. This additional
|
|||
|
information could be displayed as different node
|
|||
|
shapes, colors, line sizes, etc.
|
|||
|
|
|||
|
The descriptor file can also be read and edited by a
|
|||
|
Bash script (of course). You should be able to find
|
|||
|
most of the functions required within the
|
|||
|
"is_spammer.bash" script.
|
|||
|
|
|||
|
# End Quickstart.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Additional Note
|
|||
|
========== ====
|
|||
|
|
|||
|
Michael Zick points out that there is a "makeviz.bash" interactive
|
|||
|
Web site at rediris.es. Can't give the full URL, since this is not
|
|||
|
a publically accessible site.</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><P
|
|||
|
>Another anti-spam script.</P
|
|||
|
><P
|
|||
|
><A
|
|||
|
NAME="WHX0"
|
|||
|
></A
|
|||
|
></P
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="WHX"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-29. Spammer Hunt</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
# whx.sh: "whois" spammer lookup
|
|||
|
# Author: Walter Dnes
|
|||
|
# Slight revisions (first section) by ABS Guide author.
|
|||
|
# Used in ABS Guide with permission.
|
|||
|
|
|||
|
# Needs version 3.x or greater of Bash to run (because of =~ operator).
|
|||
|
# Commented by script author and ABS Guide author.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
E_BADARGS=85 # Missing command-line arg.
|
|||
|
E_NOHOST=86 # Host not found.
|
|||
|
E_TIMEOUT=87 # Host lookup timed out.
|
|||
|
E_UNDEF=88 # Some other (undefined) error.
|
|||
|
|
|||
|
HOSTWAIT=10 # Specify up to 10 seconds for host query reply.
|
|||
|
# The actual wait may be a bit longer.
|
|||
|
OUTFILE=whois.txt # Output file.
|
|||
|
PORT=4321
|
|||
|
|
|||
|
|
|||
|
if [ -z "$1" ] # Check for (required) command-line arg.
|
|||
|
then
|
|||
|
echo "Usage: $0 domain name or IP address"
|
|||
|
exit $E_BADARGS
|
|||
|
fi
|
|||
|
|
|||
|
|
|||
|
if [[ "$1" =~ [a-zA-Z][a-zA-Z]$ ]] # Ends in two alpha chars?
|
|||
|
then # It's a domain name &&
|
|||
|
#+ must do host lookup.
|
|||
|
IPADDR=$(host -W $HOSTWAIT $1 | awk '{print $4}')
|
|||
|
# Doing host lookup
|
|||
|
#+ to get IP address.
|
|||
|
# Extract final field.
|
|||
|
else
|
|||
|
IPADDR="$1" # Command-line arg was IP address.
|
|||
|
fi
|
|||
|
|
|||
|
echo; echo "IP Address is: "$IPADDR""; echo
|
|||
|
|
|||
|
if [ -e "$OUTFILE" ]
|
|||
|
then
|
|||
|
rm -f "$OUTFILE"
|
|||
|
echo "Stale output file \"$OUTFILE\" removed."; echo
|
|||
|
fi
|
|||
|
|
|||
|
|
|||
|
# Sanity checks.
|
|||
|
# (This section needs more work.)
|
|||
|
# ===============================
|
|||
|
if [ -z "$IPADDR" ]
|
|||
|
# No response.
|
|||
|
then
|
|||
|
echo "Host not found!"
|
|||
|
exit $E_NOHOST # Bail out.
|
|||
|
fi
|
|||
|
|
|||
|
if [[ "$IPADDR" =~ ^[;;] ]]
|
|||
|
# ;; Connection timed out; no servers could be reached.
|
|||
|
then
|
|||
|
echo "Host lookup timed out!"
|
|||
|
exit $E_TIMEOUT # Bail out.
|
|||
|
fi
|
|||
|
|
|||
|
if [[ "$IPADDR" =~ [(NXDOMAIN)]$ ]]
|
|||
|
# Host xxxxxxxxx.xxx not found: 3(NXDOMAIN)
|
|||
|
then
|
|||
|
echo "Host not found!"
|
|||
|
exit $E_NOHOST # Bail out.
|
|||
|
fi
|
|||
|
|
|||
|
if [[ "$IPADDR" =~ [(SERVFAIL)]$ ]]
|
|||
|
# Host xxxxxxxxx.xxx not found: 2(SERVFAIL)
|
|||
|
then
|
|||
|
echo "Host not found!"
|
|||
|
exit $E_NOHOST # Bail out.
|
|||
|
fi
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# ======================== Main body of script ========================
|
|||
|
|
|||
|
AFRINICquery() {
|
|||
|
# Define the function that queries AFRINIC. Echo a notification to the
|
|||
|
#+ screen, and then run the actual query, redirecting output to $OUTFILE.
|
|||
|
|
|||
|
echo "Searching for $IPADDR in whois.afrinic.net"
|
|||
|
whois -h whois.afrinic.net "$IPADDR" > $OUTFILE
|
|||
|
|
|||
|
# Check for presence of reference to an rwhois.
|
|||
|
# Warn about non-functional rwhois.infosat.net server
|
|||
|
#+ and attempt rwhois query.
|
|||
|
if grep -e "^remarks: .*rwhois\.[^ ]\+" "$OUTFILE"
|
|||
|
then
|
|||
|
echo " " >> $OUTFILE
|
|||
|
echo "***" >> $OUTFILE
|
|||
|
echo "***" >> $OUTFILE
|
|||
|
echo "Warning: rwhois.infosat.net was not working \
|
|||
|
as of 2005/02/02" >> $OUTFILE
|
|||
|
echo " when this script was written." >> $OUTFILE
|
|||
|
echo "***" >> $OUTFILE
|
|||
|
echo "***" >> $OUTFILE
|
|||
|
echo " " >> $OUTFILE
|
|||
|
RWHOIS=`grep "^remarks: .*rwhois\.[^ ]\+" "$OUTFILE" | tail -n 1 |\
|
|||
|
sed "s/\(^.*\)\(rwhois\..*\)\(:4.*\)/\2/"`
|
|||
|
whois -h ${RWHOIS}:${PORT} "$IPADDR" >> $OUTFILE
|
|||
|
fi
|
|||
|
}
|
|||
|
|
|||
|
APNICquery() {
|
|||
|
echo "Searching for $IPADDR in whois.apnic.net"
|
|||
|
whois -h whois.apnic.net "$IPADDR" > $OUTFILE
|
|||
|
|
|||
|
# Just about every country has its own internet registrar.
|
|||
|
# I don't normally bother consulting them, because the regional registry
|
|||
|
#+ usually supplies sufficient information.
|
|||
|
# There are a few exceptions, where the regional registry simply
|
|||
|
#+ refers to the national registry for direct data.
|
|||
|
# These are Japan and South Korea in APNIC, and Brasil in LACNIC.
|
|||
|
# The following if statement checks $OUTFILE (whois.txt) for the presence
|
|||
|
#+ of "KR" (South Korea) or "JP" (Japan) in the country field.
|
|||
|
# If either is found, the query is re-run against the appropriate
|
|||
|
#+ national registry.
|
|||
|
|
|||
|
if grep -E "^country:[ ]+KR$" "$OUTFILE"
|
|||
|
then
|
|||
|
echo "Searching for $IPADDR in whois.krnic.net"
|
|||
|
whois -h whois.krnic.net "$IPADDR" >> $OUTFILE
|
|||
|
elif grep -E "^country:[ ]+JP$" "$OUTFILE"
|
|||
|
then
|
|||
|
echo "Searching for $IPADDR in whois.nic.ad.jp"
|
|||
|
whois -h whois.nic.ad.jp "$IPADDR"/e >> $OUTFILE
|
|||
|
fi
|
|||
|
}
|
|||
|
|
|||
|
ARINquery() {
|
|||
|
echo "Searching for $IPADDR in whois.arin.net"
|
|||
|
whois -h whois.arin.net "$IPADDR" > $OUTFILE
|
|||
|
|
|||
|
# Several large internet providers listed by ARIN have their own
|
|||
|
#+ internal whois service, referred to as "rwhois".
|
|||
|
# A large block of IP addresses is listed with the provider
|
|||
|
#+ under the ARIN registry.
|
|||
|
# To get the IP addresses of 2nd-level ISPs or other large customers,
|
|||
|
#+ one has to refer to the rwhois server on port 4321.
|
|||
|
# I originally started with a bunch of "if" statements checking for
|
|||
|
#+ the larger providers.
|
|||
|
# This approach is unwieldy, and there's always another rwhois server
|
|||
|
#+ that I didn't know about.
|
|||
|
# A more elegant approach is to check $OUTFILE for a reference
|
|||
|
#+ to a whois server, parse that server name out of the comment section,
|
|||
|
#+ and re-run the query against the appropriate rwhois server.
|
|||
|
# The parsing looks a bit ugly, with a long continued line inside
|
|||
|
#+ backticks.
|
|||
|
# But it only has to be done once, and will work as new servers are added.
|
|||
|
#@ ABS Guide author comment: it isn't all that ugly, and is, in fact,
|
|||
|
#@+ an instructive use of Regular Expressions.
|
|||
|
|
|||
|
if grep -E "^Comment: .*rwhois.[^ ]+" "$OUTFILE"
|
|||
|
then
|
|||
|
RWHOIS=`grep -e "^Comment:.*rwhois\.[^ ]\+" "$OUTFILE" | tail -n 1 |\
|
|||
|
sed "s/^\(.*\)\(rwhois\.[^ ]\+\)\(.*$\)/\2/"`
|
|||
|
echo "Searching for $IPADDR in ${RWHOIS}"
|
|||
|
whois -h ${RWHOIS}:${PORT} "$IPADDR" >> $OUTFILE
|
|||
|
fi
|
|||
|
}
|
|||
|
|
|||
|
LACNICquery() {
|
|||
|
echo "Searching for $IPADDR in whois.lacnic.net"
|
|||
|
whois -h whois.lacnic.net "$IPADDR" > $OUTFILE
|
|||
|
|
|||
|
# The following if statement checks $OUTFILE (whois.txt) for
|
|||
|
#+ the presence of "BR" (Brasil) in the country field.
|
|||
|
# If it is found, the query is re-run against whois.registro.br.
|
|||
|
|
|||
|
if grep -E "^country:[ ]+BR$" "$OUTFILE"
|
|||
|
then
|
|||
|
echo "Searching for $IPADDR in whois.registro.br"
|
|||
|
whois -h whois.registro.br "$IPADDR" >> $OUTFILE
|
|||
|
fi
|
|||
|
}
|
|||
|
|
|||
|
RIPEquery() {
|
|||
|
echo "Searching for $IPADDR in whois.ripe.net"
|
|||
|
whois -h whois.ripe.net "$IPADDR" > $OUTFILE
|
|||
|
}
|
|||
|
|
|||
|
# Initialize a few variables.
|
|||
|
# * slash8 is the most significant octet
|
|||
|
# * slash16 consists of the two most significant octets
|
|||
|
# * octet2 is the second most significant octet
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
slash8=`echo $IPADDR | cut -d. -f 1`
|
|||
|
if [ -z "$slash8" ] # Yet another sanity check.
|
|||
|
then
|
|||
|
echo "Undefined error!"
|
|||
|
exit $E_UNDEF
|
|||
|
fi
|
|||
|
slash16=`echo $IPADDR | cut -d. -f 1-2`
|
|||
|
# ^ Period specified as 'cut" delimiter.
|
|||
|
if [ -z "$slash16" ]
|
|||
|
then
|
|||
|
echo "Undefined error!"
|
|||
|
exit $E_UNDEF
|
|||
|
fi
|
|||
|
octet2=`echo $slash16 | cut -d. -f 2`
|
|||
|
if [ -z "$octet2" ]
|
|||
|
then
|
|||
|
echo "Undefined error!"
|
|||
|
exit $E_UNDEF
|
|||
|
fi
|
|||
|
|
|||
|
|
|||
|
# Check for various odds and ends of reserved space.
|
|||
|
# There is no point in querying for those addresses.
|
|||
|
|
|||
|
if [ $slash8 == 0 ]; then
|
|||
|
echo $IPADDR is '"This Network"' space\; Not querying
|
|||
|
elif [ $slash8 == 10 ]; then
|
|||
|
echo $IPADDR is RFC1918 space\; Not querying
|
|||
|
elif [ $slash8 == 14 ]; then
|
|||
|
echo $IPADDR is '"Public Data Network"' space\; Not querying
|
|||
|
elif [ $slash8 == 127 ]; then
|
|||
|
echo $IPADDR is loopback space\; Not querying
|
|||
|
elif [ $slash16 == 169.254 ]; then
|
|||
|
echo $IPADDR is link-local space\; Not querying
|
|||
|
elif [ $slash8 == 172 ] && [ $octet2 -ge 16 ] && [ $octet2 -le 31 ];then
|
|||
|
echo $IPADDR is RFC1918 space\; Not querying
|
|||
|
elif [ $slash16 == 192.168 ]; then
|
|||
|
echo $IPADDR is RFC1918 space\; Not querying
|
|||
|
elif [ $slash8 -ge 224 ]; then
|
|||
|
echo $IPADDR is either Multicast or reserved space\; Not querying
|
|||
|
elif [ $slash8 -ge 200 ] && [ $slash8 -le 201 ]; then LACNICquery "$IPADDR"
|
|||
|
elif [ $slash8 -ge 202 ] && [ $slash8 -le 203 ]; then APNICquery "$IPADDR"
|
|||
|
elif [ $slash8 -ge 210 ] && [ $slash8 -le 211 ]; then APNICquery "$IPADDR"
|
|||
|
elif [ $slash8 -ge 218 ] && [ $slash8 -le 223 ]; then APNICquery "$IPADDR"
|
|||
|
|
|||
|
# If we got this far without making a decision, query ARIN.
|
|||
|
# If a reference is found in $OUTFILE to APNIC, AFRINIC, LACNIC, or RIPE,
|
|||
|
#+ query the appropriate whois server.
|
|||
|
|
|||
|
else
|
|||
|
ARINquery "$IPADDR"
|
|||
|
if grep "whois.afrinic.net" "$OUTFILE"; then
|
|||
|
AFRINICquery "$IPADDR"
|
|||
|
elif grep -E "^OrgID:[ ]+RIPE$" "$OUTFILE"; then
|
|||
|
RIPEquery "$IPADDR"
|
|||
|
elif grep -E "^OrgID:[ ]+APNIC$" "$OUTFILE"; then
|
|||
|
APNICquery "$IPADDR"
|
|||
|
elif grep -E "^OrgID:[ ]+LACNIC$" "$OUTFILE"; then
|
|||
|
LACNICquery "$IPADDR"
|
|||
|
fi
|
|||
|
fi
|
|||
|
|
|||
|
#@ ---------------------------------------------------------------
|
|||
|
# Try also:
|
|||
|
# wget http://logi.cc/nw/whois.php3?ACTION=doQuery&DOMAIN=$IPADDR
|
|||
|
#@ ---------------------------------------------------------------
|
|||
|
|
|||
|
# We've now finished the querying.
|
|||
|
# Echo a copy of the final result to the screen.
|
|||
|
|
|||
|
cat $OUTFILE
|
|||
|
# Or "less $OUTFILE" . . .
|
|||
|
|
|||
|
|
|||
|
exit 0
|
|||
|
|
|||
|
#@ ABS Guide author comments:
|
|||
|
#@ Nothing fancy here, but still a very useful tool for hunting spammers.
|
|||
|
#@ Sure, the script can be cleaned up some, and it's still a bit buggy,
|
|||
|
#@+ (exercise for reader), but all the same, it's a nice piece of coding
|
|||
|
#@+ by Walter Dnes.
|
|||
|
#@ Thank you!</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><P
|
|||
|
><SPAN
|
|||
|
CLASS="QUOTE"
|
|||
|
>"Little Monster's"</SPAN
|
|||
|
> front end to <A
|
|||
|
HREF="communications.html#WGETREF"
|
|||
|
>wget</A
|
|||
|
>.</P
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="WGETTER2"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-30. Making <I
|
|||
|
CLASS="FIRSTTERM"
|
|||
|
>wget</I
|
|||
|
> easier to use</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
# wgetter2.bash
|
|||
|
|
|||
|
# Author: Little Monster [monster@monstruum.co.uk]
|
|||
|
# ==> Used in ABS Guide with permission of script author.
|
|||
|
# ==> This script still needs debugging and fixups (exercise for reader).
|
|||
|
# ==> It could also use some additional editing in the comments.
|
|||
|
|
|||
|
|
|||
|
# This is wgetter2 --
|
|||
|
#+ a Bash script to make wget a bit more friendly, and save typing.
|
|||
|
|
|||
|
# Carefully crafted by Little Monster.
|
|||
|
# More or less complete on 02/02/2005.
|
|||
|
# If you think this script can be improved,
|
|||
|
#+ email me at: monster@monstruum.co.uk
|
|||
|
# ==> and cc: to the author of the ABS Guide, please.
|
|||
|
# This script is licenced under the GPL.
|
|||
|
# You are free to copy, alter and re-use it,
|
|||
|
#+ but please don't try to claim you wrote it.
|
|||
|
# Log your changes here instead.
|
|||
|
|
|||
|
# =======================================================================
|
|||
|
# changelog:
|
|||
|
|
|||
|
# 07/02/2005. Fixups by Little Monster.
|
|||
|
# 02/02/2005. Minor additions by Little Monster.
|
|||
|
# (See after # +++++++++++ )
|
|||
|
# 29/01/2005. Minor stylistic edits and cleanups by author of ABS Guide.
|
|||
|
# Added exit error codes.
|
|||
|
# 22/11/2004. Finished initial version of second version of wgetter:
|
|||
|
# wgetter2 is born.
|
|||
|
# 01/12/2004. Changed 'runn' function so it can be run 2 ways --
|
|||
|
# either ask for a file name or have one input on the CL.
|
|||
|
# 01/12/2004. Made sensible handling of no URL's given.
|
|||
|
# 01/12/2004. Made loop of main options, so you don't
|
|||
|
# have to keep calling wgetter 2 all the time.
|
|||
|
# Runs as a session instead.
|
|||
|
# 01/12/2004. Added looping to 'runn' function.
|
|||
|
# Simplified and improved.
|
|||
|
# 01/12/2004. Added state to recursion setting.
|
|||
|
# Enables re-use of previous value.
|
|||
|
# 05/12/2004. Modified the file detection routine in the 'runn' function
|
|||
|
# so it's not fooled by empty values, and is cleaner.
|
|||
|
# 01/02/2004. Added cookie finding routine from later version (which
|
|||
|
# isn't ready yet), so as not to have hard-coded paths.
|
|||
|
# =======================================================================
|
|||
|
|
|||
|
# Error codes for abnormal exit.
|
|||
|
E_USAGE=67 # Usage message, then quit.
|
|||
|
E_NO_OPTS=68 # No command-line args entered.
|
|||
|
E_NO_URLS=69 # No URLs passed to script.
|
|||
|
E_NO_SAVEFILE=70 # No save filename passed to script.
|
|||
|
E_USER_EXIT=71 # User decides to quit.
|
|||
|
|
|||
|
|
|||
|
# Basic default wget command we want to use.
|
|||
|
# This is the place to change it, if required.
|
|||
|
# NB: if using a proxy, set http_proxy = yourproxy in .wgetrc.
|
|||
|
# Otherwise delete --proxy=on, below.
|
|||
|
# ====================================================================
|
|||
|
CommandA="wget -nc -c -t 5 --progress=bar --random-wait --proxy=on -r"
|
|||
|
# ====================================================================
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# --------------------------------------------------------------------
|
|||
|
# Set some other variables and explain them.
|
|||
|
|
|||
|
pattern=" -A .jpg,.JPG,.jpeg,.JPEG,.gif,.GIF,.htm,.html,.shtml,.php"
|
|||
|
# wget's option to only get certain types of file.
|
|||
|
# comment out if not using
|
|||
|
today=`date +%F` # Used for a filename.
|
|||
|
home=$HOME # Set HOME to an internal variable.
|
|||
|
# In case some other path is used, change it here.
|
|||
|
depthDefault=3 # Set a sensible default recursion.
|
|||
|
Depth=$depthDefault # Otherwise user feedback doesn't tie in properly.
|
|||
|
RefA="" # Set blank referring page.
|
|||
|
Flag="" # Default to not saving anything,
|
|||
|
#+ or whatever else might be wanted in future.
|
|||
|
lister="" # Used for passing a list of urls directly to wget.
|
|||
|
Woptions="" # Used for passing wget some options for itself.
|
|||
|
inFile="" # Used for the run function.
|
|||
|
newFile="" # Used for the run function.
|
|||
|
savePath="$home/w-save"
|
|||
|
Config="$home/.wgetter2rc"
|
|||
|
# This is where some variables can be stored,
|
|||
|
#+ if permanently changed from within the script.
|
|||
|
Cookie_List="$home/.cookielist"
|
|||
|
# So we know where the cookies are kept . . .
|
|||
|
cFlag="" # Part of the cookie file selection routine.
|
|||
|
|
|||
|
# Define the options available. Easy to change letters here if needed.
|
|||
|
# These are the optional options; you don't just wait to be asked.
|
|||
|
|
|||
|
save=s # Save command instead of executing it.
|
|||
|
cook=c # Change cookie file for this session.
|
|||
|
help=h # Usage guide.
|
|||
|
list=l # Pass wget the -i option and URL list.
|
|||
|
runn=r # Run saved commands as an argument to the option.
|
|||
|
inpu=i # Run saved commands interactively.
|
|||
|
wopt=w # Allow to enter options to pass directly to wget.
|
|||
|
# --------------------------------------------------------------------
|
|||
|
|
|||
|
|
|||
|
if [ -z "$1" ]; then # Make sure we get something for wget to eat.
|
|||
|
echo "You must at least enter a URL or option!"
|
|||
|
echo "-$help for usage."
|
|||
|
exit $E_NO_OPTS
|
|||
|
fi
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|||
|
# added added added added added added added added added added added added
|
|||
|
|
|||
|
if [ ! -e "$Config" ]; then # See if configuration file exists.
|
|||
|
echo "Creating configuration file, $Config"
|
|||
|
echo "# This is the configuration file for wgetter2" > "$Config"
|
|||
|
echo "# Your customised settings will be saved in this file" >> "$Config"
|
|||
|
else
|
|||
|
source $Config # Import variables we set outside the script.
|
|||
|
fi
|
|||
|
|
|||
|
if [ ! -e "$Cookie_List" ]; then
|
|||
|
# Set up a list of cookie files, if there isn't one.
|
|||
|
echo "Hunting for cookies . . ."
|
|||
|
find -name cookies.txt >> $Cookie_List # Create the list of cookie files.
|
|||
|
fi # Isolate this in its own 'if' statement,
|
|||
|
#+ in case we got interrupted while searching.
|
|||
|
|
|||
|
if [ -z "$cFlag" ]; then # If we haven't already done this . . .
|
|||
|
echo # Make a nice space after the command prompt.
|
|||
|
echo "Looks like you haven't set up your source of cookies yet."
|
|||
|
n=0 # Make sure the counter
|
|||
|
#+ doesn't contain random values.
|
|||
|
while read; do
|
|||
|
Cookies[$n]=$REPLY # Put the cookie files we found into an array.
|
|||
|
echo "$n) ${Cookies[$n]}" # Create a menu.
|
|||
|
n=$(( n + 1 )) # Increment the counter.
|
|||
|
done < $Cookie_List # Feed the read statement.
|
|||
|
echo "Enter the number of the cookie file you want to use."
|
|||
|
echo "If you won't be using cookies, just press RETURN."
|
|||
|
echo
|
|||
|
echo "I won't be asking this again. Edit $Config"
|
|||
|
echo "If you decide to change at a later date"
|
|||
|
echo "or use the -${cook} option for per session changes."
|
|||
|
read
|
|||
|
if [ ! -z $REPLY ]; then # User didn't just press return.
|
|||
|
Cookie=" --load-cookies ${Cookies[$REPLY]}"
|
|||
|
# Set the variable here as well as in the config file.
|
|||
|
|
|||
|
echo "Cookie=\" --load-cookies ${Cookies[$REPLY]}\"" >> $Config
|
|||
|
fi
|
|||
|
echo "cFlag=1" >> $Config # So we know not to ask again.
|
|||
|
fi
|
|||
|
|
|||
|
# end added section end added section end added section end added section
|
|||
|
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# Another variable.
|
|||
|
# This one may or may not be subject to variation.
|
|||
|
# A bit like the small print.
|
|||
|
CookiesON=$Cookie
|
|||
|
# echo "cookie file is $CookiesON" # For debugging.
|
|||
|
# echo "home is ${home}" # For debugging.
|
|||
|
# Got caught with this one!
|
|||
|
|
|||
|
|
|||
|
wopts()
|
|||
|
{
|
|||
|
echo "Enter options to pass to wget."
|
|||
|
echo "It is assumed you know what you're doing."
|
|||
|
echo
|
|||
|
echo "You can pass their arguments here too."
|
|||
|
# That is to say, everything passed here is passed to wget.
|
|||
|
|
|||
|
read Wopts
|
|||
|
# Read in the options to be passed to wget.
|
|||
|
|
|||
|
Woptions=" $Wopts"
|
|||
|
# ^ Why the leading space?
|
|||
|
# Assign to another variable.
|
|||
|
# Just for fun, or something . . .
|
|||
|
|
|||
|
echo "passing options ${Wopts} to wget"
|
|||
|
# Mainly for debugging.
|
|||
|
# Is cute.
|
|||
|
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
save_func()
|
|||
|
{
|
|||
|
echo "Settings will be saved."
|
|||
|
if [ ! -d $savePath ]; then # See if directory exists.
|
|||
|
mkdir $savePath # Create the directory to save things in
|
|||
|
#+ if it isn't already there.
|
|||
|
fi
|
|||
|
|
|||
|
Flag=S
|
|||
|
# Tell the final bit of code what to do.
|
|||
|
# Set a flag since stuff is done in main.
|
|||
|
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
usage() # Tell them how it works.
|
|||
|
{
|
|||
|
echo "Welcome to wgetter. This is a front end to wget."
|
|||
|
echo "It will always run wget with these options:"
|
|||
|
echo "$CommandA"
|
|||
|
echo "and the pattern to match: $pattern \
|
|||
|
(which you can change at the top of this script)."
|
|||
|
echo "It will also ask you for recursion depth, \
|
|||
|
and if you want to use a referring page."
|
|||
|
echo "Wgetter accepts the following options:"
|
|||
|
echo ""
|
|||
|
echo "-$help : Display this help."
|
|||
|
echo "-$save : Save the command to a file $savePath/wget-($today) \
|
|||
|
instead of running it."
|
|||
|
echo "-$runn : Run saved wget commands instead of starting a new one -"
|
|||
|
echo "Enter filename as argument to this option."
|
|||
|
echo "-$inpu : Run saved wget commands interactively --"
|
|||
|
echo "The script will ask you for the filename."
|
|||
|
echo "-$cook : Change the cookies file for this session."
|
|||
|
echo "-$list : Tell wget to use URL's from a list instead of \
|
|||
|
from the command-line."
|
|||
|
echo "-$wopt : Pass any other options direct to wget."
|
|||
|
echo ""
|
|||
|
echo "See the wget man page for additional options \
|
|||
|
you can pass to wget."
|
|||
|
echo ""
|
|||
|
|
|||
|
exit $E_USAGE # End here. Don't process anything else.
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
list_func() # Gives the user the option to use the -i option to wget,
|
|||
|
#+ and a list of URLs.
|
|||
|
{
|
|||
|
while [ 1 ]; do
|
|||
|
echo "Enter the name of the file containing URL's (press q to change
|
|||
|
your mind)."
|
|||
|
read urlfile
|
|||
|
if [ ! -e "$urlfile" ] && [ "$urlfile" != q ]; then
|
|||
|
# Look for a file, or the quit option.
|
|||
|
echo "That file does not exist!"
|
|||
|
elif [ "$urlfile" = q ]; then # Check quit option.
|
|||
|
echo "Not using a url list."
|
|||
|
return
|
|||
|
else
|
|||
|
echo "using $urlfile."
|
|||
|
echo "If you gave url's on the command-line, I'll use those first."
|
|||
|
# Report wget standard behaviour to the user.
|
|||
|
lister=" -i $urlfile" # This is what we want to pass to wget.
|
|||
|
return
|
|||
|
fi
|
|||
|
done
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
cookie_func() # Give the user the option to use a different cookie file.
|
|||
|
{
|
|||
|
while [ 1 ]; do
|
|||
|
echo "Change the cookies file. Press return if you don't want to change
|
|||
|
it."
|
|||
|
read Cookies
|
|||
|
# NB: this is not the same as Cookie, earlier.
|
|||
|
# There is an 's' on the end.
|
|||
|
# Bit like chocolate chips.
|
|||
|
if [ -z "$Cookies" ]; then # Escape clause for wusses.
|
|||
|
return
|
|||
|
elif [ ! -e "$Cookies" ]; then
|
|||
|
echo "File does not exist. Try again." # Keep em going . . .
|
|||
|
else
|
|||
|
CookiesON=" --load-cookies $Cookies" # File is good -- use it!
|
|||
|
return
|
|||
|
fi
|
|||
|
done
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
run_func()
|
|||
|
{
|
|||
|
if [ -z "$OPTARG" ]; then
|
|||
|
# Test to see if we used the in-line option or the query one.
|
|||
|
if [ ! -d "$savePath" ]; then # If directory doesn't exist . . .
|
|||
|
echo "$savePath does not appear to exist."
|
|||
|
echo "Please supply path and filename of saved wget commands:"
|
|||
|
read newFile
|
|||
|
until [ -f "$newFile" ]; do # Keep going till we get something.
|
|||
|
echo "Sorry, that file does not exist. Please try again."
|
|||
|
# Try really hard to get something.
|
|||
|
read newFile
|
|||
|
done
|
|||
|
|
|||
|
|
|||
|
# -----------------------------------------------------------------------
|
|||
|
# if [ -z ( grep wget ${newfile} ) ]; then
|
|||
|
# Assume they haven't got the right file and bail out.
|
|||
|
# echo "Sorry, that file does not contain wget commands. Aborting."
|
|||
|
# exit
|
|||
|
# fi
|
|||
|
#
|
|||
|
# This is bogus code.
|
|||
|
# It doesn't actually work.
|
|||
|
# If anyone wants to fix it, feel free!
|
|||
|
# -----------------------------------------------------------------------
|
|||
|
|
|||
|
|
|||
|
filePath="${newFile}"
|
|||
|
else
|
|||
|
echo "Save path is $savePath"
|
|||
|
echo "Please enter name of the file which you want to use."
|
|||
|
echo "You have a choice of:"
|
|||
|
ls $savePath # Give them a choice.
|
|||
|
read inFile
|
|||
|
until [ -f "$savePath/$inFile" ]; do # Keep going till
|
|||
|
#+ we get something.
|
|||
|
if [ ! -f "${savePath}/${inFile}" ]; then # If file doesn't exist.
|
|||
|
echo "Sorry, that file does not exist. Please choose from:"
|
|||
|
ls $savePath # If a mistake is made.
|
|||
|
read inFile
|
|||
|
fi
|
|||
|
done
|
|||
|
filePath="${savePath}/${inFile}" # Make one variable . . .
|
|||
|
fi
|
|||
|
else filePath="${savePath}/${OPTARG}" # Which can be many things . . .
|
|||
|
fi
|
|||
|
|
|||
|
if [ ! -f "$filePath" ]; then # If a bogus file got through.
|
|||
|
echo "You did not specify a suitable file."
|
|||
|
echo "Run this script with the -${save} option first."
|
|||
|
echo "Aborting."
|
|||
|
exit $E_NO_SAVEFILE
|
|||
|
fi
|
|||
|
echo "Using: $filePath"
|
|||
|
while read; do
|
|||
|
eval $REPLY
|
|||
|
echo "Completed: $REPLY"
|
|||
|
done < $filePath # Feed the actual file we are using into a 'while' loop.
|
|||
|
|
|||
|
exit
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# Fish out any options we are using for the script.
|
|||
|
# This is based on the demo in "Learning The Bash Shell" (O'Reilly).
|
|||
|
while getopts ":$save$cook$help$list$runn:$inpu$wopt" opt
|
|||
|
do
|
|||
|
case $opt in
|
|||
|
$save) save_func;; # Save some wgetter sessions for later.
|
|||
|
$cook) cookie_func;; # Change cookie file.
|
|||
|
$help) usage;; # Get help.
|
|||
|
$list) list_func;; # Allow wget to use a list of URLs.
|
|||
|
$runn) run_func;; # Useful if you are calling wgetter from,
|
|||
|
#+ for example, a cron script.
|
|||
|
$inpu) run_func;; # When you don't know what your files are named.
|
|||
|
$wopt) wopts;; # Pass options directly to wget.
|
|||
|
\?) echo "Not a valid option."
|
|||
|
echo "Use -${wopt} to pass options directly to wget,"
|
|||
|
echo "or -${help} for help";; # Catch anything else.
|
|||
|
esac
|
|||
|
done
|
|||
|
shift $((OPTIND - 1)) # Do funky magic stuff with $#.
|
|||
|
|
|||
|
|
|||
|
if [ -z "$1" ] && [ -z "$lister" ]; then
|
|||
|
# We should be left with at least one URL
|
|||
|
#+ on the command-line, unless a list is
|
|||
|
#+ being used -- catch empty CL's.
|
|||
|
echo "No URL's given! You must enter them on the same line as wgetter2."
|
|||
|
echo "E.g., wgetter2 http://somesite http://anothersite."
|
|||
|
echo "Use $help option for more information."
|
|||
|
exit $E_NO_URLS # Bail out, with appropriate error code.
|
|||
|
fi
|
|||
|
|
|||
|
URLS=" $@"
|
|||
|
# Use this so that URL list can be changed if we stay in the option loop.
|
|||
|
|
|||
|
while [ 1 ]; do
|
|||
|
# This is where we ask for the most used options.
|
|||
|
# (Mostly unchanged from version 1 of wgetter)
|
|||
|
if [ -z $curDepth ]; then
|
|||
|
Current=""
|
|||
|
else Current=" Current value is $curDepth"
|
|||
|
fi
|
|||
|
echo "How deep should I go? \
|
|||
|
(integer: Default is $depthDefault.$Current)"
|
|||
|
read Depth # Recursion -- how far should we go?
|
|||
|
inputB="" # Reset this to blank on each pass of the loop.
|
|||
|
echo "Enter the name of the referring page (default is none)."
|
|||
|
read inputB # Need this for some sites.
|
|||
|
|
|||
|
echo "Do you want to have the output logged to the terminal"
|
|||
|
echo "(y/n, default is yes)?"
|
|||
|
read noHide # Otherwise wget will just log it to a file.
|
|||
|
|
|||
|
case $noHide in # Now you see me, now you don't.
|
|||
|
y|Y ) hide="";;
|
|||
|
n|N ) hide=" -b";;
|
|||
|
* ) hide="";;
|
|||
|
esac
|
|||
|
|
|||
|
if [ -z ${Depth} ]; then
|
|||
|
# User accepted either default or current depth,
|
|||
|
#+ in which case Depth is now empty.
|
|||
|
if [ -z ${curDepth} ]; then
|
|||
|
# See if a depth was set on a previous iteration.
|
|||
|
Depth="$depthDefault"
|
|||
|
# Set the default recursion depth if nothing
|
|||
|
#+ else to use.
|
|||
|
else Depth="$curDepth" # Otherwise, set the one we used before.
|
|||
|
fi
|
|||
|
fi
|
|||
|
Recurse=" -l $Depth" # Set how deep we want to go.
|
|||
|
curDepth=$Depth # Remember setting for next time.
|
|||
|
|
|||
|
if [ ! -z $inputB ]; then
|
|||
|
RefA=" --referer=$inputB" # Option to use referring page.
|
|||
|
fi
|
|||
|
|
|||
|
WGETTER="${CommandA}${pattern}${hide}${RefA}${Recurse}\
|
|||
|
${CookiesON}${lister}${Woptions}${URLS}"
|
|||
|
# Just string the whole lot together . . .
|
|||
|
# NB: no embedded spaces.
|
|||
|
# They are in the individual elements so that if any are empty,
|
|||
|
#+ we don't get an extra space.
|
|||
|
|
|||
|
if [ -z "${CookiesON}" ] && [ "$cFlag" = "1" ] ; then
|
|||
|
echo "Warning -- can't find cookie file"
|
|||
|
# This should be changed,
|
|||
|
#+ in case the user has opted to not use cookies.
|
|||
|
fi
|
|||
|
|
|||
|
if [ "$Flag" = "S" ]; then
|
|||
|
echo "$WGETTER" >> $savePath/wget-${today}
|
|||
|
# Create a unique filename for today, or append to it if it exists.
|
|||
|
echo "$inputB" >> $savePath/site-list-${today}
|
|||
|
# Make a list, so it's easy to refer back to,
|
|||
|
#+ since the whole command is a bit confusing to look at.
|
|||
|
echo "Command saved to the file $savePath/wget-${today}"
|
|||
|
# Tell the user.
|
|||
|
echo "Referring page URL saved to the file$ \
|
|||
|
savePath/site-list-${today}"
|
|||
|
# Tell the user.
|
|||
|
Saver=" with save option"
|
|||
|
# Stick this somewhere, so it appears in the loop if set.
|
|||
|
else
|
|||
|
echo "*****************"
|
|||
|
echo "*****Getting*****"
|
|||
|
echo "*****************"
|
|||
|
echo ""
|
|||
|
echo "$WGETTER"
|
|||
|
echo ""
|
|||
|
echo "*****************"
|
|||
|
eval "$WGETTER"
|
|||
|
fi
|
|||
|
|
|||
|
echo ""
|
|||
|
echo "Starting over$Saver."
|
|||
|
echo "If you want to stop, press q."
|
|||
|
echo "Otherwise, enter some URL's:"
|
|||
|
# Let them go again. Tell about save option being set.
|
|||
|
|
|||
|
read
|
|||
|
case $REPLY in
|
|||
|
# Need to change this to a 'trap' clause.
|
|||
|
q|Q ) exit $E_USER_EXIT;; # Exercise for the reader?
|
|||
|
* ) URLS=" $REPLY";;
|
|||
|
esac
|
|||
|
|
|||
|
echo ""
|
|||
|
done
|
|||
|
|
|||
|
|
|||
|
exit 0</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="BASHPODDER"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-31. A <I
|
|||
|
CLASS="FIRSTTERM"
|
|||
|
>podcasting</I
|
|||
|
> script</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
|
|||
|
# bashpodder.sh:
|
|||
|
# By Linc 10/1/2004
|
|||
|
# Find the latest script at
|
|||
|
#+ http://linc.homeunix.org:8080/scripts/bashpodder
|
|||
|
# Last revision 12/14/2004 - Many Contributors!
|
|||
|
# If you use this and have made improvements or have comments
|
|||
|
#+ drop me an email at linc dot fessenden at gmail dot com
|
|||
|
# I'd appreciate it!
|
|||
|
|
|||
|
# ==> ABS Guide extra comments.
|
|||
|
|
|||
|
# ==> Author of this script has kindly granted permission
|
|||
|
# ==>+ for inclusion in ABS Guide.
|
|||
|
|
|||
|
|
|||
|
# ==> ################################################################
|
|||
|
#
|
|||
|
# ==> What is "podcasting"?
|
|||
|
|
|||
|
# ==> It's broadcasting "radio shows" over the Internet.
|
|||
|
# ==> These shows can be played on iPods and other music file players.
|
|||
|
|
|||
|
# ==> This script makes it possible.
|
|||
|
# ==> See documentation at the script author's site, above.
|
|||
|
|
|||
|
# ==> ################################################################
|
|||
|
|
|||
|
|
|||
|
# Make script crontab friendly:
|
|||
|
cd $(dirname $0)
|
|||
|
# ==> Change to directory where this script lives.
|
|||
|
|
|||
|
# datadir is the directory you want podcasts saved to:
|
|||
|
datadir=$(date +%Y-%m-%d)
|
|||
|
# ==> Will create a date-labeled directory, named: YYYY-MM-DD
|
|||
|
|
|||
|
# Check for and create datadir if necessary:
|
|||
|
if test ! -d $datadir
|
|||
|
then
|
|||
|
mkdir $datadir
|
|||
|
fi
|
|||
|
|
|||
|
# Delete any temp file:
|
|||
|
rm -f temp.log
|
|||
|
|
|||
|
# Read the bp.conf file and wget any url not already
|
|||
|
#+ in the podcast.log file:
|
|||
|
while read podcast
|
|||
|
do # ==> Main action follows.
|
|||
|
file=$(wget -q $podcast -O - | tr '\r' '\n' | tr \' \" | \
|
|||
|
sed -n 's/.*url="\([^"]*\)".*/\1/p')
|
|||
|
for url in $file
|
|||
|
do
|
|||
|
echo $url >> temp.log
|
|||
|
if ! grep "$url" podcast.log > /dev/null
|
|||
|
then
|
|||
|
wget -q -P $datadir "$url"
|
|||
|
fi
|
|||
|
done
|
|||
|
done < bp.conf
|
|||
|
|
|||
|
# Move dynamically created log file to permanent log file:
|
|||
|
cat podcast.log >> temp.log
|
|||
|
sort temp.log | uniq > podcast.log
|
|||
|
rm temp.log
|
|||
|
# Create an m3u playlist:
|
|||
|
ls $datadir | grep -v m3u > $datadir/podcast.m3u
|
|||
|
|
|||
|
|
|||
|
exit 0
|
|||
|
|
|||
|
#################################################
|
|||
|
For a different scripting approach to Podcasting,
|
|||
|
see Phil Salkie's article,
|
|||
|
"Internet Radio to Podcast with Shell Tools"
|
|||
|
in the September, 2005 issue of LINUX JOURNAL,
|
|||
|
http://www.linuxjournal.com/article/8171
|
|||
|
#################################################</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="NIGHTLYBACKUP"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-32. Nightly backup to a firewire HD</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
# nightly-backup.sh
|
|||
|
# http://www.richardneill.org/source.php#nightly-backup-rsync
|
|||
|
# Copyright (c) 2005 Richard Neill <backup@richardneill.org>.
|
|||
|
# This is Free Software licensed under the GNU GPL.
|
|||
|
# ==> Included in ABS Guide with script author's kind permission.
|
|||
|
# ==> (Thanks!)
|
|||
|
|
|||
|
# This does a backup from the host computer to a locally connected
|
|||
|
#+ firewire HDD using rsync and ssh.
|
|||
|
# (Script should work with USB-connected device (see lines 40-43).
|
|||
|
# It then rotates the backups.
|
|||
|
# Run it via cron every night at 5am.
|
|||
|
# This only backs up the home directory.
|
|||
|
# If ownerships (other than the user's) should be preserved,
|
|||
|
#+ then run the rsync process as root (and re-instate the -o).
|
|||
|
# We save every day for 7 days, then every week for 4 weeks,
|
|||
|
#+ then every month for 3 months.
|
|||
|
|
|||
|
# See: http://www.mikerubel.org/computers/rsync_snapshots/
|
|||
|
#+ for more explanation of the theory.
|
|||
|
# Save as: $HOME/bin/nightly-backup_firewire-hdd.sh
|
|||
|
|
|||
|
# Known bugs:
|
|||
|
# ----------
|
|||
|
# i) Ideally, we want to exclude ~/.tmp and the browser caches.
|
|||
|
|
|||
|
# ii) If the user is sitting at the computer at 5am,
|
|||
|
#+ and files are modified while the rsync is occurring,
|
|||
|
#+ then the BACKUP_JUSTINCASE branch gets triggered.
|
|||
|
# To some extent, this is a
|
|||
|
#+ feature, but it also causes a "disk-space leak".
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
##### BEGIN CONFIGURATION SECTION ############################################
|
|||
|
LOCAL_USER=rjn # User whose home directory should be backed up.
|
|||
|
MOUNT_POINT=/backup # Mountpoint of backup drive.
|
|||
|
# NO trailing slash!
|
|||
|
# This must be unique (eg using a udev symlink)
|
|||
|
# MOUNT_POINT=/media/disk # For USB-connected device.
|
|||
|
SOURCE_DIR=/home/$LOCAL_USER # NO trailing slash - it DOES matter to rsync.
|
|||
|
BACKUP_DEST_DIR=$MOUNT_POINT/backup/`hostname -s`.${LOCAL_USER}.nightly_backup
|
|||
|
DRY_RUN=false #If true, invoke rsync with -n, to do a dry run.
|
|||
|
# Comment out or set to false for normal use.
|
|||
|
VERBOSE=false # If true, make rsync verbose.
|
|||
|
# Comment out or set to false otherwise.
|
|||
|
COMPRESS=false # If true, compress.
|
|||
|
# Good for internet, bad on LAN.
|
|||
|
# Comment out or set to false otherwise.
|
|||
|
|
|||
|
### Exit Codes ###
|
|||
|
E_VARS_NOT_SET=64
|
|||
|
E_COMMANDLINE=65
|
|||
|
E_MOUNT_FAIL=70
|
|||
|
E_NOSOURCEDIR=71
|
|||
|
E_UNMOUNTED=72
|
|||
|
E_BACKUP=73
|
|||
|
##### END CONFIGURATION SECTION ##############################################
|
|||
|
|
|||
|
|
|||
|
# Check that all the important variables have been set:
|
|||
|
if [ -z "$LOCAL_USER" ] ||
|
|||
|
[ -z "$SOURCE_DIR" ] ||
|
|||
|
[ -z "$MOUNT_POINT" ] ||
|
|||
|
[ -z "$BACKUP_DEST_DIR" ]
|
|||
|
then
|
|||
|
echo 'One of the variables is not set! Edit the file: $0. BACKUP FAILED.'
|
|||
|
exit $E_VARS_NOT_SET
|
|||
|
fi
|
|||
|
|
|||
|
if [ "$#" != 0 ] # If command-line param(s) . . .
|
|||
|
then # Here document(ation).
|
|||
|
cat <<-ENDOFTEXT
|
|||
|
Automatic Nightly backup run from cron.
|
|||
|
Read the source for more details: $0
|
|||
|
The backup directory is $BACKUP_DEST_DIR .
|
|||
|
It will be created if necessary; initialisation is no longer required.
|
|||
|
|
|||
|
WARNING: Contents of $BACKUP_DEST_DIR are rotated.
|
|||
|
Directories named 'backup.\$i' will eventually be DELETED.
|
|||
|
We keep backups from every day for 7 days (1-8),
|
|||
|
then every week for 4 weeks (9-12),
|
|||
|
then every month for 3 months (13-15).
|
|||
|
|
|||
|
You may wish to add this to your crontab using 'crontab -e'
|
|||
|
# Back up files: $SOURCE_DIR to $BACKUP_DEST_DIR
|
|||
|
#+ every night at 3:15 am
|
|||
|
15 03 * * * /home/$LOCAL_USER/bin/nightly-backup_firewire-hdd.sh
|
|||
|
|
|||
|
Don't forget to verify the backups are working,
|
|||
|
especially if you don't read cron's mail!"
|
|||
|
ENDOFTEXT
|
|||
|
exit $E_COMMANDLINE
|
|||
|
fi
|
|||
|
|
|||
|
|
|||
|
# Parse the options.
|
|||
|
# ==================
|
|||
|
|
|||
|
if [ "$DRY_RUN" == "true" ]; then
|
|||
|
DRY_RUN="-n"
|
|||
|
echo "WARNING:"
|
|||
|
echo "THIS IS A 'DRY RUN'!"
|
|||
|
echo "No data will actually be transferred!"
|
|||
|
else
|
|||
|
DRY_RUN=""
|
|||
|
fi
|
|||
|
|
|||
|
if [ "$VERBOSE" == "true" ]; then
|
|||
|
VERBOSE="-v"
|
|||
|
else
|
|||
|
VERBOSE=""
|
|||
|
fi
|
|||
|
|
|||
|
if [ "$COMPRESS" == "true" ]; then
|
|||
|
COMPRESS="-z"
|
|||
|
else
|
|||
|
COMPRESS=""
|
|||
|
fi
|
|||
|
|
|||
|
|
|||
|
# Every week (actually of 8 days) and every month,
|
|||
|
#+ extra backups are preserved.
|
|||
|
DAY_OF_MONTH=`date +%d` # Day of month (01..31).
|
|||
|
if [ $DAY_OF_MONTH = 01 ]; then # First of month.
|
|||
|
MONTHSTART=true
|
|||
|
elif [ $DAY_OF_MONTH = 08 \
|
|||
|
-o $DAY_OF_MONTH = 16 \
|
|||
|
-o $DAY_OF_MONTH = 24 ]; then
|
|||
|
# Day 8,16,24 (use 8, not 7 to better handle 31-day months)
|
|||
|
WEEKSTART=true
|
|||
|
fi
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# Check that the HDD is mounted.
|
|||
|
# At least, check that *something* is mounted here!
|
|||
|
# We can use something unique to the device, rather than just guessing
|
|||
|
#+ the scsi-id by having an appropriate udev rule in
|
|||
|
#+ /etc/udev/rules.d/10-rules.local
|
|||
|
#+ and by putting a relevant entry in /etc/fstab.
|
|||
|
# Eg: this udev rule:
|
|||
|
# BUS="scsi", KERNEL="sd*", SYSFS{vendor}="WDC WD16",
|
|||
|
# SYSFS{model}="00JB-00GVA0 ", NAME="%k", SYMLINK="lacie_1394d%n"
|
|||
|
|
|||
|
if mount | grep $MOUNT_POINT >/dev/null; then
|
|||
|
echo "Mount point $MOUNT_POINT is indeed mounted. OK"
|
|||
|
else
|
|||
|
echo -n "Attempting to mount $MOUNT_POINT..."
|
|||
|
# If it isn't mounted, try to mount it.
|
|||
|
sudo mount $MOUNT_POINT 2>/dev/null
|
|||
|
|
|||
|
if mount | grep $MOUNT_POINT >/dev/null; then
|
|||
|
UNMOUNT_LATER=TRUE
|
|||
|
echo "OK"
|
|||
|
# Note: Ensure that this is also unmounted
|
|||
|
#+ if we exit prematurely with failure.
|
|||
|
else
|
|||
|
echo "FAILED"
|
|||
|
echo -e "Nothing is mounted at $MOUNT_POINT. BACKUP FAILED!"
|
|||
|
exit $E_MOUNT_FAIL
|
|||
|
fi
|
|||
|
fi
|
|||
|
|
|||
|
|
|||
|
# Check that source dir exists and is readable.
|
|||
|
if [ ! -r $SOURCE_DIR ] ; then
|
|||
|
echo "$SOURCE_DIR does not exist, or cannot be read. BACKUP FAILED."
|
|||
|
exit $E_NOSOURCEDIR
|
|||
|
fi
|
|||
|
|
|||
|
|
|||
|
# Check that the backup directory structure is as it should be.
|
|||
|
# If not, create it.
|
|||
|
# Create the subdirectories.
|
|||
|
# Note that backup.0 will be created as needed by rsync.
|
|||
|
|
|||
|
for ((i=1;i<=15;i++)); do
|
|||
|
if [ ! -d $BACKUP_DEST_DIR/backup.$i ]; then
|
|||
|
if /bin/mkdir -p $BACKUP_DEST_DIR/backup.$i ; then
|
|||
|
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ No [ ] test brackets. Why?
|
|||
|
echo "Warning: directory $BACKUP_DEST_DIR/backup.$i is missing,"
|
|||
|
echo "or was not initialised. (Re-)creating it."
|
|||
|
else
|
|||
|
echo "ERROR: directory $BACKUP_DEST_DIR/backup.$i"
|
|||
|
echo "is missing and could not be created."
|
|||
|
if [ "$UNMOUNT_LATER" == "TRUE" ]; then
|
|||
|
# Before we exit, unmount the mount point if necessary.
|
|||
|
cd
|
|||
|
sudo umount $MOUNT_POINT &&
|
|||
|
echo "Unmounted $MOUNT_POINT again. Giving up."
|
|||
|
fi
|
|||
|
exit $E_UNMOUNTED
|
|||
|
fi
|
|||
|
fi
|
|||
|
done
|
|||
|
|
|||
|
|
|||
|
# Set the permission to 700 for security
|
|||
|
#+ on an otherwise permissive multi-user system.
|
|||
|
if ! /bin/chmod 700 $BACKUP_DEST_DIR ; then
|
|||
|
echo "ERROR: Could not set permissions on $BACKUP_DEST_DIR to 700."
|
|||
|
|
|||
|
if [ "$UNMOUNT_LATER" == "TRUE" ]; then
|
|||
|
# Before we exit, unmount the mount point if necessary.
|
|||
|
cd ; sudo umount $MOUNT_POINT \
|
|||
|
&& echo "Unmounted $MOUNT_POINT again. Giving up."
|
|||
|
fi
|
|||
|
|
|||
|
exit $E_UNMOUNTED
|
|||
|
fi
|
|||
|
|
|||
|
# Create the symlink: current -> backup.1 if required.
|
|||
|
# A failure here is not critical.
|
|||
|
cd $BACKUP_DEST_DIR
|
|||
|
if [ ! -h current ] ; then
|
|||
|
if ! /bin/ln -s backup.1 current ; then
|
|||
|
echo "WARNING: could not create symlink current -> backup.1"
|
|||
|
fi
|
|||
|
fi
|
|||
|
|
|||
|
|
|||
|
# Now, do the rsync.
|
|||
|
echo "Now doing backup with rsync..."
|
|||
|
echo "Source dir: $SOURCE_DIR"
|
|||
|
echo -e "Backup destination dir: $BACKUP_DEST_DIR\n"
|
|||
|
|
|||
|
|
|||
|
/usr/bin/rsync $DRY_RUN $VERBOSE -a -S --delete --modify-window=60 \
|
|||
|
--link-dest=../backup.1 $SOURCE_DIR $BACKUP_DEST_DIR/backup.0/
|
|||
|
|
|||
|
# Only warn, rather than exit if the rsync failed,
|
|||
|
#+ since it may only be a minor problem.
|
|||
|
# E.g., if one file is not readable, rsync will fail.
|
|||
|
# This shouldn't prevent the rotation.
|
|||
|
# Not using, e.g., `date +%a` since these directories
|
|||
|
#+ are just full of links and don't consume *that much* space.
|
|||
|
|
|||
|
if [ $? != 0 ]; then
|
|||
|
BACKUP_JUSTINCASE=backup.`date +%F_%T`.justincase
|
|||
|
echo "WARNING: the rsync process did not entirely succeed."
|
|||
|
echo "Something might be wrong."
|
|||
|
echo "Saving an extra copy at: $BACKUP_JUSTINCASE"
|
|||
|
echo "WARNING: if this occurs regularly, a LOT of space will be consumed,"
|
|||
|
echo "even though these are just hard-links!"
|
|||
|
fi
|
|||
|
|
|||
|
# Save a readme in the backup parent directory.
|
|||
|
# Save another one in the recent subdirectory.
|
|||
|
echo "Backup of $SOURCE_DIR on `hostname` was last run on \
|
|||
|
`date`" > $BACKUP_DEST_DIR/README.txt
|
|||
|
echo "This backup of $SOURCE_DIR on `hostname` was created on \
|
|||
|
`date`" > $BACKUP_DEST_DIR/backup.0/README.txt
|
|||
|
|
|||
|
# If we are not in a dry run, rotate the backups.
|
|||
|
[ -z "$DRY_RUN" ] &&
|
|||
|
|
|||
|
# Check how full the backup disk is.
|
|||
|
# Warn if 90%. if 98% or more, we'll probably fail, so give up.
|
|||
|
# (Note: df can output to more than one line.)
|
|||
|
# We test this here, rather than before
|
|||
|
#+ so that rsync may possibly have a chance.
|
|||
|
DISK_FULL_PERCENT=`/bin/df $BACKUP_DEST_DIR |
|
|||
|
tr "\n" ' ' | awk '{print $12}' | grep -oE [0-9]+ `
|
|||
|
echo "Disk space check on backup partition \
|
|||
|
$MOUNT_POINT $DISK_FULL_PERCENT% full."
|
|||
|
if [ $DISK_FULL_PERCENT -gt 90 ]; then
|
|||
|
echo "Warning: Disk is greater than 90% full."
|
|||
|
fi
|
|||
|
if [ $DISK_FULL_PERCENT -gt 98 ]; then
|
|||
|
echo "Error: Disk is full! Giving up."
|
|||
|
if [ "$UNMOUNT_LATER" == "TRUE" ]; then
|
|||
|
# Before we exit, unmount the mount point if necessary.
|
|||
|
cd; sudo umount $MOUNT_POINT &&
|
|||
|
echo "Unmounted $MOUNT_POINT again. Giving up."
|
|||
|
fi
|
|||
|
exit $E_UNMOUNTED
|
|||
|
fi
|
|||
|
|
|||
|
|
|||
|
# Create an extra backup.
|
|||
|
# If this copy fails, give up.
|
|||
|
if [ -n "$BACKUP_JUSTINCASE" ]; then
|
|||
|
if ! /bin/cp -al $BACKUP_DEST_DIR/backup.0 \
|
|||
|
$BACKUP_DEST_DIR/$BACKUP_JUSTINCASE
|
|||
|
then
|
|||
|
echo "ERROR: Failed to create extra copy \
|
|||
|
$BACKUP_DEST_DIR/$BACKUP_JUSTINCASE"
|
|||
|
if [ "$UNMOUNT_LATER" == "TRUE" ]; then
|
|||
|
# Before we exit, unmount the mount point if necessary.
|
|||
|
cd ;sudo umount $MOUNT_POINT &&
|
|||
|
echo "Unmounted $MOUNT_POINT again. Giving up."
|
|||
|
fi
|
|||
|
exit $E_UNMOUNTED
|
|||
|
fi
|
|||
|
fi
|
|||
|
|
|||
|
|
|||
|
# At start of month, rotate the oldest 8.
|
|||
|
if [ "$MONTHSTART" == "true" ]; then
|
|||
|
echo -e "\nStart of month. \
|
|||
|
Removing oldest backup: $BACKUP_DEST_DIR/backup.15" &&
|
|||
|
/bin/rm -rf $BACKUP_DEST_DIR/backup.15 &&
|
|||
|
echo "Rotating monthly,weekly backups: \
|
|||
|
$BACKUP_DEST_DIR/backup.[8-14] -> $BACKUP_DEST_DIR/backup.[9-15]" &&
|
|||
|
/bin/mv $BACKUP_DEST_DIR/backup.14 $BACKUP_DEST_DIR/backup.15 &&
|
|||
|
/bin/mv $BACKUP_DEST_DIR/backup.13 $BACKUP_DEST_DIR/backup.14 &&
|
|||
|
/bin/mv $BACKUP_DEST_DIR/backup.12 $BACKUP_DEST_DIR/backup.13 &&
|
|||
|
/bin/mv $BACKUP_DEST_DIR/backup.11 $BACKUP_DEST_DIR/backup.12 &&
|
|||
|
/bin/mv $BACKUP_DEST_DIR/backup.10 $BACKUP_DEST_DIR/backup.11 &&
|
|||
|
/bin/mv $BACKUP_DEST_DIR/backup.9 $BACKUP_DEST_DIR/backup.10 &&
|
|||
|
/bin/mv $BACKUP_DEST_DIR/backup.8 $BACKUP_DEST_DIR/backup.9
|
|||
|
|
|||
|
# At start of week, rotate the second-oldest 4.
|
|||
|
elif [ "$WEEKSTART" == "true" ]; then
|
|||
|
echo -e "\nStart of week. \
|
|||
|
Removing oldest weekly backup: $BACKUP_DEST_DIR/backup.12" &&
|
|||
|
/bin/rm -rf $BACKUP_DEST_DIR/backup.12 &&
|
|||
|
|
|||
|
echo "Rotating weekly backups: \
|
|||
|
$BACKUP_DEST_DIR/backup.[8-11] -> $BACKUP_DEST_DIR/backup.[9-12]" &&
|
|||
|
/bin/mv $BACKUP_DEST_DIR/backup.11 $BACKUP_DEST_DIR/backup.12 &&
|
|||
|
/bin/mv $BACKUP_DEST_DIR/backup.10 $BACKUP_DEST_DIR/backup.11 &&
|
|||
|
/bin/mv $BACKUP_DEST_DIR/backup.9 $BACKUP_DEST_DIR/backup.10 &&
|
|||
|
/bin/mv $BACKUP_DEST_DIR/backup.8 $BACKUP_DEST_DIR/backup.9
|
|||
|
|
|||
|
else
|
|||
|
echo -e "\nRemoving oldest daily backup: $BACKUP_DEST_DIR/backup.8" &&
|
|||
|
/bin/rm -rf $BACKUP_DEST_DIR/backup.8
|
|||
|
|
|||
|
fi &&
|
|||
|
|
|||
|
# Every day, rotate the newest 8.
|
|||
|
echo "Rotating daily backups: \
|
|||
|
$BACKUP_DEST_DIR/backup.[1-7] -> $BACKUP_DEST_DIR/backup.[2-8]" &&
|
|||
|
/bin/mv $BACKUP_DEST_DIR/backup.7 $BACKUP_DEST_DIR/backup.8 &&
|
|||
|
/bin/mv $BACKUP_DEST_DIR/backup.6 $BACKUP_DEST_DIR/backup.7 &&
|
|||
|
/bin/mv $BACKUP_DEST_DIR/backup.5 $BACKUP_DEST_DIR/backup.6 &&
|
|||
|
/bin/mv $BACKUP_DEST_DIR/backup.4 $BACKUP_DEST_DIR/backup.5 &&
|
|||
|
/bin/mv $BACKUP_DEST_DIR/backup.3 $BACKUP_DEST_DIR/backup.4 &&
|
|||
|
/bin/mv $BACKUP_DEST_DIR/backup.2 $BACKUP_DEST_DIR/backup.3 &&
|
|||
|
/bin/mv $BACKUP_DEST_DIR/backup.1 $BACKUP_DEST_DIR/backup.2 &&
|
|||
|
/bin/mv $BACKUP_DEST_DIR/backup.0 $BACKUP_DEST_DIR/backup.1 &&
|
|||
|
|
|||
|
SUCCESS=true
|
|||
|
|
|||
|
|
|||
|
if [ "$UNMOUNT_LATER" == "TRUE" ]; then
|
|||
|
# Unmount the mount point if it wasn't mounted to begin with.
|
|||
|
cd ; sudo umount $MOUNT_POINT && echo "Unmounted $MOUNT_POINT again."
|
|||
|
fi
|
|||
|
|
|||
|
|
|||
|
if [ "$SUCCESS" == "true" ]; then
|
|||
|
echo 'SUCCESS!'
|
|||
|
exit 0
|
|||
|
fi
|
|||
|
|
|||
|
# Should have already exited if backup worked.
|
|||
|
echo 'BACKUP FAILED! Is this just a dry run? Is the disk full?) '
|
|||
|
exit $E_BACKUP</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="CDLL"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-33. An expanded <I
|
|||
|
CLASS="FIRSTTERM"
|
|||
|
>cd</I
|
|||
|
> command</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>###########################################################################
|
|||
|
#
|
|||
|
# cdll
|
|||
|
# by Phil Braham
|
|||
|
#
|
|||
|
# ############################################
|
|||
|
# Latest version of this script available from
|
|||
|
# http://freshmeat.net/projects/cd/
|
|||
|
# ############################################
|
|||
|
#
|
|||
|
# .cd_new
|
|||
|
#
|
|||
|
# An enhancement of the Unix cd command
|
|||
|
#
|
|||
|
# There are unlimited stack entries and special entries. The stack
|
|||
|
# entries keep the last cd_maxhistory
|
|||
|
# directories that have been used. The special entries can be
|
|||
|
# assigned to commonly used directories.
|
|||
|
#
|
|||
|
# The special entries may be pre-assigned by setting the environment
|
|||
|
# variables CDSn or by using the -u or -U command.
|
|||
|
#
|
|||
|
# The following is a suggestion for the .profile file:
|
|||
|
#
|
|||
|
# . cdll # Set up the cd command
|
|||
|
# alias cd='cd_new' # Replace the cd command
|
|||
|
# cd -U # Upload pre-assigned entries for
|
|||
|
# #+ the stack and special entries
|
|||
|
# cd -D # Set non-default mode
|
|||
|
# alias @="cd_new @" # Allow @ to be used to get history
|
|||
|
#
|
|||
|
# For help type:
|
|||
|
#
|
|||
|
# cd -h or
|
|||
|
# cd -H
|
|||
|
#
|
|||
|
#
|
|||
|
###########################################################################
|
|||
|
#
|
|||
|
# Version 1.2.1
|
|||
|
#
|
|||
|
# Written by Phil Braham - Realtime Software Pty Ltd
|
|||
|
# (realtime@mpx.com.au)
|
|||
|
# Please send any suggestions or enhancements to the author (also at
|
|||
|
# phil@braham.net)
|
|||
|
#
|
|||
|
############################################################################
|
|||
|
|
|||
|
cd_hm ()
|
|||
|
{
|
|||
|
${PRINTF} "%s" "cd [dir] [0-9] [@[s|h] [-g [<dir>]] [-d] \
|
|||
|
[-D] [-r<n>] [dir|0-9] [-R<n>] [<dir>|0-9]
|
|||
|
[-s<n>] [-S<n>] [-u] [-U] [-f] [-F] [-h] [-H] [-v]
|
|||
|
<dir> Go to directory
|
|||
|
0-n Go to previous directory (0 is previous, 1 is last but 1 etc)
|
|||
|
n is up to max history (default is 50)
|
|||
|
@ List history and special entries
|
|||
|
@h List history entries
|
|||
|
@s List special entries
|
|||
|
-g [<dir>] Go to literal name (bypass special names)
|
|||
|
This is to allow access to dirs called '0','1','-h' etc
|
|||
|
-d Change default action - verbose. (See note)
|
|||
|
-D Change default action - silent. (See note)
|
|||
|
-s<n> Go to the special entry <n>*
|
|||
|
-S<n> Go to the special entry <n>
|
|||
|
and replace it with the current dir*
|
|||
|
-r<n> [<dir>] Go to directory <dir>
|
|||
|
and then put it on special entry <n>*
|
|||
|
-R<n> [<dir>] Go to directory <dir>
|
|||
|
and put current dir on special entry <n>*
|
|||
|
-a<n> Alternative suggested directory. See note below.
|
|||
|
-f [<file>] File entries to <file>.
|
|||
|
-u [<file>] Update entries from <file>.
|
|||
|
If no filename supplied then default file
|
|||
|
(${CDPath}${2:-"$CDFile"}) is used
|
|||
|
-F and -U are silent versions
|
|||
|
-v Print version number
|
|||
|
-h Help
|
|||
|
-H Detailed help
|
|||
|
|
|||
|
*The special entries (0 - 9) are held until log off, replaced by another
|
|||
|
entry or updated with the -u command
|
|||
|
|
|||
|
Alternative suggested directories:
|
|||
|
If a directory is not found then CD will suggest any
|
|||
|
possibilities. These are directories starting with the same letters
|
|||
|
and if any are found they are listed prefixed with -a<n>
|
|||
|
where <n> is a number.
|
|||
|
It's possible to go to the directory by entering cd -a<n>
|
|||
|
on the command line.
|
|||
|
|
|||
|
The directory for -r<n> or -R<n> may be a number.
|
|||
|
For example:
|
|||
|
$ cd -r3 4 Go to history entry 4 and put it on special entry 3
|
|||
|
$ cd -R3 4 Put current dir on the special entry 3
|
|||
|
and go to history entry 4
|
|||
|
$ cd -s3 Go to special entry 3
|
|||
|
|
|||
|
Note that commands R,r,S and s may be used without a number
|
|||
|
and refer to 0:
|
|||
|
$ cd -s Go to special entry 0
|
|||
|
$ cd -S Go to special entry 0 and make special
|
|||
|
entry 0 current dir
|
|||
|
$ cd -r 1 Go to history entry 1 and put it on special entry 0
|
|||
|
$ cd -r Go to history entry 0 and put it on special entry 0
|
|||
|
"
|
|||
|
if ${TEST} "$CD_MODE" = "PREV"
|
|||
|
then
|
|||
|
${PRINTF} "$cd_mnset"
|
|||
|
else
|
|||
|
${PRINTF} "$cd_mset"
|
|||
|
fi
|
|||
|
}
|
|||
|
|
|||
|
cd_Hm ()
|
|||
|
{
|
|||
|
cd_hm
|
|||
|
${PRINTF} "%s" "
|
|||
|
The previous directories (0-$cd_maxhistory) are stored in the
|
|||
|
environment variables CD[0] - CD[$cd_maxhistory]
|
|||
|
Similarly the special directories S0 - $cd_maxspecial are in
|
|||
|
the environment variable CDS[0] - CDS[$cd_maxspecial]
|
|||
|
and may be accessed from the command line
|
|||
|
|
|||
|
The default pathname for the -f and -u commands is $CDPath
|
|||
|
The default filename for the -f and -u commands is $CDFile
|
|||
|
|
|||
|
Set the following environment variables:
|
|||
|
CDL_PROMPTLEN - Set to the length of prompt you require.
|
|||
|
Prompt string is set to the right characters of the
|
|||
|
current directory.
|
|||
|
If not set then prompt is left unchanged
|
|||
|
CDL_PROMPT_PRE - Set to the string to prefix the prompt.
|
|||
|
Default is:
|
|||
|
non-root: \"\\[\\e[01;34m\\]\" (sets colour to blue).
|
|||
|
root: \"\\[\\e[01;31m\\]\" (sets colour to red).
|
|||
|
CDL_PROMPT_POST - Set to the string to suffix the prompt.
|
|||
|
Default is:
|
|||
|
non-root: \"\\[\\e[00m\\]$\"
|
|||
|
(resets colour and displays $).
|
|||
|
root: \"\\[\\e[00m\\]#\"
|
|||
|
(resets colour and displays #).
|
|||
|
CDPath - Set the default path for the -f & -u options.
|
|||
|
Default is home directory
|
|||
|
CDFile - Set the default filename for the -f & -u options.
|
|||
|
Default is cdfile
|
|||
|
|
|||
|
"
|
|||
|
cd_version
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
cd_version ()
|
|||
|
{
|
|||
|
printf "Version: ${VERSION_MAJOR}.${VERSION_MINOR} Date: ${VERSION_DATE}\n"
|
|||
|
}
|
|||
|
|
|||
|
#
|
|||
|
# Truncate right.
|
|||
|
#
|
|||
|
# params:
|
|||
|
# p1 - string
|
|||
|
# p2 - length to truncate to
|
|||
|
#
|
|||
|
# returns string in tcd
|
|||
|
#
|
|||
|
cd_right_trunc ()
|
|||
|
{
|
|||
|
local tlen=${2}
|
|||
|
local plen=${#1}
|
|||
|
local str="${1}"
|
|||
|
local diff
|
|||
|
local filler="<--"
|
|||
|
if ${TEST} ${plen} -le ${tlen}
|
|||
|
then
|
|||
|
tcd="${str}"
|
|||
|
else
|
|||
|
let diff=${plen}-${tlen}
|
|||
|
elen=3
|
|||
|
if ${TEST} ${diff} -le 2
|
|||
|
then
|
|||
|
let elen=${diff}
|
|||
|
fi
|
|||
|
tlen=-${tlen}
|
|||
|
let tlen=${tlen}+${elen}
|
|||
|
tcd=${filler:0:elen}${str:tlen}
|
|||
|
fi
|
|||
|
}
|
|||
|
|
|||
|
#
|
|||
|
# Three versions of do history:
|
|||
|
# cd_dohistory - packs history and specials side by side
|
|||
|
# cd_dohistoryH - Shows only hstory
|
|||
|
# cd_dohistoryS - Shows only specials
|
|||
|
#
|
|||
|
cd_dohistory ()
|
|||
|
{
|
|||
|
cd_getrc
|
|||
|
${PRINTF} "History:\n"
|
|||
|
local -i count=${cd_histcount}
|
|||
|
while ${TEST} ${count} -ge 0
|
|||
|
do
|
|||
|
cd_right_trunc "${CD[count]}" ${cd_lchar}
|
|||
|
${PRINTF} "%2d %-${cd_lchar}.${cd_lchar}s " ${count} "${tcd}"
|
|||
|
|
|||
|
cd_right_trunc "${CDS[count]}" ${cd_rchar}
|
|||
|
${PRINTF} "S%d %-${cd_rchar}.${cd_rchar}s\n" ${count} "${tcd}"
|
|||
|
count=${count}-1
|
|||
|
done
|
|||
|
}
|
|||
|
|
|||
|
cd_dohistoryH ()
|
|||
|
{
|
|||
|
cd_getrc
|
|||
|
${PRINTF} "History:\n"
|
|||
|
local -i count=${cd_maxhistory}
|
|||
|
while ${TEST} ${count} -ge 0
|
|||
|
do
|
|||
|
${PRINTF} "${count} %-${cd_flchar}.${cd_flchar}s\n" ${CD[$count]}
|
|||
|
count=${count}-1
|
|||
|
done
|
|||
|
}
|
|||
|
|
|||
|
cd_dohistoryS ()
|
|||
|
{
|
|||
|
cd_getrc
|
|||
|
${PRINTF} "Specials:\n"
|
|||
|
local -i count=${cd_maxspecial}
|
|||
|
while ${TEST} ${count} -ge 0
|
|||
|
do
|
|||
|
${PRINTF} "S${count} %-${cd_flchar}.${cd_flchar}s\n" ${CDS[$count]}
|
|||
|
count=${count}-1
|
|||
|
done
|
|||
|
}
|
|||
|
|
|||
|
cd_getrc ()
|
|||
|
{
|
|||
|
cd_flchar=$(stty -a | awk -F \;
|
|||
|
'/rows/ { print $2 $3 }' | awk -F \ '{ print $4 }')
|
|||
|
if ${TEST} ${cd_flchar} -ne 0
|
|||
|
then
|
|||
|
cd_lchar=${cd_flchar}/2-5
|
|||
|
cd_rchar=${cd_flchar}/2-5
|
|||
|
cd_flchar=${cd_flchar}-5
|
|||
|
else
|
|||
|
cd_flchar=${FLCHAR:=75}
|
|||
|
# cd_flchar is used for for the @s & @h history
|
|||
|
cd_lchar=${LCHAR:=35}
|
|||
|
cd_rchar=${RCHAR:=35}
|
|||
|
fi
|
|||
|
}
|
|||
|
|
|||
|
cd_doselection ()
|
|||
|
{
|
|||
|
local -i nm=0
|
|||
|
cd_doflag="TRUE"
|
|||
|
if ${TEST} "${CD_MODE}" = "PREV"
|
|||
|
then
|
|||
|
if ${TEST} -z "$cd_npwd"
|
|||
|
then
|
|||
|
cd_npwd=0
|
|||
|
fi
|
|||
|
fi
|
|||
|
tm=$(echo "${cd_npwd}" | cut -b 1)
|
|||
|
if ${TEST} "${tm}" = "-"
|
|||
|
then
|
|||
|
pm=$(echo "${cd_npwd}" | cut -b 2)
|
|||
|
nm=$(echo "${cd_npwd}" | cut -d $pm -f2)
|
|||
|
case "${pm}" in
|
|||
|
a) cd_npwd=${cd_sugg[$nm]} ;;
|
|||
|
s) cd_npwd="${CDS[$nm]}" ;;
|
|||
|
S) cd_npwd="${CDS[$nm]}" ; CDS[$nm]=`pwd` ;;
|
|||
|
r) cd_npwd="$2" ; cd_specDir=$nm ; cd_doselection "$1" "$2";;
|
|||
|
R) cd_npwd="$2" ; CDS[$nm]=`pwd` ; cd_doselection "$1" "$2";;
|
|||
|
esac
|
|||
|
fi
|
|||
|
|
|||
|
if ${TEST} "${cd_npwd}" != "." -a "${cd_npwd}" \
|
|||
|
!= ".." -a "${cd_npwd}" -le ${cd_maxhistory} >>/dev/null 2>&1
|
|||
|
then
|
|||
|
cd_npwd=${CD[$cd_npwd]}
|
|||
|
else
|
|||
|
case "$cd_npwd" in
|
|||
|
@) cd_dohistory ; cd_doflag="FALSE" ;;
|
|||
|
@h) cd_dohistoryH ; cd_doflag="FALSE" ;;
|
|||
|
@s) cd_dohistoryS ; cd_doflag="FALSE" ;;
|
|||
|
-h) cd_hm ; cd_doflag="FALSE" ;;
|
|||
|
-H) cd_Hm ; cd_doflag="FALSE" ;;
|
|||
|
-f) cd_fsave "SHOW" $2 ; cd_doflag="FALSE" ;;
|
|||
|
-u) cd_upload "SHOW" $2 ; cd_doflag="FALSE" ;;
|
|||
|
-F) cd_fsave "NOSHOW" $2 ; cd_doflag="FALSE" ;;
|
|||
|
-U) cd_upload "NOSHOW" $2 ; cd_doflag="FALSE" ;;
|
|||
|
-g) cd_npwd="$2" ;;
|
|||
|
-d) cd_chdefm 1; cd_doflag="FALSE" ;;
|
|||
|
-D) cd_chdefm 0; cd_doflag="FALSE" ;;
|
|||
|
-r) cd_npwd="$2" ; cd_specDir=0 ; cd_doselection "$1" "$2";;
|
|||
|
-R) cd_npwd="$2" ; CDS[0]=`pwd` ; cd_doselection "$1" "$2";;
|
|||
|
-s) cd_npwd="${CDS[0]}" ;;
|
|||
|
-S) cd_npwd="${CDS[0]}" ; CDS[0]=`pwd` ;;
|
|||
|
-v) cd_version ; cd_doflag="FALSE";;
|
|||
|
esac
|
|||
|
fi
|
|||
|
}
|
|||
|
|
|||
|
cd_chdefm ()
|
|||
|
{
|
|||
|
if ${TEST} "${CD_MODE}" = "PREV"
|
|||
|
then
|
|||
|
CD_MODE=""
|
|||
|
if ${TEST} $1 -eq 1
|
|||
|
then
|
|||
|
${PRINTF} "${cd_mset}"
|
|||
|
fi
|
|||
|
else
|
|||
|
CD_MODE="PREV"
|
|||
|
if ${TEST} $1 -eq 1
|
|||
|
then
|
|||
|
${PRINTF} "${cd_mnset}"
|
|||
|
fi
|
|||
|
fi
|
|||
|
}
|
|||
|
|
|||
|
cd_fsave ()
|
|||
|
{
|
|||
|
local sfile=${CDPath}${2:-"$CDFile"}
|
|||
|
if ${TEST} "$1" = "SHOW"
|
|||
|
then
|
|||
|
${PRINTF} "Saved to %s\n" $sfile
|
|||
|
fi
|
|||
|
${RM} -f ${sfile}
|
|||
|
local -i count=0
|
|||
|
while ${TEST} ${count} -le ${cd_maxhistory}
|
|||
|
do
|
|||
|
echo "CD[$count]=\"${CD[$count]}\"" >> ${sfile}
|
|||
|
count=${count}+1
|
|||
|
done
|
|||
|
count=0
|
|||
|
while ${TEST} ${count} -le ${cd_maxspecial}
|
|||
|
do
|
|||
|
echo "CDS[$count]=\"${CDS[$count]}\"" >> ${sfile}
|
|||
|
count=${count}+1
|
|||
|
done
|
|||
|
}
|
|||
|
|
|||
|
cd_upload ()
|
|||
|
{
|
|||
|
local sfile=${CDPath}${2:-"$CDFile"}
|
|||
|
if ${TEST} "${1}" = "SHOW"
|
|||
|
then
|
|||
|
${PRINTF} "Loading from %s\n" ${sfile}
|
|||
|
fi
|
|||
|
. ${sfile}
|
|||
|
}
|
|||
|
|
|||
|
cd_new ()
|
|||
|
{
|
|||
|
local -i count
|
|||
|
local -i choose=0
|
|||
|
|
|||
|
cd_npwd="${1}"
|
|||
|
cd_specDir=-1
|
|||
|
cd_doselection "${1}" "${2}"
|
|||
|
|
|||
|
if ${TEST} ${cd_doflag} = "TRUE"
|
|||
|
then
|
|||
|
if ${TEST} "${CD[0]}" != "`pwd`"
|
|||
|
then
|
|||
|
count=$cd_maxhistory
|
|||
|
while ${TEST} $count -gt 0
|
|||
|
do
|
|||
|
CD[$count]=${CD[$count-1]}
|
|||
|
count=${count}-1
|
|||
|
done
|
|||
|
CD[0]=`pwd`
|
|||
|
fi
|
|||
|
command cd "${cd_npwd}" 2>/dev/null
|
|||
|
if ${TEST} $? -eq 1
|
|||
|
then
|
|||
|
${PRINTF} "Unknown dir: %s\n" "${cd_npwd}"
|
|||
|
local -i ftflag=0
|
|||
|
for i in "${cd_npwd}"*
|
|||
|
do
|
|||
|
if ${TEST} -d "${i}"
|
|||
|
then
|
|||
|
if ${TEST} ${ftflag} -eq 0
|
|||
|
then
|
|||
|
${PRINTF} "Suggest:\n"
|
|||
|
ftflag=1
|
|||
|
fi
|
|||
|
${PRINTF} "\t-a${choose} %s\n" "$i"
|
|||
|
cd_sugg[$choose]="${i}"
|
|||
|
choose=${choose}+1
|
|||
|
fi
|
|||
|
done
|
|||
|
fi
|
|||
|
fi
|
|||
|
|
|||
|
if ${TEST} ${cd_specDir} -ne -1
|
|||
|
then
|
|||
|
CDS[${cd_specDir}]=`pwd`
|
|||
|
fi
|
|||
|
|
|||
|
if ${TEST} ! -z "${CDL_PROMPTLEN}"
|
|||
|
then
|
|||
|
cd_right_trunc "${PWD}" ${CDL_PROMPTLEN}
|
|||
|
cd_rp=${CDL_PROMPT_PRE}${tcd}${CDL_PROMPT_POST}
|
|||
|
export PS1="$(echo -ne ${cd_rp})"
|
|||
|
fi
|
|||
|
}
|
|||
|
#########################################################################
|
|||
|
# #
|
|||
|
# Initialisation here #
|
|||
|
# #
|
|||
|
#########################################################################
|
|||
|
#
|
|||
|
VERSION_MAJOR="1"
|
|||
|
VERSION_MINOR="2.1"
|
|||
|
VERSION_DATE="24-MAY-2003"
|
|||
|
#
|
|||
|
alias cd=cd_new
|
|||
|
#
|
|||
|
# Set up commands
|
|||
|
RM=/bin/rm
|
|||
|
TEST=test
|
|||
|
PRINTF=printf # Use builtin printf
|
|||
|
|
|||
|
#########################################################################
|
|||
|
# #
|
|||
|
# Change this to modify the default pre- and post prompt strings. #
|
|||
|
# These only come into effect if CDL_PROMPTLEN is set. #
|
|||
|
# #
|
|||
|
#########################################################################
|
|||
|
if ${TEST} ${EUID} -eq 0
|
|||
|
then
|
|||
|
# CDL_PROMPT_PRE=${CDL_PROMPT_PRE:="$HOSTNAME@"}
|
|||
|
CDL_PROMPT_PRE=${CDL_PROMPT_PRE:="\\[\\e[01;31m\\]"} # Root is in red
|
|||
|
CDL_PROMPT_POST=${CDL_PROMPT_POST:="\\[\\e[00m\\]#"}
|
|||
|
else
|
|||
|
CDL_PROMPT_PRE=${CDL_PROMPT_PRE:="\\[\\e[01;34m\\]"} # Users in blue
|
|||
|
CDL_PROMPT_POST=${CDL_PROMPT_POST:="\\[\\e[00m\\]$"}
|
|||
|
fi
|
|||
|
#########################################################################
|
|||
|
#
|
|||
|
# cd_maxhistory defines the max number of history entries allowed.
|
|||
|
typeset -i cd_maxhistory=50
|
|||
|
|
|||
|
#########################################################################
|
|||
|
#
|
|||
|
# cd_maxspecial defines the number of special entries.
|
|||
|
typeset -i cd_maxspecial=9
|
|||
|
#
|
|||
|
#
|
|||
|
#########################################################################
|
|||
|
#
|
|||
|
# cd_histcount defines the number of entries displayed in
|
|||
|
#+ the history command.
|
|||
|
typeset -i cd_histcount=9
|
|||
|
#
|
|||
|
#########################################################################
|
|||
|
export CDPath=${HOME}/
|
|||
|
# Change these to use a different #
|
|||
|
#+ default path and filename #
|
|||
|
export CDFile=${CDFILE:=cdfile} # for the -u and -f commands #
|
|||
|
#
|
|||
|
#########################################################################
|
|||
|
#
|
|||
|
typeset -i cd_lchar cd_rchar cd_flchar
|
|||
|
# This is the number of chars to allow for the #
|
|||
|
cd_flchar=${FLCHAR:=75} #+ cd_flchar is used for for the @s & @h history#
|
|||
|
|
|||
|
typeset -ax CD CDS
|
|||
|
#
|
|||
|
cd_mset="\n\tDefault mode is now set - entering cd with no parameters \
|
|||
|
has the default action\n\tUse cd -d or -D for cd to go to \
|
|||
|
previous directory with no parameters\n"
|
|||
|
cd_mnset="\n\tNon-default mode is now set - entering cd with no \
|
|||
|
parameters is the same as entering cd 0\n\tUse cd -d or \
|
|||
|
-D to change default cd action\n"
|
|||
|
|
|||
|
# ==================================================================== #
|
|||
|
|
|||
|
|
|||
|
|
|||
|
: <<DOCUMENTATION
|
|||
|
|
|||
|
Written by Phil Braham. Realtime Software Pty Ltd.
|
|||
|
Released under GNU license. Free to use. Please pass any modifications
|
|||
|
or comments to the author Phil Braham:
|
|||
|
|
|||
|
realtime@mpx.com.au
|
|||
|
=======================================================================
|
|||
|
|
|||
|
cdll is a replacement for cd and incorporates similar functionality to
|
|||
|
the bash pushd and popd commands but is independent of them.
|
|||
|
|
|||
|
This version of cdll has been tested on Linux using Bash. It will work
|
|||
|
on most Linux versions but will probably not work on other shells without
|
|||
|
modification.
|
|||
|
|
|||
|
Introduction
|
|||
|
============
|
|||
|
|
|||
|
cdll allows easy moving about between directories. When changing to a new
|
|||
|
directory the current one is automatically put onto a stack. By default
|
|||
|
50 entries are kept, but this is configurable. Special directories can be
|
|||
|
kept for easy access - by default up to 10, but this is configurable. The
|
|||
|
most recent stack entries and the special entries can be easily viewed.
|
|||
|
|
|||
|
The directory stack and special entries can be saved to, and loaded from,
|
|||
|
a file. This allows them to be set up on login, saved before logging out
|
|||
|
or changed when moving project to project.
|
|||
|
|
|||
|
In addition, cdll provides a flexible command prompt facility that allows,
|
|||
|
for example, a directory name in colour that is truncated from the left
|
|||
|
if it gets too long.
|
|||
|
|
|||
|
|
|||
|
Setting up cdll
|
|||
|
===============
|
|||
|
|
|||
|
Copy cdll to either your local home directory or a central directory
|
|||
|
such as /usr/bin (this will require root access).
|
|||
|
|
|||
|
Copy the file cdfile to your home directory. It will require read and
|
|||
|
write access. This a default file that contains a directory stack and
|
|||
|
special entries.
|
|||
|
|
|||
|
To replace the cd command you must add commands to your login script.
|
|||
|
The login script is one or more of:
|
|||
|
|
|||
|
/etc/profile
|
|||
|
~/.bash_profile
|
|||
|
~/.bash_login
|
|||
|
~/.profile
|
|||
|
~/.bashrc
|
|||
|
/etc/bash.bashrc.local
|
|||
|
|
|||
|
To setup your login, ~/.bashrc is recommended, for global (and root) setup
|
|||
|
add the commands to /etc/bash.bashrc.local
|
|||
|
|
|||
|
To set up on login, add the command:
|
|||
|
. <dir>/cdll
|
|||
|
For example if cdll is in your local home directory:
|
|||
|
. ~/cdll
|
|||
|
If in /usr/bin then:
|
|||
|
. /usr/bin/cdll
|
|||
|
|
|||
|
If you want to use this instead of the buitin cd command then add:
|
|||
|
alias cd='cd_new'
|
|||
|
We would also recommend the following commands:
|
|||
|
alias @='cd_new @'
|
|||
|
cd -U
|
|||
|
cd -D
|
|||
|
|
|||
|
If you want to use cdll's prompt facilty then add the following:
|
|||
|
CDL_PROMPTLEN=nn
|
|||
|
Where nn is a number described below. Initially 99 would be suitable
|
|||
|
number.
|
|||
|
|
|||
|
Thus the script looks something like this:
|
|||
|
|
|||
|
######################################################################
|
|||
|
# CD Setup
|
|||
|
######################################################################
|
|||
|
CDL_PROMPTLEN=21 # Allow a prompt length of up to 21 characters
|
|||
|
. /usr/bin/cdll # Initialise cdll
|
|||
|
alias cd='cd_new' # Replace the built in cd command
|
|||
|
alias @='cd_new @' # Allow @ at the prompt to display history
|
|||
|
cd -U # Upload directories
|
|||
|
cd -D # Set default action to non-posix
|
|||
|
######################################################################
|
|||
|
|
|||
|
The full meaning of these commands will become clear later.
|
|||
|
|
|||
|
There are a couple of caveats. If another program changes the directory
|
|||
|
without calling cdll, then the directory won't be put on the stack and
|
|||
|
also if the prompt facility is used then this will not be updated. Two
|
|||
|
programs that can do this are pushd and popd. To update the prompt and
|
|||
|
stack simply enter:
|
|||
|
|
|||
|
cd .
|
|||
|
|
|||
|
Note that if the previous entry on the stack is the current directory
|
|||
|
then the stack is not updated.
|
|||
|
|
|||
|
Usage
|
|||
|
=====
|
|||
|
cd [dir] [0-9] [@[s|h] [-g <dir>] [-d] [-D] [-r<n>]
|
|||
|
[dir|0-9] [-R<n>] [<dir>|0-9] [-s<n>] [-S<n>]
|
|||
|
[-u] [-U] [-f] [-F] [-h] [-H] [-v]
|
|||
|
|
|||
|
<dir> Go to directory
|
|||
|
0-n Goto previous directory (0 is previous,
|
|||
|
1 is last but 1, etc.)
|
|||
|
n is up to max history (default is 50)
|
|||
|
@ List history and special entries (Usually available as $ @)
|
|||
|
@h List history entries
|
|||
|
@s List special entries
|
|||
|
-g [<dir>] Go to literal name (bypass special names)
|
|||
|
This is to allow access to dirs called '0','1','-h' etc
|
|||
|
-d Change default action - verbose. (See note)
|
|||
|
-D Change default action - silent. (See note)
|
|||
|
-s<n> Go to the special entry <n>
|
|||
|
-S<n> Go to the special entry <n>
|
|||
|
and replace it with the current dir
|
|||
|
-r<n> [<dir>] Go to directory <dir>
|
|||
|
and then put it on special entry <n>
|
|||
|
-R<n> [<dir>] Go to directory <dir>
|
|||
|
and put current dir on special entry <n>
|
|||
|
-a<n> Alternative suggested directory. See note below.
|
|||
|
-f [<file>] File entries to <file>.
|
|||
|
-u [<file>] Update entries from <file>.
|
|||
|
If no filename supplied then default file (~/cdfile) is used
|
|||
|
-F and -U are silent versions
|
|||
|
-v Print version number
|
|||
|
-h Help
|
|||
|
-H Detailed help
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Examples
|
|||
|
========
|
|||
|
|
|||
|
These examples assume non-default mode is set (that is, cd with no
|
|||
|
parameters will go to the most recent stack directory), that aliases
|
|||
|
have been set up for cd and @ as described above and that cd's prompt
|
|||
|
facility is active and the prompt length is 21 characters.
|
|||
|
|
|||
|
/home/phil$ @
|
|||
|
# List the entries with the @
|
|||
|
History:
|
|||
|
# Output of the @ command
|
|||
|
.....
|
|||
|
# Skipped these entries for brevity
|
|||
|
1 /home/phil/ummdev S1 /home/phil/perl
|
|||
|
# Most recent two history entries
|
|||
|
0 /home/phil/perl/eg S0 /home/phil/umm/ummdev
|
|||
|
# and two special entries are shown
|
|||
|
|
|||
|
/home/phil$ cd /home/phil/utils/Cdll
|
|||
|
# Now change directories
|
|||
|
/home/phil/utils/Cdll$ @
|
|||
|
# Prompt reflects the directory.
|
|||
|
History:
|
|||
|
# New history
|
|||
|
.....
|
|||
|
1 /home/phil/perl/eg S1 /home/phil/perl
|
|||
|
# History entry 0 has moved to 1
|
|||
|
0 /home/phil S0 /home/phil/umm/ummdev
|
|||
|
# and the most recent has entered
|
|||
|
|
|||
|
To go to a history entry:
|
|||
|
|
|||
|
/home/phil/utils/Cdll$ cd 1
|
|||
|
# Go to history entry 1.
|
|||
|
/home/phil/perl/eg$
|
|||
|
# Current directory is now what was 1
|
|||
|
|
|||
|
To go to a special entry:
|
|||
|
|
|||
|
/home/phil/perl/eg$ cd -s1
|
|||
|
# Go to special entry 1
|
|||
|
/home/phil/umm/ummdev$
|
|||
|
# Current directory is S1
|
|||
|
|
|||
|
To go to a directory called, for example, 1:
|
|||
|
|
|||
|
/home/phil$ cd -g 1
|
|||
|
# -g ignores the special meaning of 1
|
|||
|
/home/phil/1$
|
|||
|
|
|||
|
To put current directory on the special list as S1:
|
|||
|
cd -r1 . # OR
|
|||
|
cd -R1 . # These have the same effect if the directory is
|
|||
|
#+ . (the current directory)
|
|||
|
|
|||
|
To go to a directory and add it as a special
|
|||
|
The directory for -r<n> or -R<n> may be a number.
|
|||
|
For example:
|
|||
|
$ cd -r3 4 Go to history entry 4 and put it on special entry 3
|
|||
|
$ cd -R3 4 Put current dir on the special entry 3 and go to
|
|||
|
history entry 4
|
|||
|
$ cd -s3 Go to special entry 3
|
|||
|
|
|||
|
Note that commands R,r,S and s may be used without a number and
|
|||
|
refer to 0:
|
|||
|
$ cd -s Go to special entry 0
|
|||
|
$ cd -S Go to special entry 0 and make special entry 0
|
|||
|
current dir
|
|||
|
$ cd -r 1 Go to history entry 1 and put it on special entry 0
|
|||
|
$ cd -r Go to history entry 0 and put it on special entry 0
|
|||
|
|
|||
|
|
|||
|
Alternative suggested directories:
|
|||
|
|
|||
|
If a directory is not found, then CD will suggest any
|
|||
|
possibilities. These are directories starting with the same letters
|
|||
|
and if any are found they are listed prefixed with -a<n>
|
|||
|
where <n> is a number. It's possible to go to the directory
|
|||
|
by entering cd -a<n> on the command line.
|
|||
|
|
|||
|
Use cd -d or -D to change default cd action. cd -H will show
|
|||
|
current action.
|
|||
|
|
|||
|
The history entries (0-n) are stored in the environment variables
|
|||
|
CD[0] - CD[n]
|
|||
|
Similarly the special directories S0 - 9 are in the environment
|
|||
|
variable CDS[0] - CDS[9]
|
|||
|
and may be accessed from the command line, for example:
|
|||
|
|
|||
|
ls -l ${CDS[3]}
|
|||
|
cat ${CD[8]}/file.txt
|
|||
|
|
|||
|
The default pathname for the -f and -u commands is ~
|
|||
|
The default filename for the -f and -u commands is cdfile
|
|||
|
|
|||
|
|
|||
|
Configuration
|
|||
|
=============
|
|||
|
|
|||
|
The following environment variables can be set:
|
|||
|
|
|||
|
CDL_PROMPTLEN - Set to the length of prompt you require.
|
|||
|
Prompt string is set to the right characters of the current
|
|||
|
directory. If not set, then prompt is left unchanged. Note
|
|||
|
that this is the number of characters that the directory is
|
|||
|
shortened to, not the total characters in the prompt.
|
|||
|
|
|||
|
CDL_PROMPT_PRE - Set to the string to prefix the prompt.
|
|||
|
Default is:
|
|||
|
non-root: "\\[\\e[01;34m\\]" (sets colour to blue).
|
|||
|
root: "\\[\\e[01;31m\\]" (sets colour to red).
|
|||
|
|
|||
|
CDL_PROMPT_POST - Set to the string to suffix the prompt.
|
|||
|
Default is:
|
|||
|
non-root: "\\[\\e[00m\\]$"
|
|||
|
(resets colour and displays $).
|
|||
|
root: "\\[\\e[00m\\]#"
|
|||
|
(resets colour and displays #).
|
|||
|
|
|||
|
Note:
|
|||
|
CDL_PROMPT_PRE & _POST only t
|
|||
|
|
|||
|
CDPath - Set the default path for the -f & -u options.
|
|||
|
Default is home directory
|
|||
|
CDFile - Set the default filename for the -f & -u options.
|
|||
|
Default is cdfile
|
|||
|
|
|||
|
|
|||
|
There are three variables defined in the file cdll which control the
|
|||
|
number of entries stored or displayed. They are in the section labeled
|
|||
|
'Initialisation here' towards the end of the file.
|
|||
|
|
|||
|
cd_maxhistory - The number of history entries stored.
|
|||
|
Default is 50.
|
|||
|
cd_maxspecial - The number of special entries allowed.
|
|||
|
Default is 9.
|
|||
|
cd_histcount - The number of history and special entries
|
|||
|
displayed. Default is 9.
|
|||
|
|
|||
|
Note that cd_maxspecial should be >= cd_histcount to avoid displaying
|
|||
|
special entries that can't be set.
|
|||
|
|
|||
|
|
|||
|
Version: 1.2.1 Date: 24-MAY-2003
|
|||
|
|
|||
|
DOCUMENTATION</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="SOUNDCARDON"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-34. A soundcard setup script</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
# soundcard-on.sh
|
|||
|
|
|||
|
# Script author: Mkarcher
|
|||
|
# http://www.thinkwiki.org/wiki ...
|
|||
|
# /Script_for_configuring_the_CS4239_sound_chip_in_PnP_mode
|
|||
|
# ABS Guide author made minor changes and added comments.
|
|||
|
# Couldn't contact script author to ask for permission to use, but ...
|
|||
|
#+ the script was released under the FDL,
|
|||
|
#+ so its use here should be both legal and ethical.
|
|||
|
|
|||
|
# Sound-via-pnp-script for Thinkpad 600E
|
|||
|
#+ and possibly other computers with onboard CS4239/CS4610
|
|||
|
#+ that do not work with the PCI driver
|
|||
|
#+ and are not recognized by the PnP code of snd-cs4236.
|
|||
|
# Also for some 770-series Thinkpads, such as the 770x.
|
|||
|
# Run as root user, of course.
|
|||
|
#
|
|||
|
# These are old and very obsolete laptop computers,
|
|||
|
#+ but this particular script is very instructive,
|
|||
|
#+ as it shows how to set up and hack device files.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# Search for sound card pnp device:
|
|||
|
|
|||
|
for dev in /sys/bus/pnp/devices/*
|
|||
|
do
|
|||
|
grep CSC0100 $dev/id > /dev/null && WSSDEV=$dev
|
|||
|
grep CSC0110 $dev/id > /dev/null && CTLDEV=$dev
|
|||
|
done
|
|||
|
# On 770x:
|
|||
|
# WSSDEV = /sys/bus/pnp/devices/00:07
|
|||
|
# CTLDEV = /sys/bus/pnp/devices/00:06
|
|||
|
# These are symbolic links to /sys/devices/pnp0/ ...
|
|||
|
|
|||
|
|
|||
|
# Activate devices:
|
|||
|
# Thinkpad boots with devices disabled unless "fast boot" is turned off
|
|||
|
#+ (in BIOS).
|
|||
|
|
|||
|
echo activate > $WSSDEV/resources
|
|||
|
echo activate > $CTLDEV/resources
|
|||
|
|
|||
|
|
|||
|
# Parse resource settings.
|
|||
|
|
|||
|
{ read # Discard "state = active" (see below).
|
|||
|
read bla port1
|
|||
|
read bla port2
|
|||
|
read bla port3
|
|||
|
read bla irq
|
|||
|
read bla dma1
|
|||
|
read bla dma2
|
|||
|
# The "bla's" are labels in the first field: "io," "state," etc.
|
|||
|
# These are discarded.
|
|||
|
|
|||
|
# Hack: with PnPBIOS: ports are: port1: WSS, port2:
|
|||
|
#+ OPL, port3: sb (unneeded)
|
|||
|
# with ACPI-PnP:ports are: port1: OPL, port2: sb, port3: WSS
|
|||
|
# (ACPI bios seems to be wrong here, the PnP-card-code in snd-cs4236.c
|
|||
|
#+ uses the PnPBIOS port order)
|
|||
|
# Detect port order using the fixed OPL port as reference.
|
|||
|
if [ ${port2%%-*} = 0x388 ]
|
|||
|
# ^^^^ Strip out everything following hyphen in port address.
|
|||
|
# So, if port1 is 0x530-0x537
|
|||
|
#+ we're left with 0x530 -- the start address of the port.
|
|||
|
then
|
|||
|
# PnPBIOS: usual order
|
|||
|
port=${port1%%-*}
|
|||
|
oplport=${port2%%-*}
|
|||
|
else
|
|||
|
# ACPI: mixed-up order
|
|||
|
port=${port3%%-*}
|
|||
|
oplport=${port1%%-*}
|
|||
|
fi
|
|||
|
} < $WSSDEV/resources
|
|||
|
# To see what's going on here:
|
|||
|
# ---------------------------
|
|||
|
# cat /sys/devices/pnp0/00:07/resources
|
|||
|
#
|
|||
|
# state = active
|
|||
|
# io 0x530-0x537
|
|||
|
# io 0x388-0x38b
|
|||
|
# io 0x220-0x233
|
|||
|
# irq 5
|
|||
|
# dma 1
|
|||
|
# dma 0
|
|||
|
# ^^^ "bla" labels in first field (discarded).
|
|||
|
|
|||
|
|
|||
|
{ read # Discard first line, as above.
|
|||
|
read bla port1
|
|||
|
cport=${port1%%-*}
|
|||
|
# ^^^^
|
|||
|
# Just want _start_ address of port.
|
|||
|
} < $CTLDEV/resources
|
|||
|
|
|||
|
|
|||
|
# Load the module:
|
|||
|
|
|||
|
modprobe --ignore-install snd-cs4236 port=$port cport=$cport\
|
|||
|
fm_port=$oplport irq=$irq dma1=$dma1 dma2=$dma2 isapnp=0 index=0
|
|||
|
# See the modprobe manpage.
|
|||
|
|
|||
|
exit $?</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><P
|
|||
|
><A
|
|||
|
NAME="FINDSPLIT0"
|
|||
|
></A
|
|||
|
></P
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="FINDSPLIT"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-35. Locating split paragraphs in a text file</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
# find-splitpara.sh
|
|||
|
# Finds split paragraphs in a text file,
|
|||
|
#+ and tags the line numbers.
|
|||
|
|
|||
|
|
|||
|
ARGCOUNT=1 # Expect one arg.
|
|||
|
OFF=0 # Flag states.
|
|||
|
ON=1
|
|||
|
E_WRONGARGS=85
|
|||
|
|
|||
|
file="$1" # Target filename.
|
|||
|
lineno=1 # Line number. Start at 1.
|
|||
|
Flag=$OFF # Blank line flag.
|
|||
|
|
|||
|
if [ $# -ne "$ARGCOUNT" ]
|
|||
|
then
|
|||
|
echo "Usage: `basename $0` FILENAME"
|
|||
|
exit $E_WRONGARGS
|
|||
|
fi
|
|||
|
|
|||
|
file_read () # Scan file for pattern, then print line.
|
|||
|
{
|
|||
|
while read line
|
|||
|
do
|
|||
|
|
|||
|
if [[ "$line" =~ ^[a-z] && $Flag -eq $ON ]]
|
|||
|
then # Line begins with lowercase character, following blank line.
|
|||
|
echo -n "$lineno:: "
|
|||
|
echo "$line"
|
|||
|
fi
|
|||
|
|
|||
|
|
|||
|
if [[ "$line" =~ ^$ ]]
|
|||
|
then # If blank line,
|
|||
|
Flag=$ON #+ set flag.
|
|||
|
else
|
|||
|
Flag=$OFF
|
|||
|
fi
|
|||
|
|
|||
|
((lineno++))
|
|||
|
|
|||
|
done
|
|||
|
} < $file # Redirect file into function's stdin.
|
|||
|
|
|||
|
file_read
|
|||
|
|
|||
|
|
|||
|
exit $?
|
|||
|
|
|||
|
|
|||
|
# ----------------------------------------------------------------
|
|||
|
This is line one of an example paragraph, bla, bla, bla.
|
|||
|
This is line two, and line three should follow on next line, but
|
|||
|
|
|||
|
there is a blank line separating the two parts of the paragraph.
|
|||
|
# ----------------------------------------------------------------
|
|||
|
|
|||
|
Running this script on a file containing the above paragraph
|
|||
|
yields:
|
|||
|
|
|||
|
4:: there is a blank line separating the two parts of the paragraph.
|
|||
|
|
|||
|
|
|||
|
There will be additional output for all the other split paragraphs
|
|||
|
in the target file.</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><P
|
|||
|
><A
|
|||
|
NAME="INSERTIONSORT0"
|
|||
|
></A
|
|||
|
></P
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="INSERTIONSORT"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-36. Insertion sort</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
# insertion-sort.bash: Insertion sort implementation in Bash
|
|||
|
# Heavy use of Bash array features:
|
|||
|
#+ (string) slicing, merging, etc
|
|||
|
# URL: http://www.lugmen.org.ar/~jjo/jjotip/insertion-sort.bash.d
|
|||
|
#+ /insertion-sort.bash.sh
|
|||
|
#
|
|||
|
# Author: JuanJo Ciarlante <jjo@irrigacion.gov.ar>
|
|||
|
# Lightly reformatted by ABS Guide author.
|
|||
|
# License: GPLv2
|
|||
|
# Used in ABS Guide with author's permission (thanks!).
|
|||
|
#
|
|||
|
# Test with: ./insertion-sort.bash -t
|
|||
|
# Or: bash insertion-sort.bash -t
|
|||
|
# The following *doesn't* work:
|
|||
|
# sh insertion-sort.bash -t
|
|||
|
# Why not? Hint: which Bash-specific features are disabled
|
|||
|
#+ when running a script by 'sh script.sh'?
|
|||
|
#
|
|||
|
: ${DEBUG:=0} # Debug, override with: DEBUG=1 ./scriptname . . .
|
|||
|
# Parameter substitution -- set DEBUG to 0 if not previously set.
|
|||
|
|
|||
|
# Global array: "list"
|
|||
|
typeset -a list
|
|||
|
# Load whitespace-separated numbers from stdin.
|
|||
|
if [ "$1" = "-t" ]; then
|
|||
|
DEBUG=1
|
|||
|
read -a list < <( od -Ad -w24 -t u2 /dev/urandom ) # Random list.
|
|||
|
# ^ ^ process substition
|
|||
|
else
|
|||
|
read -a list
|
|||
|
fi
|
|||
|
numelem=${#list[*]}
|
|||
|
|
|||
|
# Shows the list, marking the element whose index is $1
|
|||
|
#+ by surrounding it with the two chars passed as $2.
|
|||
|
# Whole line prefixed with $3.
|
|||
|
showlist()
|
|||
|
{
|
|||
|
echo "$3"${list[@]:0:$1} ${2:0:1}${list[$1]}${2:1:1} ${list[@]:$1+1};
|
|||
|
}
|
|||
|
|
|||
|
# Loop _pivot_ -- from second element to end of list.
|
|||
|
for(( i=1; i<numelem; i++ )) do
|
|||
|
((DEBUG))&&showlist i "[]" " "
|
|||
|
# From current _pivot_, back to first element.
|
|||
|
for(( j=i; j; j-- )) do
|
|||
|
# Search for the 1st elem. less than current "pivot" . . .
|
|||
|
[[ "${list[j-1]}" -le "${list[i]}" ]] && break
|
|||
|
done
|
|||
|
(( i==j )) && continue ## No insertion was needed for this element.
|
|||
|
# . . . Move list[i] (pivot) to the left of list[j]:
|
|||
|
list=(${list[@]:0:j} ${list[i]} ${list[j]}\
|
|||
|
# {0,j-1} {i} {j}
|
|||
|
${list[@]:j+1:i-(j+1)} ${list[@]:i+1})
|
|||
|
# {j+1,i-1} {i+1,last}
|
|||
|
((DEBUG))&&showlist j "<>" "*"
|
|||
|
done
|
|||
|
|
|||
|
|
|||
|
echo
|
|||
|
echo "------"
|
|||
|
echo $'Result:\n'${list[@]}
|
|||
|
|
|||
|
exit $?</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="STDDEV"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-37. Standard Deviation</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
# sd.sh: Standard Deviation
|
|||
|
|
|||
|
# The Standard Deviation indicates how consistent a set of data is.
|
|||
|
# It shows to what extent the individual data points deviate from the
|
|||
|
#+ arithmetic mean, i.e., how much they "bounce around" (or cluster).
|
|||
|
# It is essentially the average deviation-distance of the
|
|||
|
#+ data points from the mean.
|
|||
|
|
|||
|
# =========================================================== #
|
|||
|
# To calculate the Standard Deviation:
|
|||
|
#
|
|||
|
# 1 Find the arithmetic mean (average) of all the data points.
|
|||
|
# 2 Subtract each data point from the arithmetic mean,
|
|||
|
# and square that difference.
|
|||
|
# 3 Add all of the individual difference-squares in # 2.
|
|||
|
# 4 Divide the sum in # 3 by the number of data points.
|
|||
|
# This is known as the "variance."
|
|||
|
# 5 The square root of # 4 gives the Standard Deviation.
|
|||
|
# =========================================================== #
|
|||
|
|
|||
|
count=0 # Number of data points; global.
|
|||
|
SC=9 # Scale to be used by bc. Nine decimal places.
|
|||
|
E_DATAFILE=90 # Data file error.
|
|||
|
|
|||
|
# ----------------- Set data file ---------------------
|
|||
|
if [ ! -z "$1" ] # Specify filename as cmd-line arg?
|
|||
|
then
|
|||
|
datafile="$1" # ASCII text file,
|
|||
|
else #+ one (numerical) data point per line!
|
|||
|
datafile=sample.dat
|
|||
|
fi # See example data file, below.
|
|||
|
|
|||
|
if [ ! -e "$datafile" ]
|
|||
|
then
|
|||
|
echo "\""$datafile"\" does not exist!"
|
|||
|
exit $E_DATAFILE
|
|||
|
fi
|
|||
|
# -----------------------------------------------------
|
|||
|
|
|||
|
|
|||
|
arith_mean ()
|
|||
|
{
|
|||
|
local rt=0 # Running total.
|
|||
|
local am=0 # Arithmetic mean.
|
|||
|
local ct=0 # Number of data points.
|
|||
|
|
|||
|
while read value # Read one data point at a time.
|
|||
|
do
|
|||
|
rt=$(echo "scale=$SC; $rt + $value" | bc)
|
|||
|
(( ct++ ))
|
|||
|
done
|
|||
|
|
|||
|
am=$(echo "scale=$SC; $rt / $ct" | bc)
|
|||
|
|
|||
|
echo $am; return $ct # This function "returns" TWO values!
|
|||
|
# Caution: This little trick will not work if $ct > 255!
|
|||
|
# To handle a larger number of data points,
|
|||
|
#+ simply comment out the "return $ct" above.
|
|||
|
} <"$datafile" # Feed in data file.
|
|||
|
|
|||
|
sd ()
|
|||
|
{
|
|||
|
mean1=$1 # Arithmetic mean (passed to function).
|
|||
|
n=$2 # How many data points.
|
|||
|
sum2=0 # Sum of squared differences ("variance").
|
|||
|
avg2=0 # Average of $sum2.
|
|||
|
sdev=0 # Standard Deviation.
|
|||
|
|
|||
|
while read value # Read one line at a time.
|
|||
|
do
|
|||
|
diff=$(echo "scale=$SC; $mean1 - $value" | bc)
|
|||
|
# Difference between arith. mean and data point.
|
|||
|
dif2=$(echo "scale=$SC; $diff * $diff" | bc) # Squared.
|
|||
|
sum2=$(echo "scale=$SC; $sum2 + $dif2" | bc) # Sum of squares.
|
|||
|
done
|
|||
|
|
|||
|
avg2=$(echo "scale=$SC; $sum2 / $n" | bc) # Avg. of sum of squares.
|
|||
|
sdev=$(echo "scale=$SC; sqrt($avg2)" | bc) # Square root =
|
|||
|
echo $sdev # Standard Deviation.
|
|||
|
|
|||
|
} <"$datafile" # Rewinds data file.
|
|||
|
|
|||
|
|
|||
|
# ======================================================= #
|
|||
|
mean=$(arith_mean); count=$? # Two returns from function!
|
|||
|
std_dev=$(sd $mean $count)
|
|||
|
|
|||
|
echo
|
|||
|
echo "Number of data points in \""$datafile"\" = $count"
|
|||
|
echo "Arithmetic mean (average) = $mean"
|
|||
|
echo "Standard Deviation = $std_dev"
|
|||
|
echo
|
|||
|
# ======================================================= #
|
|||
|
|
|||
|
exit
|
|||
|
|
|||
|
# This script could stand some drastic streamlining,
|
|||
|
#+ but not at the cost of reduced legibility, please.
|
|||
|
|
|||
|
|
|||
|
# ++++++++++++++++++++++++++++++++++++++++ #
|
|||
|
# A sample data file (sample1.dat):
|
|||
|
|
|||
|
# 18.35
|
|||
|
# 19.0
|
|||
|
# 18.88
|
|||
|
# 18.91
|
|||
|
# 18.64
|
|||
|
|
|||
|
|
|||
|
# $ sh sd.sh sample1.dat
|
|||
|
|
|||
|
# Number of data points in "sample1.dat" = 5
|
|||
|
# Arithmetic mean (average) = 18.756000000
|
|||
|
# Standard Deviation = .235338054
|
|||
|
# ++++++++++++++++++++++++++++++++++++++++ #</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="PADSW"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-38. A <I
|
|||
|
CLASS="FIRSTTERM"
|
|||
|
>pad</I
|
|||
|
> file generator for shareware
|
|||
|
authors</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
# pad.sh
|
|||
|
|
|||
|
#######################################################
|
|||
|
# PAD (xml) file creator
|
|||
|
#+ Written by Mendel Cooper <thegrendel.abs@gmail.com>.
|
|||
|
#+ Released to the Public Domain.
|
|||
|
#
|
|||
|
# Generates a "PAD" descriptor file for shareware
|
|||
|
#+ packages, according to the specifications
|
|||
|
#+ of the ASP.
|
|||
|
# http://www.asp-shareware.org/pad
|
|||
|
#######################################################
|
|||
|
|
|||
|
|
|||
|
# Accepts (optional) save filename as a command-line argument.
|
|||
|
if [ -n "$1" ]
|
|||
|
then
|
|||
|
savefile=$1
|
|||
|
else
|
|||
|
savefile=save_file.xml # Default save_file name.
|
|||
|
fi
|
|||
|
|
|||
|
|
|||
|
# ===== PAD file headers =====
|
|||
|
HDR1="<?xml version=\"1.0\" encoding=\"Windows-1252\" ?>"
|
|||
|
HDR2="<XML_DIZ_INFO>"
|
|||
|
HDR3="<MASTER_PAD_VERSION_INFO>"
|
|||
|
HDR4="\t<MASTER_PAD_VERSION>1.15</MASTER_PAD_VERSION>"
|
|||
|
HDR5="\t<MASTER_PAD_INFO>Portable Application Description, or PAD
|
|||
|
for short, is a data set that is used by shareware authors to
|
|||
|
disseminate information to anyone interested in their software products.
|
|||
|
To find out more go to http://www.asp-shareware.org/pad</MASTER_PAD_INFO>"
|
|||
|
HDR6="</MASTER_PAD_VERSION_INFO>"
|
|||
|
# ============================
|
|||
|
|
|||
|
|
|||
|
fill_in ()
|
|||
|
{
|
|||
|
if [ -z "$2" ]
|
|||
|
then
|
|||
|
echo -n "$1? " # Get user input.
|
|||
|
else
|
|||
|
echo -n "$1 $2? " # Additional query?
|
|||
|
fi
|
|||
|
|
|||
|
read var # May paste to fill in field.
|
|||
|
# This shows how flexible "read" can be.
|
|||
|
|
|||
|
if [ -z "$var" ]
|
|||
|
then
|
|||
|
echo -e "\t\t<$1 />" >>$savefile # Indent with 2 tabs.
|
|||
|
return
|
|||
|
else
|
|||
|
echo -e "\t\t<$1>$var</$1>" >>$savefile
|
|||
|
return ${#var} # Return length of input string.
|
|||
|
fi
|
|||
|
}
|
|||
|
|
|||
|
check_field_length () # Check length of program description fields.
|
|||
|
{
|
|||
|
# $1 = maximum field length
|
|||
|
# $2 = actual field length
|
|||
|
if [ "$2" -gt "$1" ]
|
|||
|
then
|
|||
|
echo "Warning: Maximum field length of $1 characters exceeded!"
|
|||
|
fi
|
|||
|
}
|
|||
|
|
|||
|
clear # Clear screen.
|
|||
|
echo "PAD File Creator"
|
|||
|
echo "--- ---- -------"
|
|||
|
echo
|
|||
|
|
|||
|
# Write File Headers to file.
|
|||
|
echo $HDR1 >$savefile
|
|||
|
echo $HDR2 >>$savefile
|
|||
|
echo $HDR3 >>$savefile
|
|||
|
echo -e $HDR4 >>$savefile
|
|||
|
echo -e $HDR5 >>$savefile
|
|||
|
echo $HDR6 >>$savefile
|
|||
|
|
|||
|
|
|||
|
# Company_Info
|
|||
|
echo "COMPANY INFO"
|
|||
|
CO_HDR="Company_Info"
|
|||
|
echo "<$CO_HDR>" >>$savefile
|
|||
|
|
|||
|
fill_in Company_Name
|
|||
|
fill_in Address_1
|
|||
|
fill_in Address_2
|
|||
|
fill_in City_Town
|
|||
|
fill_in State_Province
|
|||
|
fill_in Zip_Postal_Code
|
|||
|
fill_in Country
|
|||
|
|
|||
|
# If applicable:
|
|||
|
# fill_in ASP_Member "[Y/N]"
|
|||
|
# fill_in ASP_Member_Number
|
|||
|
# fill_in ESC_Member "[Y/N]"
|
|||
|
|
|||
|
fill_in Company_WebSite_URL
|
|||
|
|
|||
|
clear # Clear screen between sections.
|
|||
|
|
|||
|
# Contact_Info
|
|||
|
echo "CONTACT INFO"
|
|||
|
CONTACT_HDR="Contact_Info"
|
|||
|
echo "<$CONTACT_HDR>" >>$savefile
|
|||
|
fill_in Author_First_Name
|
|||
|
fill_in Author_Last_Name
|
|||
|
fill_in Author_Email
|
|||
|
fill_in Contact_First_Name
|
|||
|
fill_in Contact_Last_Name
|
|||
|
fill_in Contact_Email
|
|||
|
echo -e "\t</$CONTACT_HDR>" >>$savefile
|
|||
|
# END Contact_Info
|
|||
|
|
|||
|
clear
|
|||
|
|
|||
|
# Support_Info
|
|||
|
echo "SUPPORT INFO"
|
|||
|
SUPPORT_HDR="Support_Info"
|
|||
|
echo "<$SUPPORT_HDR>" >>$savefile
|
|||
|
fill_in Sales_Email
|
|||
|
fill_in Support_Email
|
|||
|
fill_in General_Email
|
|||
|
fill_in Sales_Phone
|
|||
|
fill_in Support_Phone
|
|||
|
fill_in General_Phone
|
|||
|
fill_in Fax_Phone
|
|||
|
echo -e "\t</$SUPPORT_HDR>" >>$savefile
|
|||
|
# END Support_Info
|
|||
|
|
|||
|
echo "</$CO_HDR>" >>$savefile
|
|||
|
# END Company_Info
|
|||
|
|
|||
|
clear
|
|||
|
|
|||
|
# Program_Info
|
|||
|
echo "PROGRAM INFO"
|
|||
|
PROGRAM_HDR="Program_Info"
|
|||
|
echo "<$PROGRAM_HDR>" >>$savefile
|
|||
|
fill_in Program_Name
|
|||
|
fill_in Program_Version
|
|||
|
fill_in Program_Release_Month
|
|||
|
fill_in Program_Release_Day
|
|||
|
fill_in Program_Release_Year
|
|||
|
fill_in Program_Cost_Dollars
|
|||
|
fill_in Program_Cost_Other
|
|||
|
fill_in Program_Type "[Shareware/Freeware/GPL]"
|
|||
|
fill_in Program_Release_Status "[Beta, Major Upgrade, etc.]"
|
|||
|
fill_in Program_Install_Support
|
|||
|
fill_in Program_OS_Support "[Win9x/Win2k/Linux/etc.]"
|
|||
|
fill_in Program_Language "[English/Spanish/etc.]"
|
|||
|
|
|||
|
echo; echo
|
|||
|
|
|||
|
# File_Info
|
|||
|
echo "FILE INFO"
|
|||
|
FILEINFO_HDR="File_Info"
|
|||
|
echo "<$FILEINFO_HDR>" >>$savefile
|
|||
|
fill_in Filename_Versioned
|
|||
|
fill_in Filename_Previous
|
|||
|
fill_in Filename_Generic
|
|||
|
fill_in Filename_Long
|
|||
|
fill_in File_Size_Bytes
|
|||
|
fill_in File_Size_K
|
|||
|
fill_in File_Size_MB
|
|||
|
echo -e "\t</$FILEINFO_HDR>" >>$savefile
|
|||
|
# END File_Info
|
|||
|
|
|||
|
clear
|
|||
|
|
|||
|
# Expire_Info
|
|||
|
echo "EXPIRE INFO"
|
|||
|
EXPIRE_HDR="Expire_Info"
|
|||
|
echo "<$EXPIRE_HDR>" >>$savefile
|
|||
|
fill_in Has_Expire_Info "Y/N"
|
|||
|
fill_in Expire_Count
|
|||
|
fill_in Expire_Based_On
|
|||
|
fill_in Expire_Other_Info
|
|||
|
fill_in Expire_Month
|
|||
|
fill_in Expire_Day
|
|||
|
fill_in Expire_Year
|
|||
|
echo -e "\t</$EXPIRE_HDR>" >>$savefile
|
|||
|
# END Expire_Info
|
|||
|
|
|||
|
clear
|
|||
|
|
|||
|
# More Program_Info
|
|||
|
echo "ADDITIONAL PROGRAM INFO"
|
|||
|
fill_in Program_Change_Info
|
|||
|
fill_in Program_Specific_Category
|
|||
|
fill_in Program_Categories
|
|||
|
fill_in Includes_JAVA_VM "[Y/N]"
|
|||
|
fill_in Includes_VB_Runtime "[Y/N]"
|
|||
|
fill_in Includes_DirectX "[Y/N]"
|
|||
|
# END More Program_Info
|
|||
|
|
|||
|
echo "</$PROGRAM_HDR>" >>$savefile
|
|||
|
# END Program_Info
|
|||
|
|
|||
|
clear
|
|||
|
|
|||
|
# Program Description
|
|||
|
echo "PROGRAM DESCRIPTIONS"
|
|||
|
PROGDESC_HDR="Program_Descriptions"
|
|||
|
echo "<$PROGDESC_HDR>" >>$savefile
|
|||
|
|
|||
|
LANG="English"
|
|||
|
echo "<$LANG>" >>$savefile
|
|||
|
|
|||
|
fill_in Keywords "[comma + space separated]"
|
|||
|
echo
|
|||
|
echo "45, 80, 250, 450, 2000 word program descriptions"
|
|||
|
echo "(may cut and paste into field)"
|
|||
|
# It would be highly appropriate to compose the following
|
|||
|
#+ "Char_Desc" fields with a text editor,
|
|||
|
#+ then cut-and-paste the text into the answer fields.
|
|||
|
echo
|
|||
|
echo " |---------------45 characters---------------|"
|
|||
|
fill_in Char_Desc_45
|
|||
|
check_field_length 45 "$?"
|
|||
|
echo
|
|||
|
fill_in Char_Desc_80
|
|||
|
check_field_length 80 "$?"
|
|||
|
|
|||
|
fill_in Char_Desc_250
|
|||
|
check_field_length 250 "$?"
|
|||
|
|
|||
|
fill_in Char_Desc_450
|
|||
|
fill_in Char_Desc_2000
|
|||
|
|
|||
|
echo "</$LANG>" >>$savefile
|
|||
|
echo "</$PROGDESC_HDR>" >>$savefile
|
|||
|
# END Program Description
|
|||
|
|
|||
|
clear
|
|||
|
echo "Done."; echo; echo
|
|||
|
echo "Save file is: \""$savefile"\""
|
|||
|
|
|||
|
exit 0</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="MANED"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-39. A <I
|
|||
|
CLASS="FIRSTTERM"
|
|||
|
>man page</I
|
|||
|
> editor</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
# maned.sh
|
|||
|
# A rudimentary man page editor
|
|||
|
|
|||
|
# Version: 0.1 (Alpha, probably buggy)
|
|||
|
# Author: Mendel Cooper <thegrendel.abs@gmail.com>
|
|||
|
# Reldate: 16 June 2008
|
|||
|
# License: GPL3
|
|||
|
|
|||
|
|
|||
|
savefile= # Global, used in multiple functions.
|
|||
|
E_NOINPUT=90 # User input missing (error). May or may not be critical.
|
|||
|
|
|||
|
# =========== Markup Tags ============ #
|
|||
|
TopHeader=".TH"
|
|||
|
NameHeader=".SH NAME"
|
|||
|
SyntaxHeader=".SH SYNTAX"
|
|||
|
SynopsisHeader=".SH SYNOPSIS"
|
|||
|
InstallationHeader=".SH INSTALLATION"
|
|||
|
DescHeader=".SH DESCRIPTION"
|
|||
|
OptHeader=".SH OPTIONS"
|
|||
|
FilesHeader=".SH FILES"
|
|||
|
EnvHeader=".SH ENVIRONMENT"
|
|||
|
AuthHeader=".SH AUTHOR"
|
|||
|
BugsHeader=".SH BUGS"
|
|||
|
SeeAlsoHeader=".SH SEE ALSO"
|
|||
|
BOLD=".B"
|
|||
|
# Add more tags, as needed.
|
|||
|
# See groff docs for markup meanings.
|
|||
|
# ==================================== #
|
|||
|
|
|||
|
start ()
|
|||
|
{
|
|||
|
clear # Clear screen.
|
|||
|
echo "ManEd"
|
|||
|
echo "-----"
|
|||
|
echo
|
|||
|
echo "Simple man page creator"
|
|||
|
echo "Author: Mendel Cooper"
|
|||
|
echo "License: GPL3"
|
|||
|
echo; echo; echo
|
|||
|
}
|
|||
|
|
|||
|
progname ()
|
|||
|
{
|
|||
|
echo -n "Program name? "
|
|||
|
read name
|
|||
|
|
|||
|
echo -n "Manpage section? [Hit RETURN for default (\"1\") ] "
|
|||
|
read section
|
|||
|
if [ -z "$section" ]
|
|||
|
then
|
|||
|
section=1 # Most man pages are in section 1.
|
|||
|
fi
|
|||
|
|
|||
|
if [ -n "$name" ]
|
|||
|
then
|
|||
|
savefile=""$name"."$section"" # Filename suffix = section.
|
|||
|
echo -n "$1 " >>$savefile
|
|||
|
name1=$(echo "$name" | tr a-z A-Z) # Change to uppercase,
|
|||
|
#+ per man page convention.
|
|||
|
echo -n "$name1" >>$savefile
|
|||
|
else
|
|||
|
echo "Error! No input." # Mandatory input.
|
|||
|
exit $E_NOINPUT # Critical!
|
|||
|
# Exercise: The script-abort if no filename input is a bit clumsy.
|
|||
|
# Rewrite this section so a default filename is used
|
|||
|
#+ if no input.
|
|||
|
fi
|
|||
|
|
|||
|
echo -n " \"$section\"">>$savefile # Append, always append.
|
|||
|
|
|||
|
echo -n "Version? "
|
|||
|
read ver
|
|||
|
echo -n " \"Version $ver \"">>$savefile
|
|||
|
echo >>$savefile
|
|||
|
|
|||
|
echo -n "Short description [0 - 5 words]? "
|
|||
|
read sdesc
|
|||
|
echo "$NameHeader">>$savefile
|
|||
|
echo ""$BOLD" "$name"">>$savefile
|
|||
|
echo "\- "$sdesc"">>$savefile
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
fill_in ()
|
|||
|
{ # This function more or less copied from "pad.sh" script.
|
|||
|
echo -n "$2? " # Get user input.
|
|||
|
read var # May paste (a single line only!) to fill in field.
|
|||
|
|
|||
|
if [ -n "$var" ]
|
|||
|
then
|
|||
|
echo "$1 " >>$savefile
|
|||
|
echo -n "$var" >>$savefile
|
|||
|
else # Don't append empty field to file.
|
|||
|
return $E_NOINPUT # Not critical here.
|
|||
|
fi
|
|||
|
|
|||
|
echo >>$savefile
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
end ()
|
|||
|
{
|
|||
|
clear
|
|||
|
echo -n "Would you like to view the saved man page (y/n)? "
|
|||
|
read ans
|
|||
|
if [ "$ans" = "n" -o "$ans" = "N" ]; then exit; fi
|
|||
|
exec less "$savefile" # Exit script and hand off control to "less" ...
|
|||
|
#+ ... which formats for viewing man page source.
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
# ---------------------------------------- #
|
|||
|
start
|
|||
|
progname "$TopHeader"
|
|||
|
fill_in "$SynopsisHeader" "Synopsis"
|
|||
|
fill_in "$DescHeader" "Long description"
|
|||
|
# May paste in *single line* of text.
|
|||
|
fill_in "$OptHeader" "Options"
|
|||
|
fill_in "$FilesHeader" "Files"
|
|||
|
fill_in "$AuthHeader" "Author"
|
|||
|
fill_in "$BugsHeader" "Bugs"
|
|||
|
fill_in "$SeeAlsoHeader" "See also"
|
|||
|
# fill_in "$OtherHeader" ... as necessary.
|
|||
|
end # ... exit not needed.
|
|||
|
# ---------------------------------------- #
|
|||
|
|
|||
|
# Note that the generated man page will usually
|
|||
|
#+ require manual fine-tuning with a text editor.
|
|||
|
# However, it's a distinct improvement upon
|
|||
|
#+ writing man source from scratch
|
|||
|
#+ or even editing a blank man page template.
|
|||
|
|
|||
|
# The main deficiency of the script is that it permits
|
|||
|
#+ pasting only a single text line into the input fields.
|
|||
|
# This may be a long, cobbled-together line, which groff
|
|||
|
# will automatically wrap and hyphenate.
|
|||
|
# However, if you want multiple (newline-separated) paragraphs,
|
|||
|
#+ these must be inserted by manual text editing on the
|
|||
|
#+ script-generated man page.
|
|||
|
# Exercise (difficult): Fix this!
|
|||
|
|
|||
|
# This script is not nearly as elaborate as the
|
|||
|
#+ full-featured "manedit" package
|
|||
|
#+ http://freshmeat.net/projects/manedit/
|
|||
|
#+ but it's much easier to use.</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="PETALS"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-40. Petals Around the Rose</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash -i
|
|||
|
# petals.sh
|
|||
|
|
|||
|
#########################################################################
|
|||
|
# Petals Around the Rose #
|
|||
|
# #
|
|||
|
# Version 0.1 Created by Serghey Rodin #
|
|||
|
# Version 0.2 Modded by ABS Guide Author #
|
|||
|
# #
|
|||
|
# License: GPL3 #
|
|||
|
# Used in ABS Guide with permission. #
|
|||
|
# ##################################################################### #
|
|||
|
|
|||
|
hits=0 # Correct guesses.
|
|||
|
WIN=6 # Mastered the game.
|
|||
|
ALMOST=5 # One short of mastery.
|
|||
|
EXIT=exit # Give up early?
|
|||
|
|
|||
|
RANDOM=$$ # Seeds the random number generator from PID of script.
|
|||
|
|
|||
|
|
|||
|
# Bones (ASCII graphics for dice)
|
|||
|
bone1[1]="| |"
|
|||
|
bone1[2]="| o |"
|
|||
|
bone1[3]="| o |"
|
|||
|
bone1[4]="| o o |"
|
|||
|
bone1[5]="| o o |"
|
|||
|
bone1[6]="| o o |"
|
|||
|
bone2[1]="| o |"
|
|||
|
bone2[2]="| |"
|
|||
|
bone2[3]="| o |"
|
|||
|
bone2[4]="| |"
|
|||
|
bone2[5]="| o |"
|
|||
|
bone2[6]="| o o |"
|
|||
|
bone3[1]="| |"
|
|||
|
bone3[2]="| o |"
|
|||
|
bone3[3]="| o |"
|
|||
|
bone3[4]="| o o |"
|
|||
|
bone3[5]="| o o |"
|
|||
|
bone3[6]="| o o |"
|
|||
|
bone="+---------+"
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# Functions
|
|||
|
|
|||
|
instructions () {
|
|||
|
|
|||
|
clear
|
|||
|
echo -n "Do you need instructions? (y/n) "; read ans
|
|||
|
if [ "$ans" = "y" -o "$ans" = "Y" ]; then
|
|||
|
clear
|
|||
|
echo -e '\E[34;47m' # Blue type.
|
|||
|
|
|||
|
# "cat document"
|
|||
|
cat <<INSTRUCTIONSZZZ
|
|||
|
The name of the game is Petals Around the Rose,
|
|||
|
and that name is significant.
|
|||
|
Five dice will roll and you must guess the "answer" for each roll.
|
|||
|
It will be zero or an even number.
|
|||
|
After your guess, you will be told the answer for the roll, but . . .
|
|||
|
that's ALL the information you will get.
|
|||
|
|
|||
|
Six consecutive correct guesses admits you to the
|
|||
|
Fellowship of the Rose.
|
|||
|
INSTRUCTIONSZZZ
|
|||
|
|
|||
|
echo -e "\033[0m" # Turn off blue.
|
|||
|
else clear
|
|||
|
fi
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
fortune ()
|
|||
|
{
|
|||
|
RANGE=7
|
|||
|
FLOOR=0
|
|||
|
number=0
|
|||
|
while [ "$number" -le $FLOOR ]
|
|||
|
do
|
|||
|
number=$RANDOM
|
|||
|
let "number %= $RANGE" # 1 - 6.
|
|||
|
done
|
|||
|
|
|||
|
return $number
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
throw () { # Calculate each individual die.
|
|||
|
fortune; B1=$?
|
|||
|
fortune; B2=$?
|
|||
|
fortune; B3=$?
|
|||
|
fortune; B4=$?
|
|||
|
fortune; B5=$?
|
|||
|
|
|||
|
calc () { # Function embedded within a function!
|
|||
|
case "$1" in
|
|||
|
3 ) rose=2;;
|
|||
|
5 ) rose=4;;
|
|||
|
* ) rose=0;;
|
|||
|
esac # Simplified algorithm.
|
|||
|
# Doesn't really get to the heart of the matter.
|
|||
|
return $rose
|
|||
|
}
|
|||
|
|
|||
|
answer=0
|
|||
|
calc "$B1"; answer=$(expr $answer + $(echo $?))
|
|||
|
calc "$B2"; answer=$(expr $answer + $(echo $?))
|
|||
|
calc "$B3"; answer=$(expr $answer + $(echo $?))
|
|||
|
calc "$B4"; answer=$(expr $answer + $(echo $?))
|
|||
|
calc "$B5"; answer=$(expr $answer + $(echo $?))
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
game ()
|
|||
|
{ # Generate graphic display of dice throw.
|
|||
|
throw
|
|||
|
echo -e "\033[1m" # Bold.
|
|||
|
echo -e "\n"
|
|||
|
echo -e "$bone\t$bone\t$bone\t$bone\t$bone"
|
|||
|
echo -e \
|
|||
|
"${bone1[$B1]}\t${bone1[$B2]}\t${bone1[$B3]}\t${bone1[$B4]}\t${bone1[$B5]}"
|
|||
|
echo -e \
|
|||
|
"${bone2[$B1]}\t${bone2[$B2]}\t${bone2[$B3]}\t${bone2[$B4]}\t${bone2[$B5]}"
|
|||
|
echo -e \
|
|||
|
"${bone3[$B1]}\t${bone3[$B2]}\t${bone3[$B3]}\t${bone3[$B4]}\t${bone3[$B5]}"
|
|||
|
echo -e "$bone\t$bone\t$bone\t$bone\t$bone"
|
|||
|
echo -e "\n\n\t\t"
|
|||
|
echo -e "\033[0m" # Turn off bold.
|
|||
|
echo -n "There are how many petals around the rose? "
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# ============================================================== #
|
|||
|
|
|||
|
instructions
|
|||
|
|
|||
|
while [ "$petal" != "$EXIT" ] # Main loop.
|
|||
|
do
|
|||
|
game
|
|||
|
read petal
|
|||
|
echo "$petal" | grep [0-9] >/dev/null # Filter response for digit.
|
|||
|
# Otherwise just roll dice again.
|
|||
|
if [ "$?" -eq 0 ] # If-loop #1.
|
|||
|
then
|
|||
|
if [ "$petal" == "$answer" ]; then # If-loop #2.
|
|||
|
echo -e "\nCorrect. There are $petal petals around the rose.\n"
|
|||
|
(( hits++ ))
|
|||
|
|
|||
|
if [ "$hits" -eq "$WIN" ]; then # If-loop #3.
|
|||
|
echo -e '\E[31;47m' # Red type.
|
|||
|
echo -e "\033[1m" # Bold.
|
|||
|
echo "You have unraveled the mystery of the Rose Petals!"
|
|||
|
echo "Welcome to the Fellowship of the Rose!!!"
|
|||
|
echo "(You are herewith sworn to secrecy.)"; echo
|
|||
|
echo -e "\033[0m" # Turn off red & bold.
|
|||
|
break # Exit!
|
|||
|
else echo "You have $hits correct so far."; echo
|
|||
|
|
|||
|
if [ "$hits" -eq "$ALMOST" ]; then
|
|||
|
echo "Just one more gets you to the heart of the mystery!"; echo
|
|||
|
fi
|
|||
|
|
|||
|
fi # Close if-loop #3.
|
|||
|
|
|||
|
else
|
|||
|
echo -e "\nWrong. There are $answer petals around the rose.\n"
|
|||
|
hits=0 # Reset number of correct guesses.
|
|||
|
fi # Close if-loop #2.
|
|||
|
|
|||
|
echo -n "Hit ENTER for the next roll, or type \"exit\" to end. "
|
|||
|
read
|
|||
|
if [ "$REPLY" = "$EXIT" ]; then exit
|
|||
|
fi
|
|||
|
|
|||
|
fi # Close if-loop #1.
|
|||
|
|
|||
|
clear
|
|||
|
done # End of main (while) loop.
|
|||
|
|
|||
|
###
|
|||
|
|
|||
|
exit $?
|
|||
|
|
|||
|
# Resources:
|
|||
|
# ---------
|
|||
|
# 1) http://en.wikipedia.org/wiki/Petals_Around_the_Rose
|
|||
|
# (Wikipedia entry.)
|
|||
|
# 2) http://www.borrett.id.au/computing/petals-bg.htm
|
|||
|
# (How Bill Gates coped with the Petals Around the Rose challenge.)</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="QKY"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-41. Quacky: a Perquackey-type word game</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
# qky.sh
|
|||
|
|
|||
|
##############################################################
|
|||
|
# QUACKEY: a somewhat simplified version of Perquackey [TM]. #
|
|||
|
# #
|
|||
|
# Author: Mendel Cooper <thegrendel.abs@gmail.com> #
|
|||
|
# version 0.1.02 03 May, 2008 #
|
|||
|
# License: GPL3 #
|
|||
|
##############################################################
|
|||
|
|
|||
|
WLIST=/usr/share/dict/word.lst
|
|||
|
# ^^^^^^^^ Word list file found here.
|
|||
|
# ASCII word list, one word per line, UNIX format.
|
|||
|
# A suggested list is the script author's "yawl" word list package.
|
|||
|
# http://bash.deta.in/yawl-0.3.2.tar.gz
|
|||
|
# or
|
|||
|
# http://ibiblio.org/pub/Linux/libs/yawl-0.3.2.tar.gz
|
|||
|
|
|||
|
NONCONS=0 # Word not constructable from letter set.
|
|||
|
CONS=1 # Constructable.
|
|||
|
SUCCESS=0
|
|||
|
NG=1
|
|||
|
FAILURE=''
|
|||
|
NULL=0 # Zero out value of letter (if found).
|
|||
|
MINWLEN=3 # Minimum word length.
|
|||
|
MAXCAT=5 # Maximum number of words in a given category.
|
|||
|
PENALTY=200 # General-purpose penalty for unacceptable words.
|
|||
|
total=
|
|||
|
E_DUP=70 # Duplicate word error.
|
|||
|
|
|||
|
TIMEOUT=10 # Time for word input.
|
|||
|
|
|||
|
NVLET=10 # 10 letters for non-vulnerable.
|
|||
|
VULET=13 # 13 letters for vulnerable (not yet implemented!).
|
|||
|
|
|||
|
declare -a Words
|
|||
|
declare -a Status
|
|||
|
declare -a Score=( 0 0 0 0 0 0 0 0 0 0 0 )
|
|||
|
|
|||
|
|
|||
|
letters=( a n s r t m l k p r b c i d s i d z e w u e t f
|
|||
|
e y e r e f e g t g h h i t r s c i t i d i j a t a o l a
|
|||
|
m n a n o v n w o s e l n o s p a q e e r a b r s a o d s
|
|||
|
t g t i t l u e u v n e o x y m r k )
|
|||
|
# Letter distribution table shamelessly borrowed from "Wordy" game,
|
|||
|
#+ ca. 1992, written by a certain fine fellow named Mendel Cooper.
|
|||
|
|
|||
|
declare -a LS
|
|||
|
|
|||
|
numelements=${#letters[@]}
|
|||
|
randseed="$1"
|
|||
|
|
|||
|
instructions ()
|
|||
|
{
|
|||
|
clear
|
|||
|
echo "Welcome to QUACKEY, the anagramming word construction game."; echo
|
|||
|
echo -n "Do you need instructions? (y/n) "; read ans
|
|||
|
|
|||
|
if [ "$ans" = "y" -o "$ans" = "Y" ]; then
|
|||
|
clear
|
|||
|
echo -e '\E[31;47m' # Red foreground. '\E[34;47m' for blue.
|
|||
|
cat <<INSTRUCTION1
|
|||
|
|
|||
|
QUACKEY is a variant of Perquackey [TM].
|
|||
|
The rules are the same, but the scoring is simplified
|
|||
|
and plurals of previously played words are allowed.
|
|||
|
"Vulnerable" play is not yet implemented,
|
|||
|
but it is otherwise feature-complete.
|
|||
|
|
|||
|
As the game begins, the player gets 10 letters.
|
|||
|
The object is to construct valid dictionary words
|
|||
|
of at least 3-letter length from the letterset.
|
|||
|
Each word-length category
|
|||
|
-- 3-letter, 4-letter, 5-letter, ... --
|
|||
|
fills up with the fifth word entered,
|
|||
|
and no further words in that category are accepted.
|
|||
|
|
|||
|
The penalty for too-short (two-letter), duplicate, unconstructable,
|
|||
|
and invalid (not in dictionary) words is -200. The same penalty applies
|
|||
|
to attempts to enter a word in a filled-up category.
|
|||
|
|
|||
|
INSTRUCTION1
|
|||
|
|
|||
|
echo -n "Hit ENTER for next page of instructions. "; read az1
|
|||
|
|
|||
|
cat <<INSTRUCTION2
|
|||
|
|
|||
|
The scoring mostly corresponds to classic Perquackey:
|
|||
|
The first 3-letter word scores 60, plus 10 for each additional one.
|
|||
|
The first 4-letter word scores 120, plus 20 for each additional one.
|
|||
|
The first 5-letter word scores 200, plus 50 for each additional one.
|
|||
|
The first 6-letter word scores 300, plus 100 for each additional one.
|
|||
|
The first 7-letter word scores 500, plus 150 for each additional one.
|
|||
|
The first 8-letter word scores 750, plus 250 for each additional one.
|
|||
|
The first 9-letter word scores 1000, plus 500 for each additional one.
|
|||
|
The first 10-letter word scores 2000, plus 2000 for each additional one.
|
|||
|
|
|||
|
Category completion bonuses are:
|
|||
|
3-letter words 100
|
|||
|
4-letter words 200
|
|||
|
5-letter words 400
|
|||
|
6-letter words 800
|
|||
|
7-letter words 2000
|
|||
|
8-letter words 10000
|
|||
|
This is a simplification of the absurdly baroque Perquackey bonus
|
|||
|
scoring system.
|
|||
|
|
|||
|
INSTRUCTION2
|
|||
|
|
|||
|
echo -n "Hit ENTER for final page of instructions. "; read az1
|
|||
|
|
|||
|
cat <<INSTRUCTION3
|
|||
|
|
|||
|
|
|||
|
Hitting just ENTER for a word entry ends the game.
|
|||
|
|
|||
|
Individual word entry is timed to a maximum of 10 seconds.
|
|||
|
*** Timing out on an entry ends the game. ***
|
|||
|
Aside from that, the game is untimed.
|
|||
|
|
|||
|
--------------------------------------------------
|
|||
|
Game statistics are automatically saved to a file.
|
|||
|
--------------------------------------------------
|
|||
|
|
|||
|
For competitive ("duplicate") play, a previous letterset
|
|||
|
may be duplicated by repeating the script's random seed,
|
|||
|
command-line parameter \$1.
|
|||
|
For example, "qky 7633" specifies the letterset
|
|||
|
c a d i f r h u s k ...
|
|||
|
INSTRUCTION3
|
|||
|
|
|||
|
echo; echo -n "Hit ENTER to begin game. "; read az1
|
|||
|
|
|||
|
echo -e "\033[0m" # Turn off red.
|
|||
|
else clear
|
|||
|
fi
|
|||
|
|
|||
|
clear
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
seed_random ()
|
|||
|
{ # Seed random number generator.
|
|||
|
if [ -n "$randseed" ] # Can specify random seed.
|
|||
|
then #+ for play in competitive mode.
|
|||
|
# RANDOM="$randseed"
|
|||
|
echo "RANDOM seed set to "$randseed""
|
|||
|
else
|
|||
|
randseed="$$" # Or get random seed from process ID.
|
|||
|
echo "RANDOM seed not specified, set to Process ID of script ($$)."
|
|||
|
fi
|
|||
|
|
|||
|
RANDOM="$randseed"
|
|||
|
|
|||
|
echo
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
get_letset ()
|
|||
|
{
|
|||
|
element=0
|
|||
|
echo -n "Letterset:"
|
|||
|
|
|||
|
for lset in $(seq $NVLET)
|
|||
|
do # Pick random letters to fill out letterset.
|
|||
|
LS[element]="${letters[$((RANDOM%numelements))]}"
|
|||
|
((element++))
|
|||
|
done
|
|||
|
|
|||
|
echo
|
|||
|
echo "${LS[@]}"
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
add_word ()
|
|||
|
{
|
|||
|
wrd="$1"
|
|||
|
local idx=0
|
|||
|
|
|||
|
Status[0]=""
|
|||
|
Status[3]=""
|
|||
|
Status[4]=""
|
|||
|
|
|||
|
while [ "${Words[idx]}" != '' ]
|
|||
|
do
|
|||
|
if [ "${Words[idx]}" = "$wrd" ]
|
|||
|
then
|
|||
|
Status[3]="Duplicate-word-PENALTY"
|
|||
|
let "Score[0]= 0 - $PENALTY"
|
|||
|
let "Score[1]-=$PENALTY"
|
|||
|
return $E_DUP
|
|||
|
fi
|
|||
|
|
|||
|
((idx++))
|
|||
|
done
|
|||
|
|
|||
|
Words[idx]="$wrd"
|
|||
|
get_score
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
get_score()
|
|||
|
{
|
|||
|
local wlen=0
|
|||
|
local score=0
|
|||
|
local bonus=0
|
|||
|
local first_word=0
|
|||
|
local add_word=0
|
|||
|
local numwords=0
|
|||
|
|
|||
|
wlen=${#wrd}
|
|||
|
numwords=${Score[wlen]}
|
|||
|
Score[2]=0
|
|||
|
Status[4]="" # Initialize "bonus" to 0.
|
|||
|
|
|||
|
case "$wlen" in
|
|||
|
3) first_word=60
|
|||
|
add_word=10;;
|
|||
|
4) first_word=120
|
|||
|
add_word=20;;
|
|||
|
5) first_word=200
|
|||
|
add_word=50;;
|
|||
|
6) first_word=300
|
|||
|
add_word=100;;
|
|||
|
7) first_word=500
|
|||
|
add_word=150;;
|
|||
|
8) first_word=750
|
|||
|
add_word=250;;
|
|||
|
9) first_word=1000
|
|||
|
add_word=500;;
|
|||
|
10) first_word=2000
|
|||
|
add_word=2000;; # This category modified from original rules!
|
|||
|
esac
|
|||
|
|
|||
|
((Score[wlen]++))
|
|||
|
if [ ${Score[wlen]} -eq $MAXCAT ]
|
|||
|
then # Category completion bonus scoring simplified!
|
|||
|
case $wlen in
|
|||
|
3 ) bonus=100;;
|
|||
|
4 ) bonus=200;;
|
|||
|
5 ) bonus=400;;
|
|||
|
6 ) bonus=800;;
|
|||
|
7 ) bonus=2000;;
|
|||
|
8 ) bonus=10000;;
|
|||
|
esac # Needn't worry about 9's and 10's.
|
|||
|
Status[4]="Category-$wlen-completion***BONUS***"
|
|||
|
Score[2]=$bonus
|
|||
|
else
|
|||
|
Status[4]="" # Erase it.
|
|||
|
fi
|
|||
|
|
|||
|
|
|||
|
let "score = $first_word + $add_word * $numwords"
|
|||
|
if [ "$numwords" -eq 0 ]
|
|||
|
then
|
|||
|
Score[0]=$score
|
|||
|
else
|
|||
|
Score[0]=$add_word
|
|||
|
fi # All this to distinguish last-word score
|
|||
|
#+ from total running score.
|
|||
|
let "Score[1] += ${Score[0]}"
|
|||
|
let "Score[1] += ${Score[2]}"
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
get_word ()
|
|||
|
{
|
|||
|
local wrd=''
|
|||
|
read -t $TIMEOUT wrd # Timed read.
|
|||
|
echo $wrd
|
|||
|
}
|
|||
|
|
|||
|
is_constructable ()
|
|||
|
{ # This is the most complex and difficult-to-write function.
|
|||
|
local -a local_LS=( "${LS[@]}" ) # Local copy of letter set.
|
|||
|
local is_found=0
|
|||
|
local idx=0
|
|||
|
local pos
|
|||
|
local strlen
|
|||
|
local local_word=( "$1" )
|
|||
|
strlen=${#local_word}
|
|||
|
|
|||
|
while [ "$idx" -lt "$strlen" ]
|
|||
|
do
|
|||
|
is_found=$(expr index "${local_LS[*]}" "${local_word:idx:1}")
|
|||
|
if [ "$is_found" -eq "$NONCONS" ] # Not constructable!
|
|||
|
then
|
|||
|
echo "$FAILURE"; return
|
|||
|
else
|
|||
|
((pos = ($is_found - 1) / 2)) # Compensate for spaces betw. letters!
|
|||
|
local_LS[pos]=$NULL # Zero out used letters.
|
|||
|
((idx++)) # Bump index.
|
|||
|
fi
|
|||
|
done
|
|||
|
|
|||
|
echo "$SUCCESS"
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
is_valid ()
|
|||
|
{ # Surprisingly easy to check if word in dictionary ...
|
|||
|
fgrep -qw "$1" "$WLIST" # ... courtesy of 'grep' ...
|
|||
|
echo $?
|
|||
|
}
|
|||
|
|
|||
|
check_word ()
|
|||
|
{
|
|||
|
if [ -z "$1" ]
|
|||
|
then
|
|||
|
return
|
|||
|
fi
|
|||
|
|
|||
|
Status[1]=""
|
|||
|
Status[2]=""
|
|||
|
Status[3]=""
|
|||
|
Status[4]=""
|
|||
|
|
|||
|
iscons=$(is_constructable "$1")
|
|||
|
if [ "$iscons" ]
|
|||
|
then
|
|||
|
Status[1]="constructable"
|
|||
|
v=$(is_valid "$1")
|
|||
|
if [ "$v" -eq "$SUCCESS" ]
|
|||
|
then
|
|||
|
Status[2]="valid"
|
|||
|
strlen=${#1}
|
|||
|
|
|||
|
if [ ${Score[strlen]} -eq "$MAXCAT" ] # Category full!
|
|||
|
then
|
|||
|
Status[3]="Category-$strlen-overflow-PENALTY"
|
|||
|
return $NG
|
|||
|
fi
|
|||
|
|
|||
|
case "$strlen" in
|
|||
|
1 | 2 )
|
|||
|
Status[3]="Two-letter-word-PENALTY"
|
|||
|
return $NG;;
|
|||
|
* )
|
|||
|
Status[3]=""
|
|||
|
return $SUCCESS;;
|
|||
|
esac
|
|||
|
else
|
|||
|
Status[3]="Not-valid-PENALTY"
|
|||
|
return $NG
|
|||
|
fi
|
|||
|
else
|
|||
|
Status[3]="Not-constructable-PENALTY"
|
|||
|
return $NG
|
|||
|
fi
|
|||
|
|
|||
|
### FIXME: Streamline the above code block.
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
display_words ()
|
|||
|
{
|
|||
|
local idx=0
|
|||
|
local wlen0
|
|||
|
|
|||
|
clear
|
|||
|
echo "Letterset: ${LS[@]}"
|
|||
|
echo "Threes: Fours: Fives: Sixes: Sevens: Eights:"
|
|||
|
echo "------------------------------------------------------------"
|
|||
|
|
|||
|
|
|||
|
|
|||
|
while [ "${Words[idx]}" != '' ]
|
|||
|
do
|
|||
|
wlen0=${#Words[idx]}
|
|||
|
case "$wlen0" in
|
|||
|
3) ;;
|
|||
|
4) echo -n " " ;;
|
|||
|
5) echo -n " " ;;
|
|||
|
6) echo -n " " ;;
|
|||
|
7) echo -n " " ;;
|
|||
|
8) echo -n " " ;;
|
|||
|
esac
|
|||
|
echo "${Words[idx]}"
|
|||
|
((idx++))
|
|||
|
done
|
|||
|
|
|||
|
### FIXME: The word display is pretty crude.
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
play ()
|
|||
|
{
|
|||
|
word="Start game" # Dummy word, to start ...
|
|||
|
|
|||
|
while [ "$word" ] # If player just hits return (null word),
|
|||
|
do #+ then game ends.
|
|||
|
echo "$word: "${Status[@]}""
|
|||
|
echo -n "Last score: [${Score[0]}] TOTAL score: [${Score[1]}]: Next word: "
|
|||
|
total=${Score[1]}
|
|||
|
word=$(get_word)
|
|||
|
check_word "$word"
|
|||
|
|
|||
|
if [ "$?" -eq "$SUCCESS" ]
|
|||
|
then
|
|||
|
add_word "$word"
|
|||
|
else
|
|||
|
let "Score[0]= 0 - $PENALTY"
|
|||
|
let "Score[1]-=$PENALTY"
|
|||
|
fi
|
|||
|
|
|||
|
display_words
|
|||
|
done # Exit game.
|
|||
|
|
|||
|
### FIXME: The play () function calls too many other functions.
|
|||
|
### This verges on "spaghetti code" !!!
|
|||
|
}
|
|||
|
|
|||
|
end_of_game ()
|
|||
|
{ # Save and display stats.
|
|||
|
|
|||
|
#######################Autosave##########################
|
|||
|
savefile=qky.save.$$
|
|||
|
# ^^ PID of script
|
|||
|
echo `date` >> $savefile
|
|||
|
echo "Letterset # $randseed (random seed) ">> $savefile
|
|||
|
echo -n "Letterset: " >> $savefile
|
|||
|
echo "${LS[@]}" >> $savefile
|
|||
|
echo "---------" >> $savefile
|
|||
|
echo "Words constructed:" >> $savefile
|
|||
|
echo "${Words[@]}" >> $savefile
|
|||
|
echo >> $savefile
|
|||
|
echo "Score: $total" >> $savefile
|
|||
|
|
|||
|
echo "Statistics for this round saved in \""$savefile"\""
|
|||
|
#########################################################
|
|||
|
|
|||
|
echo "Score for this round: $total"
|
|||
|
echo "Words: ${Words[@]}"
|
|||
|
}
|
|||
|
|
|||
|
# ---------#
|
|||
|
instructions
|
|||
|
seed_random
|
|||
|
get_letset
|
|||
|
play
|
|||
|
end_of_game
|
|||
|
# ---------#
|
|||
|
|
|||
|
exit $?
|
|||
|
|
|||
|
# TODO:
|
|||
|
#
|
|||
|
# 1) Clean up code!
|
|||
|
# 2) Prettify the display_words () function (maybe with widgets?).
|
|||
|
# 3) Improve the time-out ... maybe change to untimed entry,
|
|||
|
#+ but with a time limit for the overall round.
|
|||
|
# 4) An on-screen countdown timer would be nice.
|
|||
|
# 5) Implement "vulnerable" mode of play for compatibility with classic
|
|||
|
#+ version of the game.
|
|||
|
# 6) Improve save-to-file capability (and maybe make it optional).
|
|||
|
# 7) Fix bugs!!!
|
|||
|
|
|||
|
# For more info, reference:
|
|||
|
# http://bash.deta.in/qky.README.html</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="NIM"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-42. Nim</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
# nim.sh: Game of Nim
|
|||
|
|
|||
|
# Author: Mendel Cooper
|
|||
|
# Reldate: 15 July 2008
|
|||
|
# License: GPL3
|
|||
|
|
|||
|
ROWS=5 # Five rows of pegs (or matchsticks).
|
|||
|
WON=91 # Exit codes to keep track of wins/losses.
|
|||
|
LOST=92 # Possibly useful if running in batch mode.
|
|||
|
QUIT=99
|
|||
|
peg_msg= # Peg/Pegs?
|
|||
|
Rows=( 0 5 4 3 2 1 ) # Array holding play info.
|
|||
|
# ${Rows[0]} holds total number of pegs, updated after each turn.
|
|||
|
# Other array elements hold number of pegs in corresponding row.
|
|||
|
|
|||
|
instructions ()
|
|||
|
{
|
|||
|
clear
|
|||
|
tput bold
|
|||
|
echo "Welcome to the game of Nim."; echo
|
|||
|
echo -n "Do you need instructions? (y/n) "; read ans
|
|||
|
|
|||
|
if [ "$ans" = "y" -o "$ans" = "Y" ]; then
|
|||
|
clear
|
|||
|
echo -e '\E[33;41m' # Yellow fg., over red bg.; bold.
|
|||
|
cat <<INSTRUCTIONS
|
|||
|
|
|||
|
Nim is a game with roots in the distant past.
|
|||
|
This particular variant starts with five rows of pegs.
|
|||
|
|
|||
|
1: | | | | |
|
|||
|
2: | | | |
|
|||
|
3: | | |
|
|||
|
4: | |
|
|||
|
5: |
|
|||
|
|
|||
|
The number at the left identifies the row.
|
|||
|
|
|||
|
The human player moves first, and alternates turns with the bot.
|
|||
|
A turn consists of removing at least one peg from a single row.
|
|||
|
It is permissable to remove ALL the pegs from a row.
|
|||
|
For example, in row 2, above, the player can remove 1, 2, 3, or 4 pegs.
|
|||
|
The player who removes the last peg loses.
|
|||
|
|
|||
|
The strategy consists of trying to be the one who removes
|
|||
|
the next-to-last peg(s), leaving the loser with the final peg.
|
|||
|
|
|||
|
To exit the game early, hit ENTER during your turn.
|
|||
|
INSTRUCTIONS
|
|||
|
|
|||
|
echo; echo -n "Hit ENTER to begin game. "; read azx
|
|||
|
|
|||
|
echo -e "\033[0m" # Restore display.
|
|||
|
else tput sgr0; clear
|
|||
|
fi
|
|||
|
|
|||
|
clear
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
tally_up ()
|
|||
|
{
|
|||
|
let "Rows[0] = ${Rows[1]} + ${Rows[2]} + ${Rows[3]} + ${Rows[4]} + \
|
|||
|
${Rows[5]}" # Add up how many pegs remaining.
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
display ()
|
|||
|
{
|
|||
|
index=1 # Start with top row.
|
|||
|
echo
|
|||
|
|
|||
|
while [ "$index" -le "$ROWS" ]
|
|||
|
do
|
|||
|
p=${Rows[index]}
|
|||
|
echo -n "$index: " # Show row number.
|
|||
|
|
|||
|
# ------------------------------------------------
|
|||
|
# Two concurrent inner loops.
|
|||
|
|
|||
|
indent=$index
|
|||
|
while [ "$indent" -gt 0 ]
|
|||
|
do
|
|||
|
echo -n " " # Staggered rows.
|
|||
|
((indent--)) # Spacing between pegs.
|
|||
|
done
|
|||
|
|
|||
|
while [ "$p" -gt 0 ]
|
|||
|
do
|
|||
|
echo -n "| "
|
|||
|
((p--))
|
|||
|
done
|
|||
|
# -----------------------------------------------
|
|||
|
|
|||
|
echo
|
|||
|
((index++))
|
|||
|
done
|
|||
|
|
|||
|
tally_up
|
|||
|
|
|||
|
rp=${Rows[0]}
|
|||
|
|
|||
|
if [ "$rp" -eq 1 ]
|
|||
|
then
|
|||
|
peg_msg=peg
|
|||
|
final_msg="Game over."
|
|||
|
else # Game not yet over . . .
|
|||
|
peg_msg=pegs
|
|||
|
final_msg="" # . . . So "final message" is blank.
|
|||
|
fi
|
|||
|
|
|||
|
echo " $rp $peg_msg remaining."
|
|||
|
echo " "$final_msg""
|
|||
|
|
|||
|
|
|||
|
echo
|
|||
|
}
|
|||
|
|
|||
|
player_move ()
|
|||
|
{
|
|||
|
|
|||
|
echo "Your move:"
|
|||
|
|
|||
|
echo -n "Which row? "
|
|||
|
while read idx
|
|||
|
do # Validity check, etc.
|
|||
|
|
|||
|
if [ -z "$idx" ] # Hitting return quits.
|
|||
|
then
|
|||
|
echo "Premature exit."; echo
|
|||
|
tput sgr0 # Restore display.
|
|||
|
exit $QUIT
|
|||
|
fi
|
|||
|
|
|||
|
if [ "$idx" -gt "$ROWS" -o "$idx" -lt 1 ] # Bounds check.
|
|||
|
then
|
|||
|
echo "Invalid row number!"
|
|||
|
echo -n "Which row? "
|
|||
|
else
|
|||
|
break
|
|||
|
fi
|
|||
|
# TODO:
|
|||
|
# Add check for non-numeric input.
|
|||
|
# Also, script crashes on input outside of range of long double.
|
|||
|
# Fix this.
|
|||
|
|
|||
|
done
|
|||
|
|
|||
|
echo -n "Remove how many? "
|
|||
|
while read num
|
|||
|
do # Validity check.
|
|||
|
|
|||
|
if [ -z "$num" ]
|
|||
|
then
|
|||
|
echo "Premature exit."; echo
|
|||
|
tput sgr0 # Restore display.
|
|||
|
exit $QUIT
|
|||
|
fi
|
|||
|
|
|||
|
if [ "$num" -gt ${Rows[idx]} -o "$num" -lt 1 ]
|
|||
|
then
|
|||
|
echo "Cannot remove $num!"
|
|||
|
echo -n "Remove how many? "
|
|||
|
else
|
|||
|
break
|
|||
|
fi
|
|||
|
done
|
|||
|
# TODO:
|
|||
|
# Add check for non-numeric input.
|
|||
|
# Also, script crashes on input outside of range of long double.
|
|||
|
# Fix this.
|
|||
|
|
|||
|
let "Rows[idx] -= $num"
|
|||
|
|
|||
|
display
|
|||
|
tally_up
|
|||
|
|
|||
|
if [ ${Rows[0]} -eq 1 ]
|
|||
|
then
|
|||
|
echo " Human wins!"
|
|||
|
echo " Congratulations!"
|
|||
|
tput sgr0 # Restore display.
|
|||
|
echo
|
|||
|
exit $WON
|
|||
|
fi
|
|||
|
|
|||
|
if [ ${Rows[0]} -eq 0 ]
|
|||
|
then # Snatching defeat from the jaws of victory . . .
|
|||
|
echo " Fool!"
|
|||
|
echo " You just removed the last peg!"
|
|||
|
echo " Bot wins!"
|
|||
|
tput sgr0 # Restore display.
|
|||
|
echo
|
|||
|
exit $LOST
|
|||
|
fi
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
bot_move ()
|
|||
|
{
|
|||
|
|
|||
|
row_b=0
|
|||
|
while [[ $row_b -eq 0 || ${Rows[row_b]} -eq 0 ]]
|
|||
|
do
|
|||
|
row_b=$RANDOM # Choose random row.
|
|||
|
let "row_b %= $ROWS"
|
|||
|
done
|
|||
|
|
|||
|
|
|||
|
num_b=0
|
|||
|
r0=${Rows[row_b]}
|
|||
|
|
|||
|
if [ "$r0" -eq 1 ]
|
|||
|
then
|
|||
|
num_b=1
|
|||
|
else
|
|||
|
let "num_b = $r0 - 1"
|
|||
|
# Leave only a single peg in the row.
|
|||
|
fi # Not a very strong strategy,
|
|||
|
#+ but probably a bit better than totally random.
|
|||
|
|
|||
|
let "Rows[row_b] -= $num_b"
|
|||
|
echo -n "Bot: "
|
|||
|
echo "Removing from row $row_b ... "
|
|||
|
|
|||
|
if [ "$num_b" -eq 1 ]
|
|||
|
then
|
|||
|
peg_msg=peg
|
|||
|
else
|
|||
|
peg_msg=pegs
|
|||
|
fi
|
|||
|
|
|||
|
echo " $num_b $peg_msg."
|
|||
|
|
|||
|
display
|
|||
|
tally_up
|
|||
|
|
|||
|
if [ ${Rows[0]} -eq 1 ]
|
|||
|
then
|
|||
|
echo " Bot wins!"
|
|||
|
tput sgr0 # Restore display.
|
|||
|
exit $WON
|
|||
|
fi
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
# ================================================== #
|
|||
|
instructions # If human player needs them . . .
|
|||
|
tput bold # Bold characters for easier viewing.
|
|||
|
display # Show game board.
|
|||
|
|
|||
|
while [ true ] # Main loop.
|
|||
|
do # Alternate human and bot turns.
|
|||
|
player_move
|
|||
|
bot_move
|
|||
|
done
|
|||
|
# ================================================== #
|
|||
|
|
|||
|
# Exercise:
|
|||
|
# --------
|
|||
|
# Improve the bot's strategy.
|
|||
|
# There is, in fact, a Nim strategy that can force a win.
|
|||
|
# See the Wikipedia article on Nim: http://en.wikipedia.org/wiki/Nim
|
|||
|
# Recode the bot to use this strategy (rather difficult).
|
|||
|
|
|||
|
# Curiosities:
|
|||
|
# -----------
|
|||
|
# Nim played a prominent role in Alain Resnais' 1961 New Wave film,
|
|||
|
#+ Last Year at Marienbad.
|
|||
|
#
|
|||
|
# In 1978, Leo Christopherson wrote an animated version of Nim,
|
|||
|
#+ Android Nim, for the TRS-80 Model I.</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="STOPWATCH"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-43. A command-line stopwatch</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/sh
|
|||
|
# sw.sh
|
|||
|
# A command-line Stopwatch
|
|||
|
|
|||
|
# Author: P<>draig Brady
|
|||
|
# http://www.pixelbeat.org/scripts/sw
|
|||
|
# (Minor reformatting by ABS Guide author.)
|
|||
|
# Used in ABS Guide with script author's permission.
|
|||
|
# Notes:
|
|||
|
# This script starts a few processes per lap, in addition to
|
|||
|
# the shell loop processing, so the assumption is made that
|
|||
|
# this takes an insignificant amount of time compared to
|
|||
|
# the response time of humans (~.1s) (or the keyboard
|
|||
|
# interrupt rate (~.05s)).
|
|||
|
# '?' for splits must be entered twice if characters
|
|||
|
# (erroneously) entered before it (on the same line).
|
|||
|
# '?' since not generating a signal may be slightly delayed
|
|||
|
# on heavily loaded systems.
|
|||
|
# Lap timings on ubuntu may be slightly delayed due to:
|
|||
|
# https://bugs.launchpad.net/bugs/62511
|
|||
|
# Changes:
|
|||
|
# V1.0, 23 Aug 2005, Initial release
|
|||
|
# V1.1, 26 Jul 2007, Allow both splits and laps from single invocation.
|
|||
|
# Only start timer after a key is pressed.
|
|||
|
# Indicate lap number
|
|||
|
# Cache programs at startup so there is less error
|
|||
|
# due to startup delays.
|
|||
|
# V1.2, 01 Aug 2007, Work around `date` commands that don't have
|
|||
|
# nanoseconds.
|
|||
|
# Use stty to change interrupt keys to space for
|
|||
|
# laps etc.
|
|||
|
# Ignore other input as it causes problems.
|
|||
|
# V1.3, 01 Aug 2007, Testing release.
|
|||
|
# V1.4, 02 Aug 2007, Various tweaks to get working under ubuntu
|
|||
|
# and Mac OS X.
|
|||
|
# V1.5, 27 Jun 2008, set LANG=C as got vague bug report about it.
|
|||
|
|
|||
|
export LANG=C
|
|||
|
|
|||
|
ulimit -c 0 # No coredumps from SIGQUIT.
|
|||
|
trap '' TSTP # Ignore Ctrl-Z just in case.
|
|||
|
save_tty=`stty -g` && trap "stty $save_tty" EXIT # Restore tty on exit.
|
|||
|
stty quit ' ' # Space for laps rather than Ctrl-\.
|
|||
|
stty eof '?' # ? for splits rather than Ctrl-D.
|
|||
|
stty -echo # Don't echo input.
|
|||
|
|
|||
|
cache_progs() {
|
|||
|
stty > /dev/null
|
|||
|
date > /dev/null
|
|||
|
grep . < /dev/null
|
|||
|
(echo "import time" | python) 2> /dev/null
|
|||
|
bc < /dev/null
|
|||
|
sed '' < /dev/null
|
|||
|
printf '1' > /dev/null
|
|||
|
/usr/bin/time false 2> /dev/null
|
|||
|
cat < /dev/null
|
|||
|
}
|
|||
|
cache_progs # To minimise startup delay.
|
|||
|
|
|||
|
date +%s.%N | grep -qF 'N' && use_python=1 # If `date` lacks nanoseconds.
|
|||
|
now() {
|
|||
|
if [ "$use_python" ]; then
|
|||
|
echo "import time; print time.time()" 2>/dev/null | python
|
|||
|
else
|
|||
|
printf "%.2f" `date +%s.%N`
|
|||
|
fi
|
|||
|
}
|
|||
|
|
|||
|
fmt_seconds() {
|
|||
|
seconds=$1
|
|||
|
mins=`echo $seconds/60 | bc`
|
|||
|
if [ "$mins" != "0" ]; then
|
|||
|
seconds=`echo "$seconds - ($mins*60)" | bc`
|
|||
|
echo "$mins:$seconds"
|
|||
|
else
|
|||
|
echo "$seconds"
|
|||
|
fi
|
|||
|
}
|
|||
|
|
|||
|
total() {
|
|||
|
end=`now`
|
|||
|
total=`echo "$end - $start" | bc`
|
|||
|
fmt_seconds $total
|
|||
|
}
|
|||
|
|
|||
|
stop() {
|
|||
|
[ "$lapped" ] && lap "$laptime" "display"
|
|||
|
total
|
|||
|
exit
|
|||
|
}
|
|||
|
|
|||
|
lap() {
|
|||
|
laptime=`echo "$1" | sed -n 's/.*real[^0-9.]*\(.*\)/\1/p'`
|
|||
|
[ ! "$laptime" -o "$laptime" = "0.00" ] && return
|
|||
|
# Signals too frequent.
|
|||
|
laptotal=`echo $laptime+0$laptotal | bc`
|
|||
|
if [ "$2" = "display" ]; then
|
|||
|
lapcount=`echo 0$lapcount+1 | bc`
|
|||
|
laptime=`fmt_seconds $laptotal`
|
|||
|
echo $laptime "($lapcount)"
|
|||
|
lapped="true"
|
|||
|
laptotal="0"
|
|||
|
fi
|
|||
|
}
|
|||
|
|
|||
|
echo -n "Space for lap | ? for split | Ctrl-C to stop | Space to start...">&2
|
|||
|
|
|||
|
while true; do
|
|||
|
trap true INT QUIT # Set signal handlers.
|
|||
|
laptime=`/usr/bin/time -p 2>&1 cat >/dev/null`
|
|||
|
ret=$?
|
|||
|
trap '' INT QUIT # Ignore signals within this script.
|
|||
|
if [ $ret -eq 1 -o $ret -eq 2 -o $ret -eq 130 ]; then # SIGINT = stop
|
|||
|
[ ! "$start" ] && { echo >&2; exit; }
|
|||
|
stop
|
|||
|
elif [ $ret -eq 3 -o $ret -eq 131 ]; then # SIGQUIT = lap
|
|||
|
if [ ! "$start" ]; then
|
|||
|
start=`now` || exit 1
|
|||
|
echo >&2
|
|||
|
continue
|
|||
|
fi
|
|||
|
lap "$laptime" "display"
|
|||
|
else # eof = split
|
|||
|
[ ! "$start" ] && continue
|
|||
|
total
|
|||
|
lap "$laptime" # Update laptotal.
|
|||
|
fi
|
|||
|
done
|
|||
|
|
|||
|
exit $?</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="HOMEWORK"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-44. An all-purpose shell scripting homework assignment solution</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
# homework.sh: All-purpose homework assignment solution.
|
|||
|
# Author: M. Leo Cooper
|
|||
|
# If you substitute your own name as author, then it is plagiarism,
|
|||
|
#+ possibly a lesser sin than cheating on your homework!
|
|||
|
# License: Public Domain
|
|||
|
|
|||
|
# This script may be turned in to your instructor
|
|||
|
#+ in fulfillment of ALL shell scripting homework assignments.
|
|||
|
# It's sparsely commented, but you, the student, can easily remedy that.
|
|||
|
# The script author repudiates all responsibility!
|
|||
|
|
|||
|
DLA=1
|
|||
|
P1=2
|
|||
|
P2=4
|
|||
|
P3=7
|
|||
|
PP1=0
|
|||
|
PP2=8
|
|||
|
MAXL=9
|
|||
|
E_LZY=99
|
|||
|
|
|||
|
declare -a L
|
|||
|
L[0]="3 4 0 17 29 8 13 18 19 17 20 2 19 14 17 28"
|
|||
|
L[1]="8 29 12 14 18 19 29 4 12 15 7 0 19 8 2 0 11 11 24 29 17 4 6 17 4 19"
|
|||
|
L[2]="29 19 7 0 19 29 8 29 7 0 21 4 29 13 4 6 11 4 2 19 4 3"
|
|||
|
L[3]="19 14 29 2 14 12 15 11 4 19 4 29 19 7 8 18 29"
|
|||
|
L[4]="18 2 7 14 14 11 22 14 17 10 29 0 18 18 8 6 13 12 4 13 19 26"
|
|||
|
L[5]="15 11 4 0 18 4 29 0 2 2 4 15 19 29 12 24 29 7 20 12 1 11 4 29"
|
|||
|
L[6]="4 23 2 20 18 4 29 14 5 29 4 6 17 4 6 8 14 20 18 29"
|
|||
|
L[7]="11 0 25 8 13 4 18 18 27"
|
|||
|
L[8]="0 13 3 29 6 17 0 3 4 29 12 4 29 0 2 2 14 17 3 8 13 6 11 24 26"
|
|||
|
L[9]="19 7 0 13 10 29 24 14 20 26"
|
|||
|
|
|||
|
declare -a \
|
|||
|
alph=( A B C D E F G H I J K L M N O P Q R S T U V W X Y Z . , : ' ' )
|
|||
|
|
|||
|
|
|||
|
pt_lt ()
|
|||
|
{
|
|||
|
echo -n "${alph[$1]}"
|
|||
|
echo -n -e "\a"
|
|||
|
sleep $DLA
|
|||
|
}
|
|||
|
|
|||
|
b_r ()
|
|||
|
{
|
|||
|
echo -e '\E[31;48m\033[1m'
|
|||
|
}
|
|||
|
|
|||
|
cr ()
|
|||
|
{
|
|||
|
echo -e "\a"
|
|||
|
sleep $DLA
|
|||
|
}
|
|||
|
|
|||
|
restore ()
|
|||
|
{
|
|||
|
echo -e '\033[0m' # Bold off.
|
|||
|
tput sgr0 # Normal.
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
p_l ()
|
|||
|
{
|
|||
|
for ltr in $1
|
|||
|
do
|
|||
|
pt_lt "$ltr"
|
|||
|
done
|
|||
|
}
|
|||
|
|
|||
|
# ----------------------
|
|||
|
b_r
|
|||
|
|
|||
|
for i in $(seq 0 $MAXL)
|
|||
|
do
|
|||
|
p_l "${L[i]}"
|
|||
|
if [[ "$i" -eq "$P1" || "$i" -eq "$P2" || "$i" -eq "$P3" ]]
|
|||
|
then
|
|||
|
cr
|
|||
|
elif [[ "$i" -eq "$PP1" || "$i" -eq "$PP2" ]]
|
|||
|
then
|
|||
|
cr; cr
|
|||
|
fi
|
|||
|
done
|
|||
|
|
|||
|
restore
|
|||
|
# ----------------------
|
|||
|
|
|||
|
echo
|
|||
|
|
|||
|
exit $E_LZY
|
|||
|
|
|||
|
# A typical example of an obfuscated script that is difficult
|
|||
|
#+ to understand, and frustrating to maintain.
|
|||
|
# In your career as a sysadmin, you'll run into these critters
|
|||
|
#+ all too often.</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><P
|
|||
|
><A
|
|||
|
NAME="KTOUR0"
|
|||
|
></A
|
|||
|
></P
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="KTOUR"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-45. The Knight's Tour</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
# ktour.sh
|
|||
|
|
|||
|
# author: mendel cooper
|
|||
|
# reldate: 12 Jan 2009
|
|||
|
# license: public domain
|
|||
|
# (Not much sense GPLing something that's pretty much in the common
|
|||
|
#+ domain anyhow.)
|
|||
|
|
|||
|
###################################################################
|
|||
|
# The Knight's Tour, a classic problem. #
|
|||
|
# ===================================== #
|
|||
|
# The knight must move onto every square of the chess board, #
|
|||
|
# but cannot revisit any square he has already visited. #
|
|||
|
# #
|
|||
|
# And just why is Sir Knight unwelcome for a return visit? #
|
|||
|
# Could it be that he has a habit of partying into the wee hours #
|
|||
|
#+ of the morning? #
|
|||
|
# Possibly he leaves pizza crusts in the bed, empty beer bottles #
|
|||
|
#+ all over the floor, and clogs the plumbing. . . . #
|
|||
|
# #
|
|||
|
# ------------------------------------------------------------- #
|
|||
|
# #
|
|||
|
# Usage: ktour.sh [start-square] [stupid] #
|
|||
|
# #
|
|||
|
# Note that start-square can be a square number #
|
|||
|
#+ in the range 0 - 63 ... or #
|
|||
|
# a square designator in conventional chess notation, #
|
|||
|
# such as a1, f5, h3, etc. #
|
|||
|
# #
|
|||
|
# If start-square-number not supplied, #
|
|||
|
#+ then starts on a random square somewhere on the board. #
|
|||
|
# #
|
|||
|
# "stupid" as second parameter sets the stupid strategy. #
|
|||
|
# #
|
|||
|
# Examples: #
|
|||
|
# ktour.sh 23 starts on square #23 (h3) #
|
|||
|
# ktour.sh g6 stupid starts on square #46, #
|
|||
|
# using "stupid" (non-Warnsdorff) strategy. #
|
|||
|
###################################################################
|
|||
|
|
|||
|
DEBUG= # Set this to echo debugging info to stdout.
|
|||
|
SUCCESS=0
|
|||
|
FAIL=99
|
|||
|
BADMOVE=-999
|
|||
|
FAILURE=1
|
|||
|
LINELEN=21 # How many moves to display per line.
|
|||
|
# ---------------------------------------- #
|
|||
|
# Board array params
|
|||
|
ROWS=8 # 8 x 8 board.
|
|||
|
COLS=8
|
|||
|
let "SQUARES = $ROWS * $COLS"
|
|||
|
let "MAX = $SQUARES - 1"
|
|||
|
MIN=0
|
|||
|
# 64 squares on board, indexed from 0 to 63.
|
|||
|
|
|||
|
VISITED=1
|
|||
|
UNVISITED=-1
|
|||
|
UNVSYM="##"
|
|||
|
# ---------------------------------------- #
|
|||
|
# Global variables.
|
|||
|
startpos= # Starting position (square #, 0 - 63).
|
|||
|
currpos= # Current position.
|
|||
|
movenum= # Move number.
|
|||
|
CRITPOS=37 # Have to patch for f5 starting position!
|
|||
|
|
|||
|
declare -i board
|
|||
|
# Use a one-dimensional array to simulate a two-dimensional one.
|
|||
|
# This can make life difficult and result in ugly kludges; see below.
|
|||
|
declare -i moves # Offsets from current knight position.
|
|||
|
|
|||
|
|
|||
|
initialize_board ()
|
|||
|
{
|
|||
|
local idx
|
|||
|
|
|||
|
for idx in {0..63}
|
|||
|
do
|
|||
|
board[$idx]=$UNVISITED
|
|||
|
done
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
print_board ()
|
|||
|
{
|
|||
|
local idx
|
|||
|
|
|||
|
echo " _____________________________________"
|
|||
|
for row in {7..0} # Reverse order of rows ...
|
|||
|
do #+ so it prints in chessboard order.
|
|||
|
let "rownum = $row + 1" # Start numbering rows at 1.
|
|||
|
echo -n "$rownum |" # Mark board edge with border and
|
|||
|
for column in {0..7} #+ "algebraic notation."
|
|||
|
do
|
|||
|
let "idx = $ROWS*$row + $column"
|
|||
|
if [ ${board[idx]} -eq $UNVISITED ]
|
|||
|
then
|
|||
|
echo -n "$UNVSYM " ##
|
|||
|
else # Mark square with move number.
|
|||
|
printf "%02d " "${board[idx]}"; echo -n " "
|
|||
|
fi
|
|||
|
done
|
|||
|
echo -e -n "\b\b\b|" # \b is a backspace.
|
|||
|
echo # -e enables echoing escaped chars.
|
|||
|
done
|
|||
|
|
|||
|
echo " -------------------------------------"
|
|||
|
echo " a b c d e f g h"
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
failure()
|
|||
|
{ # Whine, then bail out.
|
|||
|
echo
|
|||
|
print_board
|
|||
|
echo
|
|||
|
echo " Waah!!! Ran out of squares to move to!"
|
|||
|
echo -n " Knight's Tour attempt ended"
|
|||
|
echo " on $(to_algebraic $currpos) [square #$currpos]"
|
|||
|
echo " after just $movenum moves!"
|
|||
|
echo
|
|||
|
exit $FAIL
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
xlat_coords () # Translate x/y coordinates to board position
|
|||
|
{ #+ (board-array element #).
|
|||
|
# For user input of starting board position as x/y coords.
|
|||
|
# This function not used in initial release of ktour.sh.
|
|||
|
# May be used in an updated version, for compatibility with
|
|||
|
#+ standard implementation of the Knight's Tour in C, Python, etc.
|
|||
|
if [ -z "$1" -o -z "$2" ]
|
|||
|
then
|
|||
|
return $FAIL
|
|||
|
fi
|
|||
|
|
|||
|
local xc=$1
|
|||
|
local yc=$2
|
|||
|
|
|||
|
let "board_index = $xc * $ROWS + yc"
|
|||
|
|
|||
|
if [ $board_index -lt $MIN -o $board_index -gt $MAX ]
|
|||
|
then
|
|||
|
return $FAIL # Strayed off the board!
|
|||
|
else
|
|||
|
return $board_index
|
|||
|
fi
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
to_algebraic () # Translate board position (board-array element #)
|
|||
|
{ #+ to standard algebraic notation used by chess players.
|
|||
|
if [ -z "$1" ]
|
|||
|
then
|
|||
|
return $FAIL
|
|||
|
fi
|
|||
|
|
|||
|
local element_no=$1 # Numerical board position.
|
|||
|
local col_arr=( a b c d e f g h )
|
|||
|
local row_arr=( 1 2 3 4 5 6 7 8 )
|
|||
|
|
|||
|
let "row_no = $element_no / $ROWS"
|
|||
|
let "col_no = $element_no % $ROWS"
|
|||
|
t1=${col_arr[col_no]}; t2=${row_arr[row_no]}
|
|||
|
local apos=$t1$t2 # Concatenate.
|
|||
|
echo $apos
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
from_algebraic () # Translate standard algebraic chess notation
|
|||
|
{ #+ to numerical board position (board-array element #).
|
|||
|
# Or recognize numerical input & return it unchanged.
|
|||
|
if [ -z "$1" ]
|
|||
|
then
|
|||
|
return $FAIL
|
|||
|
fi # If no command-line arg, then will default to random start pos.
|
|||
|
|
|||
|
local ix
|
|||
|
local ix_count=0
|
|||
|
local b_index # Board index [0-63]
|
|||
|
local alpos="$1"
|
|||
|
|
|||
|
arow=${alpos:0:1} # position = 0, length = 1
|
|||
|
acol=${alpos:1:1}
|
|||
|
|
|||
|
if [[ $arow =~ [[:digit:]] ]] # Numerical input?
|
|||
|
then # POSIX char class
|
|||
|
if [[ $acol =~ [[:alpha:]] ]] # Number followed by a letter? Illegal!
|
|||
|
then return $FAIL
|
|||
|
else if [ $alpos -gt $MAX ] # Off board?
|
|||
|
then return $FAIL
|
|||
|
else return $alpos # Return digit(s) unchanged . . .
|
|||
|
fi #+ if within range.
|
|||
|
fi
|
|||
|
fi
|
|||
|
|
|||
|
if [[ $acol -eq $MIN || $acol -gt $ROWS ]]
|
|||
|
then # Outside of range 1 - 8?
|
|||
|
return $FAIL
|
|||
|
fi
|
|||
|
|
|||
|
for ix in a b c d e f g h
|
|||
|
do # Convert column letter to column number.
|
|||
|
if [ "$arow" = "$ix" ]
|
|||
|
then
|
|||
|
break
|
|||
|
fi
|
|||
|
((ix_count++)) # Find index count.
|
|||
|
done
|
|||
|
|
|||
|
((acol--)) # Decrementing converts to zero-based array.
|
|||
|
let "b_index = $ix_count + $acol * $ROWS"
|
|||
|
|
|||
|
if [ $b_index -gt $MAX ] # Off board?
|
|||
|
then
|
|||
|
return $FAIL
|
|||
|
fi
|
|||
|
|
|||
|
return $b_index
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
generate_moves () # Calculate all valid knight moves,
|
|||
|
{ #+ relative to current position ($1),
|
|||
|
#+ and store in ${moves} array.
|
|||
|
local kt_hop=1 # One square :: short leg of knight move.
|
|||
|
local kt_skip=2 # Two squares :: long leg of knight move.
|
|||
|
local valmov=0 # Valid moves.
|
|||
|
local row_pos; let "row_pos = $1 % $COLS"
|
|||
|
|
|||
|
|
|||
|
let "move1 = -$kt_skip + $ROWS" # 2 sideways to-the-left, 1 up
|
|||
|
if [[ `expr $row_pos - $kt_skip` -lt $MIN ]] # An ugly, ugly kludge!
|
|||
|
then # Can't move off board.
|
|||
|
move1=$BADMOVE # Not even temporarily.
|
|||
|
else
|
|||
|
((valmov++))
|
|||
|
fi
|
|||
|
let "move2 = -$kt_hop + $kt_skip * $ROWS" # 1 sideways to-the-left, 2 up
|
|||
|
if [[ `expr $row_pos - $kt_hop` -lt $MIN ]] # Kludge continued ...
|
|||
|
then
|
|||
|
move2=$BADMOVE
|
|||
|
else
|
|||
|
((valmov++))
|
|||
|
fi
|
|||
|
let "move3 = $kt_hop + $kt_skip * $ROWS" # 1 sideways to-the-right, 2 up
|
|||
|
if [[ `expr $row_pos + $kt_hop` -ge $COLS ]]
|
|||
|
then
|
|||
|
move3=$BADMOVE
|
|||
|
else
|
|||
|
((valmov++))
|
|||
|
fi
|
|||
|
let "move4 = $kt_skip + $ROWS" # 2 sideways to-the-right, 1 up
|
|||
|
if [[ `expr $row_pos + $kt_skip` -ge $COLS ]]
|
|||
|
then
|
|||
|
move4=$BADMOVE
|
|||
|
else
|
|||
|
((valmov++))
|
|||
|
fi
|
|||
|
let "move5 = $kt_skip - $ROWS" # 2 sideways to-the-right, 1 dn
|
|||
|
if [[ `expr $row_pos + $kt_skip` -ge $COLS ]]
|
|||
|
then
|
|||
|
move5=$BADMOVE
|
|||
|
else
|
|||
|
((valmov++))
|
|||
|
fi
|
|||
|
let "move6 = $kt_hop - $kt_skip * $ROWS" # 1 sideways to-the-right, 2 dn
|
|||
|
if [[ `expr $row_pos + $kt_hop` -ge $COLS ]]
|
|||
|
then
|
|||
|
move6=$BADMOVE
|
|||
|
else
|
|||
|
((valmov++))
|
|||
|
fi
|
|||
|
let "move7 = -$kt_hop - $kt_skip * $ROWS" # 1 sideways to-the-left, 2 dn
|
|||
|
if [[ `expr $row_pos - $kt_hop` -lt $MIN ]]
|
|||
|
then
|
|||
|
move7=$BADMOVE
|
|||
|
else
|
|||
|
((valmov++))
|
|||
|
fi
|
|||
|
let "move8 = -$kt_skip - $ROWS" # 2 sideways to-the-left, 1 dn
|
|||
|
if [[ `expr $row_pos - $kt_skip` -lt $MIN ]]
|
|||
|
then
|
|||
|
move8=$BADMOVE
|
|||
|
else
|
|||
|
((valmov++))
|
|||
|
fi # There must be a better way to do this.
|
|||
|
|
|||
|
local m=( $valmov $move1 $move2 $move3 $move4 $move5 $move6 $move7 $move8 )
|
|||
|
# ${moves[0]} = number of valid moves.
|
|||
|
# ${moves[1]} ... ${moves[8]} = possible moves.
|
|||
|
echo "${m[*]}" # Elements of array to stdout for capture in a var.
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
is_on_board () # Is position actually on the board?
|
|||
|
{
|
|||
|
if [[ "$1" -lt "$MIN" || "$1" -gt "$MAX" ]]
|
|||
|
then
|
|||
|
return $FAILURE
|
|||
|
else
|
|||
|
return $SUCCESS
|
|||
|
fi
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
do_move () # Move the knight!
|
|||
|
{
|
|||
|
local valid_moves=0
|
|||
|
local aapos
|
|||
|
currposl="$1"
|
|||
|
lmin=$ROWS
|
|||
|
iex=0
|
|||
|
squarel=
|
|||
|
mpm=
|
|||
|
mov=
|
|||
|
declare -a p_moves
|
|||
|
|
|||
|
########################## DECIDE-MOVE #############################
|
|||
|
if [ $startpos -ne $CRITPOS ]
|
|||
|
then # CRITPOS = square #37
|
|||
|
decide_move
|
|||
|
else # Needs a special patch for startpos=37 !!!
|
|||
|
decide_move_patched # Why this particular move and no other ???
|
|||
|
fi
|
|||
|
####################################################################
|
|||
|
|
|||
|
(( ++movenum )) # Increment move count.
|
|||
|
let "square = $currposl + ${moves[iex]}"
|
|||
|
|
|||
|
################## DEBUG ###############
|
|||
|
if [ "$DEBUG" ]
|
|||
|
then debug # Echo debugging information.
|
|||
|
fi
|
|||
|
##############################################
|
|||
|
|
|||
|
if [[ "$square" -gt $MAX || "$square" -lt $MIN ||
|
|||
|
${board[square]} -ne $UNVISITED ]]
|
|||
|
then
|
|||
|
(( --movenum )) # Decrement move count,
|
|||
|
echo "RAN OUT OF SQUARES!!!" #+ since previous one was invalid.
|
|||
|
return $FAIL
|
|||
|
fi
|
|||
|
|
|||
|
board[square]=$movenum
|
|||
|
currpos=$square # Update current position.
|
|||
|
((valid_moves++)); # moves[0]=$valid_moves
|
|||
|
aapos=$(to_algebraic $square)
|
|||
|
echo -n "$aapos "
|
|||
|
test $(( $Moves % $LINELEN )) -eq 0 && echo
|
|||
|
# Print LINELEN=21 moves per line. A valid tour shows 3 complete lines.
|
|||
|
return $valid_moves # Found a square to move to!
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
do_move_stupid() # Dingbat algorithm,
|
|||
|
{ #+ courtesy of script author, *not* Warnsdorff.
|
|||
|
local valid_moves=0
|
|||
|
local movloc
|
|||
|
local squareloc
|
|||
|
local aapos
|
|||
|
local cposloc="$1"
|
|||
|
|
|||
|
for movloc in {1..8}
|
|||
|
do # Move to first-found unvisited square.
|
|||
|
let "squareloc = $cposloc + ${moves[movloc]}"
|
|||
|
is_on_board $squareloc
|
|||
|
if [ $? -eq $SUCCESS ] && [ ${board[squareloc]} -eq $UNVISITED ]
|
|||
|
then # Add conditions to above if-test to improve algorithm.
|
|||
|
(( ++movenum ))
|
|||
|
board[squareloc]=$movenum
|
|||
|
currpos=$squareloc # Update current position.
|
|||
|
((valid_moves++)); # moves[0]=$valid_moves
|
|||
|
aapos=$(to_algebraic $squareloc)
|
|||
|
echo -n "$aapos "
|
|||
|
test $(( $Moves % $LINELEN )) -eq 0 && echo # Print 21 moves/line.
|
|||
|
return $valid_moves # Found a square to move to!
|
|||
|
fi
|
|||
|
done
|
|||
|
|
|||
|
return $FAIL
|
|||
|
# If no square found in all 8 loop iterations,
|
|||
|
#+ then Knight's Tour attempt ends in failure.
|
|||
|
|
|||
|
# Dingbat algorithm will typically fail after about 30 - 40 moves,
|
|||
|
#+ but executes _much_ faster than Warnsdorff's in do_move() function.
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
decide_move () # Which move will we make?
|
|||
|
{ # But, fails on startpos=37 !!!
|
|||
|
for mov in {1..8}
|
|||
|
do
|
|||
|
let "squarel = $currposl + ${moves[mov]}"
|
|||
|
is_on_board $squarel
|
|||
|
if [[ $? -eq $SUCCESS && ${board[squarel]} -eq $UNVISITED ]]
|
|||
|
then # Find accessible square with least possible future moves.
|
|||
|
# This is Warnsdorff's algorithm.
|
|||
|
# What happens is that the knight wanders toward the outer edge
|
|||
|
#+ of the board, then pretty much spirals inward.
|
|||
|
# Given two or more possible moves with same value of
|
|||
|
#+ least-possible-future-moves, this implementation chooses
|
|||
|
#+ the _first_ of those moves.
|
|||
|
# This means that there is not necessarily a unique solution
|
|||
|
#+ for any given starting position.
|
|||
|
|
|||
|
possible_moves $squarel
|
|||
|
mpm=$?
|
|||
|
p_moves[mov]=$mpm
|
|||
|
|
|||
|
if [ $mpm -lt $lmin ] # If less than previous minimum ...
|
|||
|
then # ^^
|
|||
|
lmin=$mpm # Update minimum.
|
|||
|
iex=$mov # Save index.
|
|||
|
fi
|
|||
|
|
|||
|
fi
|
|||
|
done
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
decide_move_patched () # Decide which move to make,
|
|||
|
{ # ^^^^^^^ #+ but only if startpos=37 !!!
|
|||
|
for mov in {1..8}
|
|||
|
do
|
|||
|
let "squarel = $currposl + ${moves[mov]}"
|
|||
|
is_on_board $squarel
|
|||
|
if [[ $? -eq $SUCCESS && ${board[squarel]} -eq $UNVISITED ]]
|
|||
|
then
|
|||
|
possible_moves $squarel
|
|||
|
mpm=$?
|
|||
|
p_moves[mov]=$mpm
|
|||
|
|
|||
|
if [ $mpm -le $lmin ] # If less-than-or equal to prev. minimum!
|
|||
|
then # ^^
|
|||
|
lmin=$mpm
|
|||
|
iex=$mov
|
|||
|
fi
|
|||
|
|
|||
|
fi
|
|||
|
done # There has to be a better way to do this.
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
possible_moves () # Calculate number of possible moves,
|
|||
|
{ #+ given the current position.
|
|||
|
|
|||
|
if [ -z "$1" ]
|
|||
|
then
|
|||
|
return $FAIL
|
|||
|
fi
|
|||
|
|
|||
|
local curr_pos=$1
|
|||
|
local valid_movl=0
|
|||
|
local icx=0
|
|||
|
local movl
|
|||
|
local sq
|
|||
|
declare -a movesloc
|
|||
|
|
|||
|
movesloc=( $(generate_moves $curr_pos) )
|
|||
|
|
|||
|
for movl in {1..8}
|
|||
|
do
|
|||
|
let "sq = $curr_pos + ${movesloc[movl]}"
|
|||
|
is_on_board $sq
|
|||
|
if [ $? -eq $SUCCESS ] && [ ${board[sq]} -eq $UNVISITED ]
|
|||
|
then
|
|||
|
((valid_movl++));
|
|||
|
fi
|
|||
|
done
|
|||
|
|
|||
|
return $valid_movl # Found a square to move to!
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
strategy ()
|
|||
|
{
|
|||
|
echo
|
|||
|
|
|||
|
if [ -n "$STUPID" ]
|
|||
|
then
|
|||
|
for Moves in {1..63}
|
|||
|
do
|
|||
|
cposl=$1
|
|||
|
moves=( $(generate_moves $currpos) )
|
|||
|
do_move_stupid "$currpos"
|
|||
|
if [ $? -eq $FAIL ]
|
|||
|
then
|
|||
|
failure
|
|||
|
fi
|
|||
|
done
|
|||
|
fi
|
|||
|
|
|||
|
# Don't need an "else" clause here,
|
|||
|
#+ because Stupid Strategy will always fail and exit!
|
|||
|
for Moves in {1..63}
|
|||
|
do
|
|||
|
cposl=$1
|
|||
|
moves=( $(generate_moves $currpos) )
|
|||
|
do_move "$currpos"
|
|||
|
if [ $? -eq $FAIL ]
|
|||
|
then
|
|||
|
failure
|
|||
|
fi
|
|||
|
|
|||
|
done
|
|||
|
# Could have condensed above two do-loops into a single one,
|
|||
|
echo #+ but this would have slowed execution.
|
|||
|
|
|||
|
print_board
|
|||
|
echo
|
|||
|
echo "Knight's Tour ends on $(to_algebraic $currpos) [square #$currpos]."
|
|||
|
return $SUCCESS
|
|||
|
}
|
|||
|
|
|||
|
debug ()
|
|||
|
{ # Enable this by setting DEBUG=1 near beginning of script.
|
|||
|
local n
|
|||
|
|
|||
|
echo "================================="
|
|||
|
echo " At move number $movenum:"
|
|||
|
echo " *** possible moves = $mpm ***"
|
|||
|
# echo "### square = $square ###"
|
|||
|
echo "lmin = $lmin"
|
|||
|
echo "${moves[@]}"
|
|||
|
|
|||
|
for n in {1..8}
|
|||
|
do
|
|||
|
echo -n "($n):${p_moves[n]} "
|
|||
|
done
|
|||
|
|
|||
|
echo
|
|||
|
echo "iex = $iex :: moves[iex] = ${moves[iex]}"
|
|||
|
echo "square = $square"
|
|||
|
echo "================================="
|
|||
|
echo
|
|||
|
} # Gives pretty complete status after ea. move.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# =============================================================== #
|
|||
|
# int main () {
|
|||
|
from_algebraic "$1"
|
|||
|
startpos=$?
|
|||
|
if [ "$startpos" -eq "$FAIL" ] # Okay even if no $1.
|
|||
|
then # ^^^^^^^^^^^ Okay even if input -lt 0.
|
|||
|
echo "No starting square specified (or illegal input)."
|
|||
|
let "startpos = $RANDOM % $SQUARES" # 0 - 63 permissable range.
|
|||
|
fi
|
|||
|
|
|||
|
|
|||
|
if [ "$2" = "stupid" ]
|
|||
|
then
|
|||
|
STUPID=1
|
|||
|
echo -n " ### Stupid Strategy ###"
|
|||
|
else
|
|||
|
STUPID=''
|
|||
|
echo -n " *** Warnsdorff's Algorithm ***"
|
|||
|
fi
|
|||
|
|
|||
|
|
|||
|
initialize_board
|
|||
|
|
|||
|
movenum=0
|
|||
|
board[startpos]=$movenum # Mark each board square with move number.
|
|||
|
currpos=$startpos
|
|||
|
algpos=$(to_algebraic $startpos)
|
|||
|
|
|||
|
echo; echo "Starting from $algpos [square #$startpos] ..."; echo
|
|||
|
echo -n "Moves:"
|
|||
|
|
|||
|
strategy "$currpos"
|
|||
|
|
|||
|
echo
|
|||
|
|
|||
|
exit 0 # return 0;
|
|||
|
|
|||
|
# } # End of main() pseudo-function.
|
|||
|
# =============================================================== #
|
|||
|
|
|||
|
|
|||
|
# Exercises:
|
|||
|
# ---------
|
|||
|
#
|
|||
|
# 1) Extend this example to a 10 x 10 board or larger.
|
|||
|
# 2) Improve the "stupid strategy" by modifying the
|
|||
|
# do_move_stupid function.
|
|||
|
# Hint: Prevent straying into corner squares in early moves
|
|||
|
# (the exact opposite of Warnsdorff's algorithm!).
|
|||
|
# 3) This script could stand considerable improvement and
|
|||
|
# streamlining, especially in the poorly-written
|
|||
|
# generate_moves() function
|
|||
|
# and in the DECIDE-MOVE patch in the do_move() function.
|
|||
|
# Must figure out why standard algorithm fails for startpos=37 ...
|
|||
|
#+ but _not_ on any other, including symmetrical startpos=26.
|
|||
|
# Possibly, when calculating possible moves, counts the move back
|
|||
|
#+ to the originating square. If so, it might be a relatively easy fix.</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="MSQUARE"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-46. Magic Squares</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
# msquare.sh
|
|||
|
# Magic Square generator (odd-order squares only!)
|
|||
|
|
|||
|
# Author: mendel cooper
|
|||
|
# reldate: 19 Jan. 2009
|
|||
|
# License: Public Domain
|
|||
|
# A C-program by the very talented Kwon Young Shin inspired this script.
|
|||
|
# http://user.chollian.net/~brainstm/MagicSquare.htm
|
|||
|
|
|||
|
# Definition: A "magic square" is a two-dimensional array
|
|||
|
# of integers in which all the rows, columns,
|
|||
|
# and *long* diagonals add up to the same number.
|
|||
|
# Being "square," the array has the same number
|
|||
|
# of rows and columns. That number is the "order."
|
|||
|
# An example of a magic square of order 3 is:
|
|||
|
# 8 1 6
|
|||
|
# 3 5 7
|
|||
|
# 4 9 2
|
|||
|
# All the rows, columns, and the two long diagonals add up to 15.
|
|||
|
|
|||
|
|
|||
|
# Globals
|
|||
|
EVEN=2
|
|||
|
MAXSIZE=31 # 31 rows x 31 cols.
|
|||
|
E_usage=90 # Invocation error.
|
|||
|
dimension=
|
|||
|
declare -i square
|
|||
|
|
|||
|
usage_message ()
|
|||
|
{
|
|||
|
echo "Usage: $0 order"
|
|||
|
echo " ... where \"order\" (square size) is an ODD integer"
|
|||
|
echo " in the range 3 - 31."
|
|||
|
# Actually works for squares up to order 159,
|
|||
|
#+ but large squares will not display pretty-printed in a term window.
|
|||
|
# Try increasing MAXSIZE, above.
|
|||
|
exit $E_usage
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
calculate () # Here's where the actual work gets done.
|
|||
|
{
|
|||
|
local row col index dimadj j k cell_val=1
|
|||
|
dimension=$1
|
|||
|
|
|||
|
let "dimadj = $dimension * 3"; let "dimadj /= 2" # x 1.5, then truncate.
|
|||
|
|
|||
|
for ((j=0; j < dimension; j++))
|
|||
|
do
|
|||
|
for ((k=0; k < dimension; k++))
|
|||
|
do # Calculate indices, then convert to 1-dim. array index.
|
|||
|
# Bash doesn't support multidimensional arrays. Pity.
|
|||
|
let "col = $k - $j + $dimadj"; let "col %= $dimension"
|
|||
|
let "row = $j * 2 - $k + $dimension"; let "row %= $dimension"
|
|||
|
let "index = $row*($dimension) + $col"
|
|||
|
square[$index]=cell_val; ((cell_val++))
|
|||
|
done
|
|||
|
done
|
|||
|
} # Plain math, visualization not required.
|
|||
|
|
|||
|
|
|||
|
print_square () # Output square, one row at a time.
|
|||
|
{
|
|||
|
local row col idx d1
|
|||
|
let "d1 = $dimension - 1" # Adjust for zero-indexed array.
|
|||
|
|
|||
|
for row in $(seq 0 $d1)
|
|||
|
do
|
|||
|
|
|||
|
for col in $(seq 0 $d1)
|
|||
|
do
|
|||
|
let "idx = $row * $dimension + $col"
|
|||
|
printf "%3d " "${square[idx]}"; echo -n " "
|
|||
|
done # Displays up to 13th order neatly in 80-column term window.
|
|||
|
|
|||
|
echo # Newline after each row.
|
|||
|
done
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
#################################################
|
|||
|
if [[ -z "$1" ]] || [[ "$1" -gt $MAXSIZE ]]
|
|||
|
then
|
|||
|
usage_message
|
|||
|
fi
|
|||
|
|
|||
|
let "test_even = $1 % $EVEN"
|
|||
|
if [ $test_even -eq 0 ]
|
|||
|
then # Can't handle even-order squares.
|
|||
|
usage_message
|
|||
|
fi
|
|||
|
|
|||
|
calculate $1
|
|||
|
print_square # echo "${square[@]}" # DEBUG
|
|||
|
|
|||
|
exit $?
|
|||
|
#################################################
|
|||
|
|
|||
|
|
|||
|
# Exercises:
|
|||
|
# ---------
|
|||
|
# 1) Add a function to calculate the sum of each row, column,
|
|||
|
# and *long* diagonal. The sums must match.
|
|||
|
# This is the "magic constant" of that particular order square.
|
|||
|
# 2) Have the print_square function auto-calculate how much space
|
|||
|
# to allot between square elements for optimized display.
|
|||
|
# This might require parameterizing the "printf" line.
|
|||
|
# 3) Add appropriate functions for generating magic squares
|
|||
|
# with an *even* number of rows/columns.
|
|||
|
# This is non-trivial(!).
|
|||
|
# See the URL for Kwon Young Shin, above, for help.</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="FIFTEEN"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-47. Fifteen Puzzle</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
# fifteen.sh
|
|||
|
|
|||
|
# Classic "Fifteen Puzzle"
|
|||
|
# Author: Antonio Macchi
|
|||
|
# Lightly edited and commented by ABS Guide author.
|
|||
|
# Used in ABS Guide with permission. (Thanks!)
|
|||
|
|
|||
|
# The invention of the Fifteen Puzzle is attributed to either
|
|||
|
#+ Sam Loyd or Noyes Palmer Chapman.
|
|||
|
# The puzzle was wildly popular in the late 19th-century.
|
|||
|
|
|||
|
# Object: Rearrange the numbers so they read in order,
|
|||
|
#+ from 1 - 15: ________________
|
|||
|
# | 1 2 3 4 |
|
|||
|
# | 5 6 7 8 |
|
|||
|
# | 9 10 11 12 |
|
|||
|
# | 13 14 15 |
|
|||
|
# ----------------
|
|||
|
|
|||
|
|
|||
|
#######################
|
|||
|
# Constants #
|
|||
|
SQUARES=16 #
|
|||
|
FAIL=70 #
|
|||
|
E_PREMATURE_EXIT=80 #
|
|||
|
#######################
|
|||
|
|
|||
|
|
|||
|
########
|
|||
|
# Data #
|
|||
|
########
|
|||
|
|
|||
|
Puzzle=( 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 " " )
|
|||
|
|
|||
|
|
|||
|
#############
|
|||
|
# Functions #
|
|||
|
#############
|
|||
|
|
|||
|
function swap
|
|||
|
{
|
|||
|
local tmp
|
|||
|
|
|||
|
tmp=${Puzzle[$1]}
|
|||
|
Puzzle[$1]=${Puzzle[$2]}
|
|||
|
Puzzle[$2]=$tmp
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
function Jumble
|
|||
|
{ # Scramble the pieces at beginning of round.
|
|||
|
local i pos1 pos2
|
|||
|
|
|||
|
for i in {1..100}
|
|||
|
do
|
|||
|
pos1=$(( $RANDOM % $SQUARES))
|
|||
|
pos2=$(( $RANDOM % $SQUARES ))
|
|||
|
swap $pos1 $pos2
|
|||
|
done
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
function PrintPuzzle
|
|||
|
{
|
|||
|
local i1 i2 puzpos
|
|||
|
puzpos=0
|
|||
|
|
|||
|
clear
|
|||
|
echo "Enter quit to exit."; echo # Better that than Ctl-C.
|
|||
|
|
|||
|
echo ",----.----.----.----." # Top border.
|
|||
|
for i1 in {1..4}
|
|||
|
do
|
|||
|
for i2 in {1..4}
|
|||
|
do
|
|||
|
printf "| %2s " "${Puzzle[$puzpos]}"
|
|||
|
(( puzpos++ ))
|
|||
|
done
|
|||
|
echo "|" # Right-side border.
|
|||
|
test $i1 = 4 || echo "+----+----+----+----+"
|
|||
|
done
|
|||
|
echo "'----'----'----'----'" # Bottom border.
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
function GetNum
|
|||
|
{ # Test for valid input.
|
|||
|
local puznum garbage
|
|||
|
|
|||
|
while true
|
|||
|
do
|
|||
|
echo "Moves: $moves" # Also counts invalid moves.
|
|||
|
read -p "Number to move: " puznum garbage
|
|||
|
if [ "$puznum" = "quit" ]; then echo; exit $E_PREMATURE_EXIT; fi
|
|||
|
test -z "$puznum" -o -n "${puznum//[0-9]/}" && continue
|
|||
|
test $puznum -gt 0 -a $puznum -lt $SQUARES && break
|
|||
|
done
|
|||
|
return $puznum
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
function GetPosFromNum
|
|||
|
{ # $1 = puzzle-number
|
|||
|
local puzpos
|
|||
|
|
|||
|
for puzpos in {0..15}
|
|||
|
do
|
|||
|
test "${Puzzle[$puzpos]}" = "$1" && break
|
|||
|
done
|
|||
|
return $puzpos
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
function Move
|
|||
|
{ # $1=Puzzle-pos
|
|||
|
test $1 -gt 3 && test "${Puzzle[$(( $1 - 4 ))]}" = " "\
|
|||
|
&& swap $1 $(( $1 - 4 )) && return 0
|
|||
|
test $(( $1%4 )) -ne 3 && test "${Puzzle[$(( $1 + 1 ))]}" = " "\
|
|||
|
&& swap $1 $(( $1 + 1 )) && return 0
|
|||
|
test $1 -lt 12 && test "${Puzzle[$(( $1 + 4 ))]}" = " "\
|
|||
|
&& swap $1 $(( $1 + 4 )) && return 0
|
|||
|
test $(( $1%4 )) -ne 0 && test "${Puzzle[$(( $1 - 1 ))]}" = " " &&\
|
|||
|
swap $1 $(( $1 - 1 )) && return 0
|
|||
|
return 1
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
function Solved
|
|||
|
{
|
|||
|
local pos
|
|||
|
|
|||
|
for pos in {0..14}
|
|||
|
do
|
|||
|
test "${Puzzle[$pos]}" = $(( $pos + 1 )) || return $FAIL
|
|||
|
# Check whether number in each square = square number.
|
|||
|
done
|
|||
|
return 0 # Successful solution.
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
################### MAIN () #######################{
|
|||
|
moves=0
|
|||
|
Jumble
|
|||
|
|
|||
|
while true # Loop continuously until puzzle solved.
|
|||
|
do
|
|||
|
echo; echo
|
|||
|
PrintPuzzle
|
|||
|
echo
|
|||
|
while true
|
|||
|
do
|
|||
|
GetNum
|
|||
|
puznum=$?
|
|||
|
GetPosFromNum $puznum
|
|||
|
puzpos=$?
|
|||
|
((moves++))
|
|||
|
Move $puzpos && break
|
|||
|
done
|
|||
|
Solved && break
|
|||
|
done
|
|||
|
|
|||
|
echo;echo
|
|||
|
PrintPuzzle
|
|||
|
echo; echo "BRAVO!"; echo
|
|||
|
|
|||
|
exit 0
|
|||
|
###################################################}
|
|||
|
|
|||
|
# Exercise:
|
|||
|
# --------
|
|||
|
# Rewrite the script to display the letters A - O,
|
|||
|
#+ rather than the numbers 1 - 15.</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><P
|
|||
|
><A
|
|||
|
NAME="HANOI2REF"
|
|||
|
></A
|
|||
|
></P
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="HANOI2"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-48. <I
|
|||
|
CLASS="FIRSTTERM"
|
|||
|
>The Towers of Hanoi, graphic
|
|||
|
version</I
|
|||
|
></B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#! /bin/bash
|
|||
|
# The Towers Of Hanoi
|
|||
|
# Original script (hanoi.bash) copyright (C) 2000 Amit Singh.
|
|||
|
# All Rights Reserved.
|
|||
|
# http://hanoi.kernelthread.com
|
|||
|
|
|||
|
# hanoi2.bash
|
|||
|
# Version 2.00: modded for ASCII-graphic display.
|
|||
|
# Version 2.01: fixed no command-line param bug.
|
|||
|
# Uses code contributed by Antonio Macchi,
|
|||
|
#+ with heavy editing by ABS Guide author.
|
|||
|
# This variant falls under the original copyright, see above.
|
|||
|
# Used in ABS Guide with Amit Singh's permission (thanks!).
|
|||
|
|
|||
|
|
|||
|
### Variables && sanity check ###
|
|||
|
|
|||
|
E_NOPARAM=86
|
|||
|
E_BADPARAM=87 # Illegal no. of disks passed to script.
|
|||
|
E_NOEXIT=88
|
|||
|
|
|||
|
DISKS=${1:-$E_NOPARAM} # Must specify how many disks.
|
|||
|
Moves=0
|
|||
|
|
|||
|
MWIDTH=7
|
|||
|
MARGIN=2
|
|||
|
# Arbitrary "magic" constants; work okay for relatively small # of disks.
|
|||
|
# BASEWIDTH=51 # Original code.
|
|||
|
let "basewidth = $MWIDTH * $DISKS + $MARGIN" # "Base" beneath rods.
|
|||
|
# Above "algorithm" could likely stand improvement.
|
|||
|
|
|||
|
### Display variables ###
|
|||
|
let "disks1 = $DISKS - 1"
|
|||
|
let "spaces1 = $DISKS"
|
|||
|
let "spaces2 = 2 * $DISKS"
|
|||
|
|
|||
|
let "lastmove_t = $DISKS - 1" # Final move?
|
|||
|
|
|||
|
|
|||
|
declare -a Rod1 Rod2 Rod3
|
|||
|
|
|||
|
### ######################### ###
|
|||
|
|
|||
|
|
|||
|
function repeat { # $1=char $2=number of repetitions
|
|||
|
local n # Repeat-print a character.
|
|||
|
|
|||
|
for (( n=0; n<$2; n++ )); do
|
|||
|
echo -n "$1"
|
|||
|
done
|
|||
|
}
|
|||
|
|
|||
|
function FromRod {
|
|||
|
local rod summit weight sequence
|
|||
|
|
|||
|
while true; do
|
|||
|
rod=$1
|
|||
|
test ${rod/[^123]/} || continue
|
|||
|
|
|||
|
sequence=$(echo $(seq 0 $disks1 | tac))
|
|||
|
for summit in $sequence; do
|
|||
|
eval weight=\${Rod${rod}[$summit]}
|
|||
|
test $weight -ne 0 &&
|
|||
|
{ echo "$rod $summit $weight"; return; }
|
|||
|
done
|
|||
|
done
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
function ToRod { # $1=previous (FromRod) weight
|
|||
|
local rod firstfree weight sequence
|
|||
|
|
|||
|
while true; do
|
|||
|
rod=$2
|
|||
|
test ${rod/[^123]} || continue
|
|||
|
|
|||
|
sequence=$(echo $(seq 0 $disks1 | tac))
|
|||
|
for firstfree in $sequence; do
|
|||
|
eval weight=\${Rod${rod}[$firstfree]}
|
|||
|
test $weight -gt 0 && { (( firstfree++ )); break; }
|
|||
|
done
|
|||
|
test $weight -gt $1 -o $firstfree = 0 &&
|
|||
|
{ echo "$rod $firstfree"; return; }
|
|||
|
done
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
function PrintRods {
|
|||
|
local disk rod empty fill sp sequence
|
|||
|
|
|||
|
|
|||
|
repeat " " $spaces1
|
|||
|
echo -n "|"
|
|||
|
repeat " " $spaces2
|
|||
|
echo -n "|"
|
|||
|
repeat " " $spaces2
|
|||
|
echo "|"
|
|||
|
|
|||
|
sequence=$(echo $(seq 0 $disks1 | tac))
|
|||
|
for disk in $sequence; do
|
|||
|
for rod in {1..3}; do
|
|||
|
eval empty=$(( $DISKS - (Rod${rod}[$disk] / 2) ))
|
|||
|
eval fill=\${Rod${rod}[$disk]}
|
|||
|
repeat " " $empty
|
|||
|
test $fill -gt 0 && repeat "*" $fill || echo -n "|"
|
|||
|
repeat " " $empty
|
|||
|
done
|
|||
|
echo
|
|||
|
done
|
|||
|
repeat "=" $basewidth # Print "base" beneath rods.
|
|||
|
echo
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
display ()
|
|||
|
{
|
|||
|
echo
|
|||
|
PrintRods
|
|||
|
|
|||
|
# Get rod-number, summit and weight
|
|||
|
first=( `FromRod $1` )
|
|||
|
eval Rod${first[0]}[${first[1]}]=0
|
|||
|
|
|||
|
# Get rod-number and first-free position
|
|||
|
second=( `ToRod ${first[2]} $2` )
|
|||
|
eval Rod${second[0]}[${second[1]}]=${first[2]}
|
|||
|
|
|||
|
|
|||
|
echo; echo; echo
|
|||
|
if [ "${Rod3[lastmove_t]}" = 1 ]
|
|||
|
then # Last move? If yes, then display final position.
|
|||
|
echo "+ Final Position: $Moves moves"; echo
|
|||
|
PrintRods
|
|||
|
fi
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
# From here down, almost the same as original (hanoi.bash) script.
|
|||
|
|
|||
|
dohanoi() { # Recursive function.
|
|||
|
case $1 in
|
|||
|
0)
|
|||
|
;;
|
|||
|
*)
|
|||
|
dohanoi "$(($1-1))" $2 $4 $3
|
|||
|
if [ "$Moves" -ne 0 ]
|
|||
|
then
|
|||
|
echo "+ Position after move $Moves"
|
|||
|
fi
|
|||
|
((Moves++))
|
|||
|
echo -n " Next move will be: "
|
|||
|
echo $2 "-->" $3
|
|||
|
display $2 $3
|
|||
|
dohanoi "$(($1-1))" $4 $3 $2
|
|||
|
;;
|
|||
|
esac
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
setup_arrays ()
|
|||
|
{
|
|||
|
local dim n elem
|
|||
|
|
|||
|
let "dim1 = $1 - 1"
|
|||
|
elem=$dim1
|
|||
|
|
|||
|
for n in $(seq 0 $dim1)
|
|||
|
do
|
|||
|
let "Rod1[$elem] = 2 * $n + 1"
|
|||
|
Rod2[$n]=0
|
|||
|
Rod3[$n]=0
|
|||
|
((elem--))
|
|||
|
done
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
### Main ###
|
|||
|
|
|||
|
setup_arrays $DISKS
|
|||
|
echo; echo "+ Start Position"
|
|||
|
|
|||
|
case $# in
|
|||
|
1) case $(($1>0)) in # Must have at least one disk.
|
|||
|
1)
|
|||
|
disks=$1
|
|||
|
dohanoi $1 1 3 2
|
|||
|
# Total moves = 2^n - 1, where n = number of disks.
|
|||
|
echo
|
|||
|
exit 0;
|
|||
|
;;
|
|||
|
*)
|
|||
|
echo "$0: Illegal value for number of disks";
|
|||
|
exit $E_BADPARAM;
|
|||
|
;;
|
|||
|
esac
|
|||
|
;;
|
|||
|
*)
|
|||
|
clear
|
|||
|
echo "usage: $0 N"
|
|||
|
echo " Where \"N\" is the number of disks."
|
|||
|
exit $E_NOPARAM;
|
|||
|
;;
|
|||
|
esac
|
|||
|
|
|||
|
exit $E_NOEXIT # Shouldn't exit here.
|
|||
|
|
|||
|
# Note:
|
|||
|
# Redirect script output to a file, otherwise it scrolls off display.</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><P
|
|||
|
><A
|
|||
|
NAME="HANOI2AREF"
|
|||
|
></A
|
|||
|
></P
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="HANOI2A"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-49. <I
|
|||
|
CLASS="FIRSTTERM"
|
|||
|
>The Towers of Hanoi, alternate graphic
|
|||
|
version</I
|
|||
|
></B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#! /bin/bash
|
|||
|
# The Towers Of Hanoi
|
|||
|
# Original script (hanoi.bash) copyright (C) 2000 Amit Singh.
|
|||
|
# All Rights Reserved.
|
|||
|
# http://hanoi.kernelthread.com
|
|||
|
|
|||
|
# hanoi2.bash
|
|||
|
# Version 2: modded for ASCII-graphic display.
|
|||
|
# Uses code contributed by Antonio Macchi,
|
|||
|
#+ with heavy editing by ABS Guide author.
|
|||
|
# This variant also falls under the original copyright, see above.
|
|||
|
# Used in ABS Guide with Amit Singh's permission (thanks!).
|
|||
|
|
|||
|
|
|||
|
# Variables #
|
|||
|
E_NOPARAM=86
|
|||
|
E_BADPARAM=87 # Illegal no. of disks passed to script.
|
|||
|
E_NOEXIT=88
|
|||
|
DELAY=2 # Interval, in seconds, between moves. Change, if desired.
|
|||
|
DISKS=$1
|
|||
|
Moves=0
|
|||
|
|
|||
|
MWIDTH=7
|
|||
|
MARGIN=2
|
|||
|
# Arbitrary "magic" constants, work okay for relatively small # of disks.
|
|||
|
# BASEWIDTH=51 # Original code.
|
|||
|
let "basewidth = $MWIDTH * $DISKS + $MARGIN" # "Base" beneath rods.
|
|||
|
# Above "algorithm" could likely stand improvement.
|
|||
|
|
|||
|
# Display variables.
|
|||
|
let "disks1 = $DISKS - 1"
|
|||
|
let "spaces1 = $DISKS"
|
|||
|
let "spaces2 = 2 * $DISKS"
|
|||
|
|
|||
|
let "lastmove_t = $DISKS - 1" # Final move?
|
|||
|
|
|||
|
|
|||
|
declare -a Rod1 Rod2 Rod3
|
|||
|
|
|||
|
#################
|
|||
|
|
|||
|
|
|||
|
function repeat { # $1=char $2=number of repetitions
|
|||
|
local n # Repeat-print a character.
|
|||
|
|
|||
|
for (( n=0; n<$2; n++ )); do
|
|||
|
echo -n "$1"
|
|||
|
done
|
|||
|
}
|
|||
|
|
|||
|
function FromRod {
|
|||
|
local rod summit weight sequence
|
|||
|
|
|||
|
while true; do
|
|||
|
rod=$1
|
|||
|
test ${rod/[^123]/} || continue
|
|||
|
|
|||
|
sequence=$(echo $(seq 0 $disks1 | tac))
|
|||
|
for summit in $sequence; do
|
|||
|
eval weight=\${Rod${rod}[$summit]}
|
|||
|
test $weight -ne 0 &&
|
|||
|
{ echo "$rod $summit $weight"; return; }
|
|||
|
done
|
|||
|
done
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
function ToRod { # $1=previous (FromRod) weight
|
|||
|
local rod firstfree weight sequence
|
|||
|
|
|||
|
while true; do
|
|||
|
rod=$2
|
|||
|
test ${rod/[^123]} || continue
|
|||
|
|
|||
|
sequence=$(echo $(seq 0 $disks1 | tac))
|
|||
|
for firstfree in $sequence; do
|
|||
|
eval weight=\${Rod${rod}[$firstfree]}
|
|||
|
test $weight -gt 0 && { (( firstfree++ )); break; }
|
|||
|
done
|
|||
|
test $weight -gt $1 -o $firstfree = 0 &&
|
|||
|
{ echo "$rod $firstfree"; return; }
|
|||
|
done
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
function PrintRods {
|
|||
|
local disk rod empty fill sp sequence
|
|||
|
|
|||
|
tput cup 5 0
|
|||
|
|
|||
|
repeat " " $spaces1
|
|||
|
echo -n "|"
|
|||
|
repeat " " $spaces2
|
|||
|
echo -n "|"
|
|||
|
repeat " " $spaces2
|
|||
|
echo "|"
|
|||
|
|
|||
|
sequence=$(echo $(seq 0 $disks1 | tac))
|
|||
|
for disk in $sequence; do
|
|||
|
for rod in {1..3}; do
|
|||
|
eval empty=$(( $DISKS - (Rod${rod}[$disk] / 2) ))
|
|||
|
eval fill=\${Rod${rod}[$disk]}
|
|||
|
repeat " " $empty
|
|||
|
test $fill -gt 0 && repeat "*" $fill || echo -n "|"
|
|||
|
repeat " " $empty
|
|||
|
done
|
|||
|
echo
|
|||
|
done
|
|||
|
repeat "=" $basewidth # Print "base" beneath rods.
|
|||
|
echo
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
display ()
|
|||
|
{
|
|||
|
echo
|
|||
|
PrintRods
|
|||
|
|
|||
|
# Get rod-number, summit and weight
|
|||
|
first=( `FromRod $1` )
|
|||
|
eval Rod${first[0]}[${first[1]}]=0
|
|||
|
|
|||
|
# Get rod-number and first-free position
|
|||
|
second=( `ToRod ${first[2]} $2` )
|
|||
|
eval Rod${second[0]}[${second[1]}]=${first[2]}
|
|||
|
|
|||
|
|
|||
|
if [ "${Rod3[lastmove_t]}" = 1 ]
|
|||
|
then # Last move? If yes, then display final position.
|
|||
|
tput cup 0 0
|
|||
|
echo; echo "+ Final Position: $Moves moves"
|
|||
|
PrintRods
|
|||
|
fi
|
|||
|
|
|||
|
sleep $DELAY
|
|||
|
}
|
|||
|
|
|||
|
# From here down, almost the same as original (hanoi.bash) script.
|
|||
|
|
|||
|
dohanoi() { # Recursive function.
|
|||
|
case $1 in
|
|||
|
0)
|
|||
|
;;
|
|||
|
*)
|
|||
|
dohanoi "$(($1-1))" $2 $4 $3
|
|||
|
if [ "$Moves" -ne 0 ]
|
|||
|
then
|
|||
|
tput cup 0 0
|
|||
|
echo; echo "+ Position after move $Moves"
|
|||
|
fi
|
|||
|
((Moves++))
|
|||
|
echo -n " Next move will be: "
|
|||
|
echo $2 "-->" $3
|
|||
|
display $2 $3
|
|||
|
dohanoi "$(($1-1))" $4 $3 $2
|
|||
|
;;
|
|||
|
esac
|
|||
|
}
|
|||
|
|
|||
|
setup_arrays ()
|
|||
|
{
|
|||
|
local dim n elem
|
|||
|
|
|||
|
let "dim1 = $1 - 1"
|
|||
|
elem=$dim1
|
|||
|
|
|||
|
for n in $(seq 0 $dim1)
|
|||
|
do
|
|||
|
let "Rod1[$elem] = 2 * $n + 1"
|
|||
|
Rod2[$n]=0
|
|||
|
Rod3[$n]=0
|
|||
|
((elem--))
|
|||
|
done
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
### Main ###
|
|||
|
|
|||
|
trap "tput cnorm" 0
|
|||
|
tput civis
|
|||
|
clear
|
|||
|
|
|||
|
setup_arrays $DISKS
|
|||
|
|
|||
|
tput cup 0 0
|
|||
|
echo; echo "+ Start Position"
|
|||
|
|
|||
|
case $# in
|
|||
|
1) case $(($1>0)) in # Must have at least one disk.
|
|||
|
1)
|
|||
|
disks=$1
|
|||
|
dohanoi $1 1 3 2
|
|||
|
# Total moves = 2^n - 1, where n = # of disks.
|
|||
|
echo
|
|||
|
exit 0;
|
|||
|
;;
|
|||
|
*)
|
|||
|
echo "$0: Illegal value for number of disks";
|
|||
|
exit $E_BADPARAM;
|
|||
|
;;
|
|||
|
esac
|
|||
|
;;
|
|||
|
*)
|
|||
|
echo "usage: $0 N"
|
|||
|
echo " Where \"N\" is the number of disks."
|
|||
|
exit $E_NOPARAM;
|
|||
|
;;
|
|||
|
esac
|
|||
|
|
|||
|
exit $E_NOEXIT # Shouldn't exit here.
|
|||
|
|
|||
|
# Exercise:
|
|||
|
# --------
|
|||
|
# There is a minor bug in the script that causes the display of
|
|||
|
#+ the next-to-last move to be skipped.
|
|||
|
#+ Fix this.</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="USEGETOPT"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-50. An alternate version of the
|
|||
|
<A
|
|||
|
HREF="string-manipulation.html#GETOPTSIMPLE"
|
|||
|
>getopt-simple.sh</A
|
|||
|
> script</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
# UseGetOpt.sh
|
|||
|
|
|||
|
# Author: Peggy Russell <prusselltechgroup@gmail.com>
|
|||
|
|
|||
|
UseGetOpt () {
|
|||
|
declare inputOptions
|
|||
|
declare -r E_OPTERR=85
|
|||
|
declare -r ScriptName=${0##*/}
|
|||
|
declare -r ShortOpts="adf:hlt"
|
|||
|
declare -r LongOpts="aoption,debug,file:,help,log,test"
|
|||
|
|
|||
|
DoSomething () {
|
|||
|
echo "The function name is '${FUNCNAME}'"
|
|||
|
# Recall that $FUNCNAME is an internal variable
|
|||
|
#+ holding the name of the function it is in.
|
|||
|
}
|
|||
|
|
|||
|
inputOptions=$(getopt -o "${ShortOpts}" --long \
|
|||
|
"${LongOpts}" --name "${ScriptName}" -- "${@}")
|
|||
|
|
|||
|
if [[ ($? -ne 0) || ($# -eq 0) ]]; then
|
|||
|
echo "Usage: ${ScriptName} [-dhlt] {OPTION...}"
|
|||
|
exit $E_OPTERR
|
|||
|
fi
|
|||
|
|
|||
|
eval set -- "${inputOptions}"
|
|||
|
|
|||
|
# Only for educational purposes. Can be removed.
|
|||
|
#-----------------------------------------------
|
|||
|
echo "++ Test: Number of arguments: [$#]"
|
|||
|
echo '++ Test: Looping through "$@"'
|
|||
|
for a in "$@"; do
|
|||
|
echo " ++ [$a]"
|
|||
|
done
|
|||
|
#-----------------------------------------------
|
|||
|
|
|||
|
while true; do
|
|||
|
case "${1}" in
|
|||
|
--aoption | -a) # Argument found.
|
|||
|
echo "Option [$1]"
|
|||
|
;;
|
|||
|
|
|||
|
--debug | -d) # Enable informational messages.
|
|||
|
echo "Option [$1] Debugging enabled"
|
|||
|
;;
|
|||
|
|
|||
|
--file | -f) # Check for optional argument.
|
|||
|
case "$2" in #+ Double colon is optional argument.
|
|||
|
"") # Not there.
|
|||
|
echo "Option [$1] Use default"
|
|||
|
shift
|
|||
|
;;
|
|||
|
|
|||
|
*) # Got it
|
|||
|
echo "Option [$1] Using input [$2]"
|
|||
|
shift
|
|||
|
;;
|
|||
|
|
|||
|
esac
|
|||
|
DoSomething
|
|||
|
;;
|
|||
|
|
|||
|
--log | -l) # Enable Logging.
|
|||
|
echo "Option [$1] Logging enabled"
|
|||
|
;;
|
|||
|
|
|||
|
--test | -t) # Enable testing.
|
|||
|
echo "Option [$1] Testing enabled"
|
|||
|
;;
|
|||
|
|
|||
|
--help | -h)
|
|||
|
echo "Option [$1] Display help"
|
|||
|
break
|
|||
|
;;
|
|||
|
|
|||
|
--) # Done! $# is argument number for "--", $@ is "--"
|
|||
|
echo "Option [$1] Dash Dash"
|
|||
|
break
|
|||
|
;;
|
|||
|
|
|||
|
*)
|
|||
|
echo "Major internal error!"
|
|||
|
exit 8
|
|||
|
;;
|
|||
|
|
|||
|
esac
|
|||
|
echo "Number of arguments: [$#]"
|
|||
|
shift
|
|||
|
done
|
|||
|
|
|||
|
shift
|
|||
|
# Only for educational purposes. Can be removed.
|
|||
|
#----------------------------------------------------------------------
|
|||
|
echo "++ Test: Number of arguments after \"--\" is [$#] They are: [$@]"
|
|||
|
echo '++ Test: Looping through "$@"'
|
|||
|
for a in "$@"; do
|
|||
|
echo " ++ [$a]"
|
|||
|
done
|
|||
|
#----------------------------------------------------------------------
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
################################### M A I N ########################
|
|||
|
# If you remove "function UseGetOpt () {" and corresponding "}",
|
|||
|
#+ you can uncomment the "exit 0" line below, and invoke this script
|
|||
|
#+ with the various options from the command-line.
|
|||
|
#-------------------------------------------------------------------
|
|||
|
# exit 0
|
|||
|
|
|||
|
echo "Test 1"
|
|||
|
UseGetOpt -f myfile one "two three" four
|
|||
|
|
|||
|
echo;echo "Test 2"
|
|||
|
UseGetOpt -h
|
|||
|
|
|||
|
echo;echo "Test 3 - Short Options"
|
|||
|
UseGetOpt -adltf myfile anotherfile
|
|||
|
|
|||
|
echo;echo "Test 4 - Long Options"
|
|||
|
UseGetOpt --aoption --debug --log --test --file myfile anotherfile
|
|||
|
|
|||
|
exit</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="USEGETOPT2"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-51. The version of the
|
|||
|
<I
|
|||
|
CLASS="FIRSTTERM"
|
|||
|
>UseGetOpt.sh</I
|
|||
|
> example used in the <A
|
|||
|
HREF="tabexpansion.html"
|
|||
|
>Tab Expansion appendix</A
|
|||
|
></B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
|
|||
|
# UseGetOpt-2.sh
|
|||
|
# Modified version of the script for illustrating tab-expansion
|
|||
|
#+ of command-line options.
|
|||
|
# See the "Introduction to Tab Expansion" appendix.
|
|||
|
|
|||
|
# Possible options: -a -d -f -l -t -h
|
|||
|
#+ --aoption, --debug --file --log --test -- help --
|
|||
|
|
|||
|
# Author of original script: Peggy Russell <prusselltechgroup@gmail.com>
|
|||
|
|
|||
|
|
|||
|
# UseGetOpt () {
|
|||
|
declare inputOptions
|
|||
|
declare -r E_OPTERR=85
|
|||
|
declare -r ScriptName=${0##*/}
|
|||
|
declare -r ShortOpts="adf:hlt"
|
|||
|
declare -r LongOpts="aoption,debug,file:,help,log,test"
|
|||
|
|
|||
|
DoSomething () {
|
|||
|
echo "The function name is '${FUNCNAME}'"
|
|||
|
}
|
|||
|
|
|||
|
inputOptions=$(getopt -o "${ShortOpts}" --long \
|
|||
|
"${LongOpts}" --name "${ScriptName}" -- "${@}")
|
|||
|
|
|||
|
if [[ ($? -ne 0) || ($# -eq 0) ]]; then
|
|||
|
echo "Usage: ${ScriptName} [-dhlt] {OPTION...}"
|
|||
|
exit $E_OPTERR
|
|||
|
fi
|
|||
|
|
|||
|
eval set -- "${inputOptions}"
|
|||
|
|
|||
|
|
|||
|
while true; do
|
|||
|
case "${1}" in
|
|||
|
--aoption | -a) # Argument found.
|
|||
|
echo "Option [$1]"
|
|||
|
;;
|
|||
|
|
|||
|
--debug | -d) # Enable informational messages.
|
|||
|
echo "Option [$1] Debugging enabled"
|
|||
|
;;
|
|||
|
|
|||
|
--file | -f) # Check for optional argument.
|
|||
|
case "$2" in #+ Double colon is optional argument.
|
|||
|
"") # Not there.
|
|||
|
echo "Option [$1] Use default"
|
|||
|
shift
|
|||
|
;;
|
|||
|
|
|||
|
*) # Got it
|
|||
|
echo "Option [$1] Using input [$2]"
|
|||
|
shift
|
|||
|
;;
|
|||
|
|
|||
|
esac
|
|||
|
DoSomething
|
|||
|
;;
|
|||
|
|
|||
|
--log | -l) # Enable Logging.
|
|||
|
echo "Option [$1] Logging enabled"
|
|||
|
;;
|
|||
|
|
|||
|
--test | -t) # Enable testing.
|
|||
|
echo "Option [$1] Testing enabled"
|
|||
|
;;
|
|||
|
|
|||
|
--help | -h)
|
|||
|
echo "Option [$1] Display help"
|
|||
|
break
|
|||
|
;;
|
|||
|
|
|||
|
--) # Done! $# is argument number for "--", $@ is "--"
|
|||
|
echo "Option [$1] Dash Dash"
|
|||
|
break
|
|||
|
;;
|
|||
|
|
|||
|
*)
|
|||
|
echo "Major internal error!"
|
|||
|
exit 8
|
|||
|
;;
|
|||
|
|
|||
|
esac
|
|||
|
echo "Number of arguments: [$#]"
|
|||
|
shift
|
|||
|
done
|
|||
|
|
|||
|
shift
|
|||
|
|
|||
|
# }
|
|||
|
|
|||
|
exit</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="SHOWALLC"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-52. Cycling through all the possible color backgrounds</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
|
|||
|
# show-all-colors.sh
|
|||
|
# Displays all 256 possible background colors, using ANSI escape sequences.
|
|||
|
# Author: Chetankumar Phulpagare
|
|||
|
# Used in ABS Guide with permission.
|
|||
|
|
|||
|
T1=8
|
|||
|
T2=6
|
|||
|
T3=36
|
|||
|
offset=0
|
|||
|
|
|||
|
for num1 in {0..7}
|
|||
|
do {
|
|||
|
for num2 in {0,1}
|
|||
|
do {
|
|||
|
shownum=`echo "$offset + $T1 * ${num2} + $num1" | bc`
|
|||
|
echo -en "\E[0;48;5;${shownum}m color ${shownum} \E[0m"
|
|||
|
}
|
|||
|
done
|
|||
|
echo
|
|||
|
}
|
|||
|
done
|
|||
|
|
|||
|
offset=16
|
|||
|
for num1 in {0..5}
|
|||
|
do {
|
|||
|
for num2 in {0..5}
|
|||
|
do {
|
|||
|
for num3 in {0..5}
|
|||
|
do {
|
|||
|
shownum=`echo "$offset + $T2 * ${num3} \
|
|||
|
+ $num2 + $T3 * ${num1}" | bc`
|
|||
|
echo -en "\E[0;48;5;${shownum}m color ${shownum} \E[0m"
|
|||
|
}
|
|||
|
done
|
|||
|
echo
|
|||
|
}
|
|||
|
done
|
|||
|
}
|
|||
|
done
|
|||
|
|
|||
|
offset=232
|
|||
|
for num1 in {0..23}
|
|||
|
do {
|
|||
|
shownum=`expr $offset + $num1`
|
|||
|
echo -en "\E[0;48;5;${shownum}m ${shownum}\E[0m"
|
|||
|
}
|
|||
|
done
|
|||
|
|
|||
|
echo</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="SAMORSE"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-53. Morse Code Practice</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
# sam.sh, v. .01a
|
|||
|
# Still Another Morse (code training script)
|
|||
|
# With profuse apologies to Sam (F.B.) Morse.
|
|||
|
# Author: Mendel Cooper
|
|||
|
# License: GPL3
|
|||
|
# Reldate: 05/25/11
|
|||
|
|
|||
|
# Morse code training script.
|
|||
|
# Converts arguments to audible dots and dashes.
|
|||
|
# Note: lowercase input only at this time.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# Get the wav files from the source tarball:
|
|||
|
# http://bash.deta.in/abs-guide-latest.tar.bz2
|
|||
|
DOT='soundfiles/dot.wav'
|
|||
|
DASH='soundfiles/dash.wav'
|
|||
|
# Maybe move soundfiles to /usr/local/sounds?
|
|||
|
|
|||
|
LETTERSPACE=300000 # Microseconds.
|
|||
|
WORDSPACE=980000
|
|||
|
# Nice and slow, for beginners. Maybe 5 wpm?
|
|||
|
|
|||
|
EXIT_MSG="May the Morse be with you!"
|
|||
|
E_NOARGS=75 # No command-line args?
|
|||
|
|
|||
|
|
|||
|
|
|||
|
declare -A morse # Associative array!
|
|||
|
# ======================================= #
|
|||
|
morse[a]="dot; dash"
|
|||
|
morse[b]="dash; dot; dot; dot"
|
|||
|
morse[c]="dash; dot; dash; dot"
|
|||
|
morse[d]="dash; dot; dot"
|
|||
|
morse[e]="dot"
|
|||
|
morse[f]="dot; dot; dash; dot"
|
|||
|
morse[g]="dash; dash; dot"
|
|||
|
morse[h]="dot; dot; dot; dot"
|
|||
|
morse[i]="dot; dot;"
|
|||
|
morse[j]="dot; dash; dash; dash"
|
|||
|
morse[k]="dash; dot; dash"
|
|||
|
morse[l]="dot; dash; dot; dot"
|
|||
|
morse[m]="dash; dash"
|
|||
|
morse[n]="dash; dot"
|
|||
|
morse[o]="dash; dash; dash"
|
|||
|
morse[p]="dot; dash; dash; dot"
|
|||
|
morse[q]="dash; dash; dot; dash"
|
|||
|
morse[r]="dot; dash; dot"
|
|||
|
morse[s]="dot; dot; dot"
|
|||
|
morse[t]="dash"
|
|||
|
morse[u]="dot; dot; dash"
|
|||
|
morse[v]="dot; dot; dot; dash"
|
|||
|
morse[w]="dot; dash; dash"
|
|||
|
morse[x]="dash; dot; dot; dash"
|
|||
|
morse[y]="dash; dot; dash; dash"
|
|||
|
morse[z]="dash; dash; dot; dot"
|
|||
|
morse[0]="dash; dash; dash; dash; dash"
|
|||
|
morse[1]="dot; dash; dash; dash; dash"
|
|||
|
morse[2]="dot; dot; dash; dash; dash"
|
|||
|
morse[3]="dot; dot; dot; dash; dash"
|
|||
|
morse[4]="dot; dot; dot; dot; dash"
|
|||
|
morse[5]="dot; dot; dot; dot; dot"
|
|||
|
morse[6]="dash; dot; dot; dot; dot"
|
|||
|
morse[7]="dash; dash; dot; dot; dot"
|
|||
|
morse[8]="dash; dash; dash; dot; dot"
|
|||
|
morse[9]="dash; dash; dash; dash; dot"
|
|||
|
# The following must be escaped or quoted.
|
|||
|
morse[?]="dot; dot; dash; dash; dot; dot"
|
|||
|
morse[.]="dot; dash; dot; dash; dot; dash"
|
|||
|
morse[,]="dash; dash; dot; dot; dash; dash"
|
|||
|
morse[/]="dash; dot; dot; dash; dot"
|
|||
|
morse[\@]="dot; dash; dash; dot; dash; dot"
|
|||
|
# ======================================= #
|
|||
|
|
|||
|
play_letter ()
|
|||
|
{
|
|||
|
eval ${morse[$1]} # Play dots, dashes from appropriate sound files.
|
|||
|
# Why is 'eval' necessary here?
|
|||
|
usleep $LETTERSPACE # Pause in between letters.
|
|||
|
}
|
|||
|
|
|||
|
extract_letters ()
|
|||
|
{ # Slice string apart, letter by letter.
|
|||
|
local pos=0 # Starting at left end of string.
|
|||
|
local len=1 # One letter at a time.
|
|||
|
strlen=${#1}
|
|||
|
|
|||
|
while [ $pos -lt $strlen ]
|
|||
|
do
|
|||
|
letter=${1:pos:len}
|
|||
|
# ^^^^^^^^^^^^ See Chapter 10.1.
|
|||
|
play_letter $letter
|
|||
|
echo -n "*" # Mark letter just played.
|
|||
|
((pos++))
|
|||
|
done
|
|||
|
}
|
|||
|
|
|||
|
######### Play the sounds ############
|
|||
|
dot() { aplay "$DOT" 2&>/dev/null; }
|
|||
|
dash() { aplay "$DASH" 2&>/dev/null; }
|
|||
|
######################################
|
|||
|
|
|||
|
no_args ()
|
|||
|
{
|
|||
|
declare -a usage
|
|||
|
usage=( $0 word1 word2 ... )
|
|||
|
|
|||
|
echo "Usage:"; echo
|
|||
|
echo ${usage[*]}
|
|||
|
for index in 0 1 2 3
|
|||
|
do
|
|||
|
extract_letters ${usage[index]}
|
|||
|
usleep $WORDSPACE
|
|||
|
echo -n " " # Print space between words.
|
|||
|
done
|
|||
|
# echo "Usage: $0 word1 word2 ... "
|
|||
|
echo; echo
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
# int main()
|
|||
|
# {
|
|||
|
|
|||
|
clear # Clear the terminal screen.
|
|||
|
echo " SAM"
|
|||
|
echo "Still Another Morse code trainer"
|
|||
|
echo " Author: Mendel Cooper"
|
|||
|
echo; echo;
|
|||
|
|
|||
|
if [ -z "$1" ]
|
|||
|
then
|
|||
|
no_args
|
|||
|
echo; echo; echo "$EXIT_MSG"; echo
|
|||
|
exit $E_NOARGS
|
|||
|
fi
|
|||
|
|
|||
|
echo; echo "$*" # Print text that will be played.
|
|||
|
|
|||
|
until [ -z "$1" ]
|
|||
|
do
|
|||
|
extract_letters $1
|
|||
|
shift # On to next word.
|
|||
|
usleep $WORDSPACE
|
|||
|
echo -n " " # Print space between words.
|
|||
|
done
|
|||
|
|
|||
|
echo; echo; echo "$EXIT_MSG"; echo
|
|||
|
|
|||
|
exit 0
|
|||
|
# }
|
|||
|
|
|||
|
# Exercises:
|
|||
|
# ---------
|
|||
|
# 1) Have the script accept either lowercase or uppercase words
|
|||
|
#+ as arguments. Hint: Use 'tr' . . .
|
|||
|
# 2) Have the script optionally accept input from a text file.</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="BASE64"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-54. Base64 encoding/decoding</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
# base64.sh: Bash implementation of Base64 encoding and decoding.
|
|||
|
#
|
|||
|
# Copyright (c) 2011 vladz <vladz@devzero.fr>
|
|||
|
# Used in ABSG with permission (thanks!).
|
|||
|
#
|
|||
|
# Encode or decode original Base64 (and also Base64url)
|
|||
|
#+ from STDIN to STDOUT.
|
|||
|
#
|
|||
|
# Usage:
|
|||
|
#
|
|||
|
# Encode
|
|||
|
# $ ./base64.sh < binary-file > binary-file.base64
|
|||
|
# Decode
|
|||
|
# $ ./base64.sh -d < binary-file.base64 > binary-file
|
|||
|
#
|
|||
|
# Reference:
|
|||
|
#
|
|||
|
# [1] RFC4648 - "The Base16, Base32, and Base64 Data Encodings"
|
|||
|
# http://tools.ietf.org/html/rfc4648#section-5
|
|||
|
|
|||
|
|
|||
|
# The base64_charset[] array contains entire base64 charset,
|
|||
|
# and additionally the character "=" ...
|
|||
|
base64_charset=( {A..Z} {a..z} {0..9} + / = )
|
|||
|
# Nice illustration of brace expansion.
|
|||
|
|
|||
|
# Uncomment the ### line below to use base64url encoding instead of
|
|||
|
#+ original base64.
|
|||
|
### base64_charset=( {A..Z} {a..z} {0..9} - _ = )
|
|||
|
|
|||
|
# Output text width when encoding
|
|||
|
#+ (64 characters, just like openssl output).
|
|||
|
text_width=64
|
|||
|
|
|||
|
function display_base64_char {
|
|||
|
# Convert a 6-bit number (between 0 and 63) into its corresponding values
|
|||
|
#+ in Base64, then display the result with the specified text width.
|
|||
|
printf "${base64_charset[$1]}"; (( width++ ))
|
|||
|
(( width % text_width == 0 )) && printf "\n"
|
|||
|
}
|
|||
|
|
|||
|
function encode_base64 {
|
|||
|
# Encode three 8-bit hexadecimal codes into four 6-bit numbers.
|
|||
|
# We need two local int array variables:
|
|||
|
# c8[]: to store the codes of the 8-bit characters to encode
|
|||
|
# c6[]: to store the corresponding encoded values on 6-bit
|
|||
|
declare -a -i c8 c6
|
|||
|
|
|||
|
# Convert hexadecimal to decimal.
|
|||
|
c8=( $(printf "ibase=16; ${1:0:2}\n${1:2:2}\n${1:4:2}\n" | bc) )
|
|||
|
|
|||
|
# Let's play with bitwise operators
|
|||
|
#+ (3x8-bit into 4x6-bits conversion).
|
|||
|
(( c6[0] = c8[0] >> 2 ))
|
|||
|
(( c6[1] = ((c8[0] & 3) << 4) | (c8[1] >> 4) ))
|
|||
|
|
|||
|
# The following operations depend on the c8 element number.
|
|||
|
case ${#c8[*]} in
|
|||
|
3) (( c6[2] = ((c8[1] & 15) << 2) | (c8[2] >> 6) ))
|
|||
|
(( c6[3] = c8[2] & 63 )) ;;
|
|||
|
2) (( c6[2] = (c8[1] & 15) << 2 ))
|
|||
|
(( c6[3] = 64 )) ;;
|
|||
|
1) (( c6[2] = c6[3] = 64 )) ;;
|
|||
|
esac
|
|||
|
|
|||
|
for char in ${c6[@]}; do
|
|||
|
display_base64_char ${char}
|
|||
|
done
|
|||
|
}
|
|||
|
|
|||
|
function decode_base64 {
|
|||
|
# Decode four base64 characters into three hexadecimal ASCII characters.
|
|||
|
# c8[]: to store the codes of the 8-bit characters
|
|||
|
# c6[]: to store the corresponding Base64 values on 6-bit
|
|||
|
declare -a -i c8 c6
|
|||
|
|
|||
|
# Find decimal value corresponding to the current base64 character.
|
|||
|
for current_char in ${1:0:1} ${1:1:1} ${1:2:1} ${1:3:1}; do
|
|||
|
[ "${current_char}" = "=" ] && break
|
|||
|
|
|||
|
position=0
|
|||
|
while [ "${current_char}" != "${base64_charset[${position}]}" ]; do
|
|||
|
(( position++ ))
|
|||
|
done
|
|||
|
|
|||
|
c6=( ${c6[*]} ${position} )
|
|||
|
done
|
|||
|
|
|||
|
# Let's play with bitwise operators
|
|||
|
#+ (4x8-bit into 3x6-bits conversion).
|
|||
|
(( c8[0] = (c6[0] << 2) | (c6[1] >> 4) ))
|
|||
|
|
|||
|
# The next operations depends on the c6 elements number.
|
|||
|
case ${#c6[*]} in
|
|||
|
3) (( c8[1] = ( (c6[1] & 15) << 4) | (c6[2] >> 2) ))
|
|||
|
(( c8[2] = (c6[2] & 3) << 6 )); unset c8[2] ;;
|
|||
|
4) (( c8[1] = ( (c6[1] & 15) << 4) | (c6[2] >> 2) ))
|
|||
|
(( c8[2] = ( (c6[2] & 3) << 6) | c6[3] )) ;;
|
|||
|
esac
|
|||
|
|
|||
|
for char in ${c8[*]}; do
|
|||
|
printf "\x$(printf "%x" ${char})"
|
|||
|
done
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
# main ()
|
|||
|
|
|||
|
if [ "$1" = "-d" ]; then # decode
|
|||
|
|
|||
|
# Reformat STDIN in pseudo 4x6-bit groups.
|
|||
|
content=$(cat - | tr -d "\n" | sed -r "s/(.{4})/\1 /g")
|
|||
|
|
|||
|
for chars in ${content}; do decode_base64 ${chars}; done
|
|||
|
|
|||
|
else
|
|||
|
# Make a hexdump of stdin and reformat in 3-byte groups.
|
|||
|
content=$(cat - | xxd -ps -u | sed -r "s/(\w{6})/\1 /g" |
|
|||
|
tr -d "\n")
|
|||
|
|
|||
|
for chars in ${content}; do encode_base64 ${chars}; done
|
|||
|
|
|||
|
echo
|
|||
|
|
|||
|
fi</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="SEDAPPEND"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-55. Inserting text in a file using
|
|||
|
<I
|
|||
|
CLASS="FIRSTTERM"
|
|||
|
>sed</I
|
|||
|
></B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
# Prepends a string at a specified line
|
|||
|
#+ in files with names ending in "sample"
|
|||
|
#+ in the current working directory.
|
|||
|
# 000000000000000000000000000000000000
|
|||
|
# This script overwrites files!
|
|||
|
# Be careful running it in a directory
|
|||
|
#+ where you have important files!!!
|
|||
|
# 000000000000000000000000000000000000
|
|||
|
|
|||
|
# Create a couple of files to operate on ...
|
|||
|
# 01sample
|
|||
|
# 02sample
|
|||
|
# ... etc.
|
|||
|
# These files must not be empty, else the prepend will not work.
|
|||
|
|
|||
|
lineno=1 # Append at line 1 (prepend).
|
|||
|
filespec="*sample" # Filename pattern to operate on.
|
|||
|
|
|||
|
string=$(whoami) # Will set your username as string to insert.
|
|||
|
# It could just as easily be any other string.
|
|||
|
|
|||
|
for file in $filespec # Specify which files to alter.
|
|||
|
do # ^^^^^^^^^
|
|||
|
sed -i ""$lineno"i "$string"" $file
|
|||
|
# ^^ -i option edits files in-place.
|
|||
|
# ^ Insert (i) command.
|
|||
|
echo ""$file" altered!"
|
|||
|
done
|
|||
|
|
|||
|
echo "Warning: files possibly clobbered!"
|
|||
|
|
|||
|
exit 0
|
|||
|
|
|||
|
# Exercise:
|
|||
|
# Add error checking to this script.
|
|||
|
# It needs it badly.</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="GRONSFELD"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-56. The Gronsfeld Cipher</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
# gronsfeld.bash
|
|||
|
|
|||
|
# License: GPL3
|
|||
|
# Reldate 06/23/11
|
|||
|
|
|||
|
# This is an implementation of the Gronsfeld Cipher.
|
|||
|
# It's essentially a stripped-down variant of the
|
|||
|
#+ polyalphabetic Vigen<65>re Tableau, but with only 10 alphabets.
|
|||
|
# The classic Gronsfeld has a numeric sequence as the key word,
|
|||
|
#+ but here we substitute a letter string, for ease of use.
|
|||
|
# Allegedly, this cipher was invented by the eponymous Count Gronsfeld
|
|||
|
#+ in the 17th Century. It was at one time considered to be unbreakable.
|
|||
|
# Note that this is ###not### a secure cipher by modern standards.
|
|||
|
|
|||
|
# Global Variables #
|
|||
|
Enc_suffix="29379" # Encrypted text output with this 5-digit suffix.
|
|||
|
# This functions as a decryption flag,
|
|||
|
#+ and when used to generate passwords adds security.
|
|||
|
Default_key="gronsfeldk"
|
|||
|
# The script uses this if key not entered below
|
|||
|
# (at "Keychain").
|
|||
|
# Change the above two values frequently
|
|||
|
#+ for added security.
|
|||
|
|
|||
|
GROUPLEN=5 # Output in groups of 5 letters, per tradition.
|
|||
|
alpha1=( abcdefghijklmnopqrstuvwxyz )
|
|||
|
alpha2=( {A..Z} ) # Output in all caps, per tradition.
|
|||
|
# Use alpha2=( {a..z} ) for password generator.
|
|||
|
wraplen=26 # Wrap around if past end of alphabet.
|
|||
|
dflag= # Decrypt flag (set if $Enc_suffix present).
|
|||
|
E_NOARGS=76 # Missing command-line args?
|
|||
|
DEBUG=77 # Debugging flag.
|
|||
|
declare -a offsets # This array holds the numeric shift values for
|
|||
|
#+ encryption/decryption.
|
|||
|
|
|||
|
########Keychain#########
|
|||
|
key= ### Put key here!!!
|
|||
|
# 10 characters!
|
|||
|
#########################
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# Function
|
|||
|
: ()
|
|||
|
{ # Encrypt or decrypt, depending on whether $dflag is set.
|
|||
|
# Why ": ()" as a function name? Just to prove that it can be done.
|
|||
|
|
|||
|
local idx keydx mlen off1 shft
|
|||
|
local plaintext="$1"
|
|||
|
local mlen=${#plaintext}
|
|||
|
|
|||
|
for (( idx=0; idx<$mlen; idx++ ))
|
|||
|
do
|
|||
|
let "keydx = $idx % $keylen"
|
|||
|
shft=${offsets[keydx]}
|
|||
|
|
|||
|
if [ -n "$dflag" ]
|
|||
|
then # Decrypt!
|
|||
|
let "off1 = $(expr index "${alpha1[*]}" ${plaintext:idx:1}) - $shft"
|
|||
|
# Shift backward to decrypt.
|
|||
|
else # Encrypt!
|
|||
|
let "off1 = $(expr index "${alpha1[*]}" ${plaintext:idx:1}) + $shft"
|
|||
|
# Shift forward to encrypt.
|
|||
|
test $(( $idx % $GROUPLEN)) = 0 && echo -n " " # Groups of 5 letters.
|
|||
|
# Comment out above line for output as a string without whitespace,
|
|||
|
#+ for example, if using the script as a password generator.
|
|||
|
fi
|
|||
|
|
|||
|
((off1--)) # Normalize. Why is this necessary?
|
|||
|
|
|||
|
if [ $off1 -lt 0 ]
|
|||
|
then # Catch negative indices.
|
|||
|
let "off1 += $wraplen"
|
|||
|
fi
|
|||
|
|
|||
|
((off1 %= $wraplen)) # Wrap around if past end of alphabet.
|
|||
|
|
|||
|
echo -n "${alpha2[off1]}"
|
|||
|
|
|||
|
done
|
|||
|
|
|||
|
if [ -z "$dflag" ]
|
|||
|
then
|
|||
|
echo " $Enc_suffix"
|
|||
|
# echo "$Enc_suffix" # For password generator.
|
|||
|
else
|
|||
|
echo
|
|||
|
fi
|
|||
|
} # End encrypt/decrypt function.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# int main () {
|
|||
|
|
|||
|
# Check for command-line args.
|
|||
|
if [ -z "$1" ]
|
|||
|
then
|
|||
|
echo "Usage: $0 TEXT TO ENCODE/DECODE"
|
|||
|
exit $E_NOARGS
|
|||
|
fi
|
|||
|
|
|||
|
if [ ${!#} == "$Enc_suffix" ]
|
|||
|
# ^^^^^ Final command-line arg.
|
|||
|
then
|
|||
|
dflag=ON
|
|||
|
echo -n "+" # Flag decrypted text with a "+" for easy ID.
|
|||
|
fi
|
|||
|
|
|||
|
if [ -z "$key" ]
|
|||
|
then
|
|||
|
key="$Default_key" # "gronsfeldk" per above.
|
|||
|
fi
|
|||
|
|
|||
|
keylen=${#key}
|
|||
|
|
|||
|
for (( idx=0; idx<$keylen; idx++ ))
|
|||
|
do # Calculate shift values for encryption/decryption.
|
|||
|
offsets[idx]=$(expr index "${alpha1[*]}" ${key:idx:1}) # Normalize.
|
|||
|
((offsets[idx]--)) # Necessary because "expr index" starts at 1,
|
|||
|
#+ whereas array count starts at 0.
|
|||
|
# Generate array of numerical offsets corresponding to the key.
|
|||
|
# There are simpler ways to accomplish this.
|
|||
|
done
|
|||
|
|
|||
|
args=$(echo "$*" | sed -e 's/ //g' | tr A-Z a-z | sed -e 's/[0-9]//g')
|
|||
|
# Remove whitespace and digits from command-line args.
|
|||
|
# Can modify to also remove punctuation characters, if desired.
|
|||
|
|
|||
|
# Debug:
|
|||
|
# echo "$args"; exit $DEBUG
|
|||
|
|
|||
|
: "$args" # Call the function named ":".
|
|||
|
# : is a null operator, except . . . when it's a function name!
|
|||
|
|
|||
|
exit $? # } End-of-script
|
|||
|
|
|||
|
|
|||
|
# ************************************************************** #
|
|||
|
# This script can function as a password generator,
|
|||
|
#+ with several minor mods, see above.
|
|||
|
# That would allow an easy-to-remember password, even the word
|
|||
|
#+ "password" itself, which encrypts to vrgfotvo29379
|
|||
|
#+ a fairly secure password not susceptible to a dictionary attack.
|
|||
|
# Or, you could use your own name (surely that's easy to remember!).
|
|||
|
# For example, Bozo Bozeman encrypts to hfnbttdppkt29379.
|
|||
|
# ************************************************************** #</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="BINGO"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-57. Bingo Number Generator</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
# bingo.sh
|
|||
|
# Bingo number generator
|
|||
|
# Reldate 20Aug12, License: Public Domain
|
|||
|
|
|||
|
#######################################################################
|
|||
|
# This script generates bingo numbers.
|
|||
|
# Hitting a key generates a new number.
|
|||
|
# Hitting 'q' terminates the script.
|
|||
|
# In a given run of the script, there will be no duplicate numbers.
|
|||
|
# When the script terminates, it prints a log of the numbers generated.
|
|||
|
#######################################################################
|
|||
|
|
|||
|
MIN=1 # Lowest allowable bingo number.
|
|||
|
MAX=75 # Highest allowable bingo number.
|
|||
|
COLS=15 # Numbers in each column (B I N G O).
|
|||
|
SINGLE_DIGIT_MAX=9
|
|||
|
|
|||
|
declare -a Numbers
|
|||
|
Prefix=(B I N G O)
|
|||
|
|
|||
|
initialize_Numbers ()
|
|||
|
{ # Zero them out to start.
|
|||
|
# They'll be incremented if chosen.
|
|||
|
local index=0
|
|||
|
until [ "$index" -gt $MAX ]
|
|||
|
do
|
|||
|
Numbers[index]=0
|
|||
|
((index++))
|
|||
|
done
|
|||
|
|
|||
|
Numbers[0]=1 # Flag zero, so it won't be selected.
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
generate_number ()
|
|||
|
{
|
|||
|
local number
|
|||
|
|
|||
|
while [ 1 ]
|
|||
|
do
|
|||
|
let "number = $(expr $RANDOM % $MAX)"
|
|||
|
if [ ${Numbers[number]} -eq 0 ] # Number not yet called.
|
|||
|
then
|
|||
|
let "Numbers[number]+=1" # Flag it in the array.
|
|||
|
break # And terminate loop.
|
|||
|
fi # Else if already called, loop and generate another number.
|
|||
|
done
|
|||
|
# Exercise: Rewrite this more elegantly as an until-loop.
|
|||
|
|
|||
|
return $number
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
print_numbers_called ()
|
|||
|
{ # Print out the called number log in neat columns.
|
|||
|
# echo ${Numbers[@]}
|
|||
|
|
|||
|
local pre2=0 # Prefix a zero, so columns will align
|
|||
|
#+ on single-digit numbers.
|
|||
|
|
|||
|
echo "Number Stats"
|
|||
|
|
|||
|
for (( index=1; index<=MAX; index++))
|
|||
|
do
|
|||
|
count=${Numbers[index]}
|
|||
|
let "t = $index - 1" # Normalize, since array begins with index 0.
|
|||
|
let "column = $(expr $t / $COLS)"
|
|||
|
pre=${Prefix[column]}
|
|||
|
# echo -n "${Prefix[column]} "
|
|||
|
|
|||
|
if [ $(expr $t % $COLS) -eq 0 ]
|
|||
|
then
|
|||
|
echo # Newline at end of row.
|
|||
|
fi
|
|||
|
|
|||
|
if [ "$index" -gt $SINGLE_DIGIT_MAX ] # Check for single-digit number.
|
|||
|
then
|
|||
|
echo -n "$pre$index#$count "
|
|||
|
else # Prefix a zero.
|
|||
|
echo -n "$pre$pre2$index#$count "
|
|||
|
fi
|
|||
|
|
|||
|
done
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# main () {
|
|||
|
RANDOM=$$ # Seed random number generator.
|
|||
|
|
|||
|
initialize_Numbers # Zero out the number tracking array.
|
|||
|
|
|||
|
clear
|
|||
|
echo "Bingo Number Caller"; echo
|
|||
|
|
|||
|
while [[ "$key" != "q" ]] # Main loop.
|
|||
|
do
|
|||
|
read -s -n1 -p "Hit a key for the next number [q to exit] " key
|
|||
|
# Usually 'q' exits, but not always.
|
|||
|
# Can always hit Ctl-C if q fails.
|
|||
|
echo
|
|||
|
|
|||
|
generate_number; new_number=$?
|
|||
|
|
|||
|
let "column = $(expr $new_number / $COLS)"
|
|||
|
echo -n "${Prefix[column]} " # B-I-N-G-O
|
|||
|
|
|||
|
echo $new_number
|
|||
|
done
|
|||
|
|
|||
|
echo; echo
|
|||
|
|
|||
|
# Game over ...
|
|||
|
print_numbers_called
|
|||
|
echo; echo "[#0 = not called . . . #1 = called]"
|
|||
|
|
|||
|
echo
|
|||
|
|
|||
|
exit 0
|
|||
|
# }
|
|||
|
|
|||
|
|
|||
|
# Certainly, this script could stand some improvement.
|
|||
|
#See also the author's Instructable:
|
|||
|
#www.instructables.com/id/Binguino-An-Arduino-based-Bingo-Number-Generato/</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><P
|
|||
|
><A
|
|||
|
NAME="BASICSREV0"
|
|||
|
></A
|
|||
|
>To end this section, a review of the
|
|||
|
basics . . . and more.</P
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="BASICSREVIEWED"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-58. Basics Reviewed</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
# basics-reviewed.bash
|
|||
|
|
|||
|
# File extension == *.bash == specific to Bash
|
|||
|
|
|||
|
# Copyright (c) Michael S. Zick, 2003; All rights reserved.
|
|||
|
# License: Use in any form, for any purpose.
|
|||
|
# Revision: $ID$
|
|||
|
#
|
|||
|
# Edited for layout by M.C.
|
|||
|
# (author of the "Advanced Bash Scripting Guide")
|
|||
|
# Fixes and updates (04/08) by Cliff Bamford.
|
|||
|
|
|||
|
|
|||
|
# This script tested under Bash versions 2.04, 2.05a and 2.05b.
|
|||
|
# It may not work with earlier versions.
|
|||
|
# This demonstration script generates one --intentional--
|
|||
|
#+ "command not found" error message. See line 436.
|
|||
|
|
|||
|
# The current Bash maintainer, Chet Ramey, has fixed the items noted
|
|||
|
#+ for later versions of Bash.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
###-------------------------------------------###
|
|||
|
### Pipe the output of this script to 'more' ###
|
|||
|
###+ else it will scroll off the page. ###
|
|||
|
### ###
|
|||
|
### You may also redirect its output ###
|
|||
|
###+ to a file for examination. ###
|
|||
|
###-------------------------------------------###
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# Most of the following points are described at length in
|
|||
|
#+ the text of the foregoing "Advanced Bash Scripting Guide."
|
|||
|
# This demonstration script is mostly just a reorganized presentation.
|
|||
|
# -- msz
|
|||
|
|
|||
|
# Variables are not typed unless otherwise specified.
|
|||
|
|
|||
|
# Variables are named. Names must contain a non-digit.
|
|||
|
# File descriptor names (as in, for example: 2>&1)
|
|||
|
#+ contain ONLY digits.
|
|||
|
|
|||
|
# Parameters and Bash array elements are numbered.
|
|||
|
# (Parameters are very similar to Bash arrays.)
|
|||
|
|
|||
|
# A variable name may be undefined (null reference).
|
|||
|
unset VarNull
|
|||
|
|
|||
|
# A variable name may be defined but empty (null contents).
|
|||
|
VarEmpty='' # Two, adjacent, single quotes.
|
|||
|
|
|||
|
# A variable name may be defined and non-empty.
|
|||
|
VarSomething='Literal'
|
|||
|
|
|||
|
# A variable may contain:
|
|||
|
# * A whole number as a signed 32-bit (or larger) integer
|
|||
|
# * A string
|
|||
|
# A variable may also be an array.
|
|||
|
|
|||
|
# A string may contain embedded blanks and may be treated
|
|||
|
#+ as if it where a function name with optional arguments.
|
|||
|
|
|||
|
# The names of variables and the names of functions
|
|||
|
#+ are in different namespaces.
|
|||
|
|
|||
|
|
|||
|
# A variable may be defined as a Bash array either explicitly or
|
|||
|
#+ implicitly by the syntax of the assignment statement.
|
|||
|
# Explicit:
|
|||
|
declare -a ArrayVar
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# The echo command is a builtin.
|
|||
|
echo $VarSomething
|
|||
|
|
|||
|
# The printf command is a builtin.
|
|||
|
# Translate %s as: String-Format
|
|||
|
printf %s $VarSomething # No linebreak specified, none output.
|
|||
|
echo # Default, only linebreak output.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# The Bash parser word breaks on whitespace.
|
|||
|
# Whitespace, or the lack of it is significant.
|
|||
|
# (This holds true in general; there are, of course, exceptions.)
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# Translate the DOLLAR_SIGN character as: Content-Of.
|
|||
|
|
|||
|
# Extended-Syntax way of writing Content-Of:
|
|||
|
echo ${VarSomething}
|
|||
|
|
|||
|
# The ${ ... } Extended-Syntax allows more than just the variable
|
|||
|
#+ name to be specified.
|
|||
|
# In general, $VarSomething can always be written as: ${VarSomething}.
|
|||
|
|
|||
|
# Call this script with arguments to see the following in action.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# Outside of double-quotes, the special characters @ and *
|
|||
|
#+ specify identical behavior.
|
|||
|
# May be pronounced as: All-Elements-Of.
|
|||
|
|
|||
|
# Without specification of a name, they refer to the
|
|||
|
#+ pre-defined parameter Bash-Array.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# Glob-Pattern references
|
|||
|
echo $* # All parameters to script or function
|
|||
|
echo ${*} # Same
|
|||
|
|
|||
|
# Bash disables filename expansion for Glob-Patterns.
|
|||
|
# Only character matching is active.
|
|||
|
|
|||
|
|
|||
|
# All-Elements-Of references
|
|||
|
echo $@ # Same as above
|
|||
|
echo ${@} # Same as above
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# Within double-quotes, the behavior of Glob-Pattern references
|
|||
|
#+ depends on the setting of IFS (Input Field Separator).
|
|||
|
# Within double-quotes, All-Elements-Of references behave the same.
|
|||
|
|
|||
|
|
|||
|
# Specifying only the name of a variable holding a string refers
|
|||
|
#+ to all elements (characters) of a string.
|
|||
|
|
|||
|
|
|||
|
# To specify an element (character) of a string,
|
|||
|
#+ the Extended-Syntax reference notation (see below) MAY be used.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# Specifying only the name of a Bash array references
|
|||
|
#+ the subscript zero element,
|
|||
|
#+ NOT the FIRST DEFINED nor the FIRST WITH CONTENTS element.
|
|||
|
|
|||
|
# Additional qualification is needed to reference other elements,
|
|||
|
#+ which means that the reference MUST be written in Extended-Syntax.
|
|||
|
# The general form is: ${name[subscript]}.
|
|||
|
|
|||
|
# The string forms may also be used: ${name:subscript}
|
|||
|
#+ for Bash-Arrays when referencing the subscript zero element.
|
|||
|
|
|||
|
|
|||
|
# Bash-Arrays are implemented internally as linked lists,
|
|||
|
#+ not as a fixed area of storage as in some programming languages.
|
|||
|
|
|||
|
|
|||
|
# Characteristics of Bash arrays (Bash-Arrays):
|
|||
|
# --------------------------------------------
|
|||
|
|
|||
|
# If not otherwise specified, Bash-Array subscripts begin with
|
|||
|
#+ subscript number zero. Literally: [0]
|
|||
|
# This is called zero-based indexing.
|
|||
|
###
|
|||
|
# If not otherwise specified, Bash-Arrays are subscript packed
|
|||
|
#+ (sequential subscripts without subscript gaps).
|
|||
|
###
|
|||
|
# Negative subscripts are not allowed.
|
|||
|
###
|
|||
|
# Elements of a Bash-Array need not all be of the same type.
|
|||
|
###
|
|||
|
# Elements of a Bash-Array may be undefined (null reference).
|
|||
|
# That is, a Bash-Array may be "subscript sparse."
|
|||
|
###
|
|||
|
# Elements of a Bash-Array may be defined and empty (null contents).
|
|||
|
###
|
|||
|
# Elements of a Bash-Array may contain:
|
|||
|
# * A whole number as a signed 32-bit (or larger) integer
|
|||
|
# * A string
|
|||
|
# * A string formated so that it appears to be a function name
|
|||
|
# + with optional arguments
|
|||
|
###
|
|||
|
# Defined elements of a Bash-Array may be undefined (unset).
|
|||
|
# That is, a subscript packed Bash-Array may be changed
|
|||
|
# + into a subscript sparse Bash-Array.
|
|||
|
###
|
|||
|
# Elements may be added to a Bash-Array by defining an element
|
|||
|
#+ not previously defined.
|
|||
|
###
|
|||
|
# For these reasons, I have been calling them "Bash-Arrays".
|
|||
|
# I'll return to the generic term "array" from now on.
|
|||
|
# -- msz
|
|||
|
|
|||
|
|
|||
|
echo "========================================================="
|
|||
|
|
|||
|
# Lines 202 - 334 supplied by Cliff Bamford. (Thanks!)
|
|||
|
# Demo --- Interaction with Arrays, quoting, IFS, echo, * and @ ---
|
|||
|
#+ all affect how things work
|
|||
|
|
|||
|
ArrayVar[0]='zero' # 0 normal
|
|||
|
ArrayVar[1]=one # 1 unquoted literal
|
|||
|
ArrayVar[2]='two' # 2 normal
|
|||
|
ArrayVar[3]='three' # 3 normal
|
|||
|
ArrayVar[4]='I am four' # 4 normal with spaces
|
|||
|
ArrayVar[5]='five' # 5 normal
|
|||
|
unset ArrayVar[6] # 6 undefined
|
|||
|
ArrayValue[7]='seven' # 7 normal
|
|||
|
ArrayValue[8]='' # 8 defined but empty
|
|||
|
ArrayValue[9]='nine' # 9 normal
|
|||
|
|
|||
|
|
|||
|
echo '--- Here is the array we are using for this test'
|
|||
|
echo
|
|||
|
echo "ArrayVar[0]='zero' # 0 normal"
|
|||
|
echo "ArrayVar[1]=one # 1 unquoted literal"
|
|||
|
echo "ArrayVar[2]='two' # 2 normal"
|
|||
|
echo "ArrayVar[3]='three' # 3 normal"
|
|||
|
echo "ArrayVar[4]='I am four' # 4 normal with spaces"
|
|||
|
echo "ArrayVar[5]='five' # 5 normal"
|
|||
|
echo "unset ArrayVar[6] # 6 undefined"
|
|||
|
echo "ArrayValue[7]='seven' # 7 normal"
|
|||
|
echo "ArrayValue[8]='' # 8 defined but empty"
|
|||
|
echo "ArrayValue[9]='nine' # 9 normal"
|
|||
|
echo
|
|||
|
|
|||
|
|
|||
|
echo
|
|||
|
echo '---Case0: No double-quotes, Default IFS of space,tab,newline ---'
|
|||
|
IFS=$'\x20'$'\x09'$'\x0A' # In exactly this order.
|
|||
|
echo 'Here is: printf %q {${ArrayVar[*]}'
|
|||
|
printf %q ${ArrayVar[*]}
|
|||
|
echo
|
|||
|
echo 'Here is: printf %q {${ArrayVar[@]}'
|
|||
|
printf %q ${ArrayVar[@]}
|
|||
|
echo
|
|||
|
echo 'Here is: echo ${ArrayVar[*]}'
|
|||
|
echo ${ArrayVar[@]}
|
|||
|
echo 'Here is: echo {${ArrayVar[@]}'
|
|||
|
echo ${ArrayVar[@]}
|
|||
|
|
|||
|
echo
|
|||
|
echo '---Case1: Within double-quotes - Default IFS of space-tab-
|
|||
|
newline ---'
|
|||
|
IFS=$'\x20'$'\x09'$'\x0A' # These three bytes,
|
|||
|
echo 'Here is: printf %q "{${ArrayVar[*]}"'
|
|||
|
printf %q "${ArrayVar[*]}"
|
|||
|
echo
|
|||
|
echo 'Here is: printf %q "{${ArrayVar[@]}"'
|
|||
|
printf %q "${ArrayVar[@]}"
|
|||
|
echo
|
|||
|
echo 'Here is: echo "${ArrayVar[*]}"'
|
|||
|
echo "${ArrayVar[@]}"
|
|||
|
echo 'Here is: echo "{${ArrayVar[@]}"'
|
|||
|
echo "${ArrayVar[@]}"
|
|||
|
|
|||
|
echo
|
|||
|
echo '---Case2: Within double-quotes - IFS is q'
|
|||
|
IFS='q'
|
|||
|
echo 'Here is: printf %q "{${ArrayVar[*]}"'
|
|||
|
printf %q "${ArrayVar[*]}"
|
|||
|
echo
|
|||
|
echo 'Here is: printf %q "{${ArrayVar[@]}"'
|
|||
|
printf %q "${ArrayVar[@]}"
|
|||
|
echo
|
|||
|
echo 'Here is: echo "${ArrayVar[*]}"'
|
|||
|
echo "${ArrayVar[@]}"
|
|||
|
echo 'Here is: echo "{${ArrayVar[@]}"'
|
|||
|
echo "${ArrayVar[@]}"
|
|||
|
|
|||
|
echo
|
|||
|
echo '---Case3: Within double-quotes - IFS is ^'
|
|||
|
IFS='^'
|
|||
|
echo 'Here is: printf %q "{${ArrayVar[*]}"'
|
|||
|
printf %q "${ArrayVar[*]}"
|
|||
|
echo
|
|||
|
echo 'Here is: printf %q "{${ArrayVar[@]}"'
|
|||
|
printf %q "${ArrayVar[@]}"
|
|||
|
echo
|
|||
|
echo 'Here is: echo "${ArrayVar[*]}"'
|
|||
|
echo "${ArrayVar[@]}"
|
|||
|
echo 'Here is: echo "{${ArrayVar[@]}"'
|
|||
|
echo "${ArrayVar[@]}"
|
|||
|
|
|||
|
echo
|
|||
|
echo '---Case4: Within double-quotes - IFS is ^ followed by
|
|||
|
space,tab,newline'
|
|||
|
IFS=$'^'$'\x20'$'\x09'$'\x0A' # ^ + space tab newline
|
|||
|
echo 'Here is: printf %q "{${ArrayVar[*]}"'
|
|||
|
printf %q "${ArrayVar[*]}"
|
|||
|
echo
|
|||
|
echo 'Here is: printf %q "{${ArrayVar[@]}"'
|
|||
|
printf %q "${ArrayVar[@]}"
|
|||
|
echo
|
|||
|
echo 'Here is: echo "${ArrayVar[*]}"'
|
|||
|
echo "${ArrayVar[@]}"
|
|||
|
echo 'Here is: echo "{${ArrayVar[@]}"'
|
|||
|
echo "${ArrayVar[@]}"
|
|||
|
|
|||
|
echo
|
|||
|
echo '---Case6: Within double-quotes - IFS set and empty '
|
|||
|
IFS=''
|
|||
|
echo 'Here is: printf %q "{${ArrayVar[*]}"'
|
|||
|
printf %q "${ArrayVar[*]}"
|
|||
|
echo
|
|||
|
echo 'Here is: printf %q "{${ArrayVar[@]}"'
|
|||
|
printf %q "${ArrayVar[@]}"
|
|||
|
echo
|
|||
|
echo 'Here is: echo "${ArrayVar[*]}"'
|
|||
|
echo "${ArrayVar[@]}"
|
|||
|
echo 'Here is: echo "{${ArrayVar[@]}"'
|
|||
|
echo "${ArrayVar[@]}"
|
|||
|
|
|||
|
echo
|
|||
|
echo '---Case7: Within double-quotes - IFS is unset'
|
|||
|
unset IFS
|
|||
|
echo 'Here is: printf %q "{${ArrayVar[*]}"'
|
|||
|
printf %q "${ArrayVar[*]}"
|
|||
|
echo
|
|||
|
echo 'Here is: printf %q "{${ArrayVar[@]}"'
|
|||
|
printf %q "${ArrayVar[@]}"
|
|||
|
echo
|
|||
|
echo 'Here is: echo "${ArrayVar[*]}"'
|
|||
|
echo "${ArrayVar[@]}"
|
|||
|
echo 'Here is: echo "{${ArrayVar[@]}"'
|
|||
|
echo "${ArrayVar[@]}"
|
|||
|
|
|||
|
echo
|
|||
|
echo '---End of Cases---'
|
|||
|
echo "========================================================="; echo
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# Put IFS back to the default.
|
|||
|
# Default is exactly these three bytes.
|
|||
|
IFS=$'\x20'$'\x09'$'\x0A' # In exactly this order.
|
|||
|
|
|||
|
# Interpretation of the above outputs:
|
|||
|
# A Glob-Pattern is I/O; the setting of IFS matters.
|
|||
|
###
|
|||
|
# An All-Elements-Of does not consider IFS settings.
|
|||
|
###
|
|||
|
# Note the different output using the echo command and the
|
|||
|
#+ quoted format operator of the printf command.
|
|||
|
|
|||
|
|
|||
|
# Recall:
|
|||
|
# Parameters are similar to arrays and have the similar behaviors.
|
|||
|
###
|
|||
|
# The above examples demonstrate the possible variations.
|
|||
|
# To retain the shape of a sparse array, additional script
|
|||
|
#+ programming is required.
|
|||
|
###
|
|||
|
# The source code of Bash has a routine to output the
|
|||
|
#+ [subscript]=value array assignment format.
|
|||
|
# As of version 2.05b, that routine is not used,
|
|||
|
#+ but that might change in future releases.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# The length of a string, measured in non-null elements (characters):
|
|||
|
echo
|
|||
|
echo '- - Non-quoted references - -'
|
|||
|
echo 'Non-Null character count: '${#VarSomething}' characters.'
|
|||
|
|
|||
|
# test='Lit'$'\x00''eral' # $'\x00' is a null character.
|
|||
|
# echo ${#test} # See that?
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# The length of an array, measured in defined elements,
|
|||
|
#+ including null content elements.
|
|||
|
echo
|
|||
|
echo 'Defined content count: '${#ArrayVar[@]}' elements.'
|
|||
|
# That is NOT the maximum subscript (4).
|
|||
|
# That is NOT the range of the subscripts (1 . . 4 inclusive).
|
|||
|
# It IS the length of the linked list.
|
|||
|
###
|
|||
|
# Both the maximum subscript and the range of the subscripts may
|
|||
|
#+ be found with additional script programming.
|
|||
|
|
|||
|
# The length of a string, measured in non-null elements (characters):
|
|||
|
echo
|
|||
|
echo '- - Quoted, Glob-Pattern references - -'
|
|||
|
echo 'Non-Null character count: '"${#VarSomething}"' characters.'
|
|||
|
|
|||
|
# The length of an array, measured in defined elements,
|
|||
|
#+ including null-content elements.
|
|||
|
echo
|
|||
|
echo 'Defined element count: '"${#ArrayVar[*]}"' elements.'
|
|||
|
|
|||
|
# Interpretation: Substitution does not effect the ${# ... } operation.
|
|||
|
# Suggestion:
|
|||
|
# Always use the All-Elements-Of character
|
|||
|
#+ if that is what is intended (independence from IFS).
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# Define a simple function.
|
|||
|
# I include an underscore in the name
|
|||
|
#+ to make it distinctive in the examples below.
|
|||
|
###
|
|||
|
# Bash separates variable names and function names
|
|||
|
#+ in different namespaces.
|
|||
|
# The Mark-One eyeball isn't that advanced.
|
|||
|
###
|
|||
|
_simple() {
|
|||
|
echo -n 'SimpleFunc'$@ # Newlines are swallowed in
|
|||
|
} #+ result returned in any case.
|
|||
|
|
|||
|
|
|||
|
# The ( ... ) notation invokes a command or function.
|
|||
|
# The $( ... ) notation is pronounced: Result-Of.
|
|||
|
|
|||
|
|
|||
|
# Invoke the function _simple
|
|||
|
echo
|
|||
|
echo '- - Output of function _simple - -'
|
|||
|
_simple # Try passing arguments.
|
|||
|
echo
|
|||
|
# or
|
|||
|
(_simple) # Try passing arguments.
|
|||
|
echo
|
|||
|
|
|||
|
echo '- Is there a variable of that name? -'
|
|||
|
echo $_simple not defined # No variable by that name.
|
|||
|
|
|||
|
# Invoke the result of function _simple (Error msg intended)
|
|||
|
|
|||
|
###
|
|||
|
$(_simple) # Gives an error message:
|
|||
|
# line 436: SimpleFunc: command not found
|
|||
|
# ---------------------------------------
|
|||
|
|
|||
|
echo
|
|||
|
###
|
|||
|
|
|||
|
# The first word of the result of function _simple
|
|||
|
#+ is neither a valid Bash command nor the name of a defined function.
|
|||
|
###
|
|||
|
# This demonstrates that the output of _simple is subject to evaluation.
|
|||
|
###
|
|||
|
# Interpretation:
|
|||
|
# A function can be used to generate in-line Bash commands.
|
|||
|
|
|||
|
|
|||
|
# A simple function where the first word of result IS a bash command:
|
|||
|
###
|
|||
|
_print() {
|
|||
|
echo -n 'printf %q '$@
|
|||
|
}
|
|||
|
|
|||
|
echo '- - Outputs of function _print - -'
|
|||
|
_print parm1 parm2 # An Output NOT A Command.
|
|||
|
echo
|
|||
|
|
|||
|
$(_print parm1 parm2) # Executes: printf %q parm1 parm2
|
|||
|
# See above IFS examples for the
|
|||
|
#+ various possibilities.
|
|||
|
echo
|
|||
|
|
|||
|
$(_print $VarSomething) # The predictable result.
|
|||
|
echo
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# Function variables
|
|||
|
# ------------------
|
|||
|
|
|||
|
echo
|
|||
|
echo '- - Function variables - -'
|
|||
|
# A variable may represent a signed integer, a string or an array.
|
|||
|
# A string may be used like a function name with optional arguments.
|
|||
|
|
|||
|
# set -vx # Enable if desired
|
|||
|
declare -f funcVar #+ in namespace of functions
|
|||
|
|
|||
|
funcVar=_print # Contains name of function.
|
|||
|
$funcVar parm1 # Same as _print at this point.
|
|||
|
echo
|
|||
|
|
|||
|
funcVar=$(_print ) # Contains result of function.
|
|||
|
$funcVar # No input, No output.
|
|||
|
$funcVar $VarSomething # The predictable result.
|
|||
|
echo
|
|||
|
|
|||
|
funcVar=$(_print $VarSomething) # $VarSomething replaced HERE.
|
|||
|
$funcVar # The expansion is part of the
|
|||
|
echo #+ variable contents.
|
|||
|
|
|||
|
funcVar="$(_print $VarSomething)" # $VarSomething replaced HERE.
|
|||
|
$funcVar # The expansion is part of the
|
|||
|
echo #+ variable contents.
|
|||
|
|
|||
|
# The difference between the unquoted and the double-quoted versions
|
|||
|
#+ above can be seen in the "protect_literal.sh" example.
|
|||
|
# The first case above is processed as two, unquoted, Bash-Words.
|
|||
|
# The second case above is processed as one, quoted, Bash-Word.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# Delayed replacement
|
|||
|
# -------------------
|
|||
|
|
|||
|
echo
|
|||
|
echo '- - Delayed replacement - -'
|
|||
|
funcVar="$(_print '$VarSomething')" # No replacement, single Bash-Word.
|
|||
|
eval $funcVar # $VarSomething replaced HERE.
|
|||
|
echo
|
|||
|
|
|||
|
VarSomething='NewThing'
|
|||
|
eval $funcVar # $VarSomething replaced HERE.
|
|||
|
echo
|
|||
|
|
|||
|
# Restore the original setting trashed above.
|
|||
|
VarSomething=Literal
|
|||
|
|
|||
|
# There are a pair of functions demonstrated in the
|
|||
|
#+ "protect_literal.sh" and "unprotect_literal.sh" examples.
|
|||
|
# These are general purpose functions for delayed replacement literals
|
|||
|
#+ containing variables.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# REVIEW:
|
|||
|
# ------
|
|||
|
|
|||
|
# A string can be considered a Classic-Array of elements (characters).
|
|||
|
# A string operation applies to all elements (characters) of the string
|
|||
|
#+ (in concept, anyway).
|
|||
|
###
|
|||
|
# The notation: ${array_name[@]} represents all elements of the
|
|||
|
#+ Bash-Array: array_name.
|
|||
|
###
|
|||
|
# The Extended-Syntax string operations can be applied to all
|
|||
|
#+ elements of an array.
|
|||
|
###
|
|||
|
# This may be thought of as a For-Each operation on a vector of strings.
|
|||
|
###
|
|||
|
# Parameters are similar to an array.
|
|||
|
# The initialization of a parameter array for a script
|
|||
|
#+ and a parameter array for a function only differ
|
|||
|
#+ in the initialization of ${0}, which never changes its setting.
|
|||
|
###
|
|||
|
# Subscript zero of the script's parameter array contains
|
|||
|
#+ the name of the script.
|
|||
|
###
|
|||
|
# Subscript zero of a function's parameter array DOES NOT contain
|
|||
|
#+ the name of the function.
|
|||
|
# The name of the current function is accessed by the $FUNCNAME variable.
|
|||
|
###
|
|||
|
# A quick, review list follows (quick, not short).
|
|||
|
|
|||
|
echo
|
|||
|
echo '- - Test (but not change) - -'
|
|||
|
echo '- null reference -'
|
|||
|
echo -n ${VarNull-'NotSet'}' ' # NotSet
|
|||
|
echo ${VarNull} # NewLine only
|
|||
|
echo -n ${VarNull:-'NotSet'}' ' # NotSet
|
|||
|
echo ${VarNull} # Newline only
|
|||
|
|
|||
|
echo '- null contents -'
|
|||
|
echo -n ${VarEmpty-'Empty'}' ' # Only the space
|
|||
|
echo ${VarEmpty} # Newline only
|
|||
|
echo -n ${VarEmpty:-'Empty'}' ' # Empty
|
|||
|
echo ${VarEmpty} # Newline only
|
|||
|
|
|||
|
echo '- contents -'
|
|||
|
echo ${VarSomething-'Content'} # Literal
|
|||
|
echo ${VarSomething:-'Content'} # Literal
|
|||
|
|
|||
|
echo '- Sparse Array -'
|
|||
|
echo ${ArrayVar[@]-'not set'}
|
|||
|
|
|||
|
# ASCII-Art time
|
|||
|
# State Y==yes, N==no
|
|||
|
# - :-
|
|||
|
# Unset Y Y ${# ... } == 0
|
|||
|
# Empty N Y ${# ... } == 0
|
|||
|
# Contents N N ${# ... } > 0
|
|||
|
|
|||
|
# Either the first and/or the second part of the tests
|
|||
|
#+ may be a command or a function invocation string.
|
|||
|
echo
|
|||
|
echo '- - Test 1 for undefined - -'
|
|||
|
declare -i t
|
|||
|
_decT() {
|
|||
|
t=$t-1
|
|||
|
}
|
|||
|
|
|||
|
# Null reference, set: t == -1
|
|||
|
t=${#VarNull} # Results in zero.
|
|||
|
${VarNull- _decT } # Function executes, t now -1.
|
|||
|
echo $t
|
|||
|
|
|||
|
# Null contents, set: t == 0
|
|||
|
t=${#VarEmpty} # Results in zero.
|
|||
|
${VarEmpty- _decT } # _decT function NOT executed.
|
|||
|
echo $t
|
|||
|
|
|||
|
# Contents, set: t == number of non-null characters
|
|||
|
VarSomething='_simple' # Set to valid function name.
|
|||
|
t=${#VarSomething} # non-zero length
|
|||
|
${VarSomething- _decT } # Function _simple executed.
|
|||
|
echo $t # Note the Append-To action.
|
|||
|
|
|||
|
# Exercise: clean up that example.
|
|||
|
unset t
|
|||
|
unset _decT
|
|||
|
VarSomething=Literal
|
|||
|
|
|||
|
echo
|
|||
|
echo '- - Test and Change - -'
|
|||
|
echo '- Assignment if null reference -'
|
|||
|
echo -n ${VarNull='NotSet'}' ' # NotSet NotSet
|
|||
|
echo ${VarNull}
|
|||
|
unset VarNull
|
|||
|
|
|||
|
echo '- Assignment if null reference -'
|
|||
|
echo -n ${VarNull:='NotSet'}' ' # NotSet NotSet
|
|||
|
echo ${VarNull}
|
|||
|
unset VarNull
|
|||
|
|
|||
|
echo '- No assignment if null contents -'
|
|||
|
echo -n ${VarEmpty='Empty'}' ' # Space only
|
|||
|
echo ${VarEmpty}
|
|||
|
VarEmpty=''
|
|||
|
|
|||
|
echo '- Assignment if null contents -'
|
|||
|
echo -n ${VarEmpty:='Empty'}' ' # Empty Empty
|
|||
|
echo ${VarEmpty}
|
|||
|
VarEmpty=''
|
|||
|
|
|||
|
echo '- No change if already has contents -'
|
|||
|
echo ${VarSomething='Content'} # Literal
|
|||
|
echo ${VarSomething:='Content'} # Literal
|
|||
|
|
|||
|
|
|||
|
# "Subscript sparse" Bash-Arrays
|
|||
|
###
|
|||
|
# Bash-Arrays are subscript packed, beginning with
|
|||
|
#+ subscript zero unless otherwise specified.
|
|||
|
###
|
|||
|
# The initialization of ArrayVar was one way
|
|||
|
#+ to "otherwise specify". Here is the other way:
|
|||
|
###
|
|||
|
echo
|
|||
|
declare -a ArraySparse
|
|||
|
ArraySparse=( [1]=one [2]='' [4]='four' )
|
|||
|
# [0]=null reference, [2]=null content, [3]=null reference
|
|||
|
|
|||
|
echo '- - Array-Sparse List - -'
|
|||
|
# Within double-quotes, default IFS, Glob-Pattern
|
|||
|
|
|||
|
IFS=$'\x20'$'\x09'$'\x0A'
|
|||
|
printf %q "${ArraySparse[*]}"
|
|||
|
echo
|
|||
|
|
|||
|
# Note that the output does not distinguish between "null content"
|
|||
|
#+ and "null reference".
|
|||
|
# Both print as escaped whitespace.
|
|||
|
###
|
|||
|
# Note also that the output does NOT contain escaped whitespace
|
|||
|
#+ for the "null reference(s)" prior to the first defined element.
|
|||
|
###
|
|||
|
# This behavior of 2.04, 2.05a and 2.05b has been reported
|
|||
|
#+ and may change in a future version of Bash.
|
|||
|
|
|||
|
# To output a sparse array and maintain the [subscript]=value
|
|||
|
#+ relationship without change requires a bit of programming.
|
|||
|
# One possible code fragment:
|
|||
|
###
|
|||
|
# local l=${#ArraySparse[@]} # Count of defined elements
|
|||
|
# local f=0 # Count of found subscripts
|
|||
|
# local i=0 # Subscript to test
|
|||
|
( # Anonymous in-line function
|
|||
|
for (( l=${#ArraySparse[@]}, f = 0, i = 0 ; f < l ; i++ ))
|
|||
|
do
|
|||
|
# 'if defined then...'
|
|||
|
${ArraySparse[$i]+ eval echo '\ ['$i']='${ArraySparse[$i]} ; (( f++ )) }
|
|||
|
done
|
|||
|
)
|
|||
|
|
|||
|
# The reader coming upon the above code fragment cold
|
|||
|
#+ might want to review "command lists" and "multiple commands on a line"
|
|||
|
#+ in the text of the foregoing "Advanced Bash Scripting Guide."
|
|||
|
###
|
|||
|
# Note:
|
|||
|
# The "read -a array_name" version of the "read" command
|
|||
|
#+ begins filling array_name at subscript zero.
|
|||
|
# ArraySparse does not define a value at subscript zero.
|
|||
|
###
|
|||
|
# The user needing to read/write a sparse array to either
|
|||
|
#+ external storage or a communications socket must invent
|
|||
|
#+ a read/write code pair suitable for their purpose.
|
|||
|
###
|
|||
|
# Exercise: clean it up.
|
|||
|
|
|||
|
unset ArraySparse
|
|||
|
|
|||
|
echo
|
|||
|
echo '- - Conditional alternate (But not change)- -'
|
|||
|
echo '- No alternate if null reference -'
|
|||
|
echo -n ${VarNull+'NotSet'}' '
|
|||
|
echo ${VarNull}
|
|||
|
unset VarNull
|
|||
|
|
|||
|
echo '- No alternate if null reference -'
|
|||
|
echo -n ${VarNull:+'NotSet'}' '
|
|||
|
echo ${VarNull}
|
|||
|
unset VarNull
|
|||
|
|
|||
|
echo '- Alternate if null contents -'
|
|||
|
echo -n ${VarEmpty+'Empty'}' ' # Empty
|
|||
|
echo ${VarEmpty}
|
|||
|
VarEmpty=''
|
|||
|
|
|||
|
echo '- No alternate if null contents -'
|
|||
|
echo -n ${VarEmpty:+'Empty'}' ' # Space only
|
|||
|
echo ${VarEmpty}
|
|||
|
VarEmpty=''
|
|||
|
|
|||
|
echo '- Alternate if already has contents -'
|
|||
|
|
|||
|
# Alternate literal
|
|||
|
echo -n ${VarSomething+'Content'}' ' # Content Literal
|
|||
|
echo ${VarSomething}
|
|||
|
|
|||
|
# Invoke function
|
|||
|
echo -n ${VarSomething:+ $(_simple) }' ' # SimpleFunc Literal
|
|||
|
echo ${VarSomething}
|
|||
|
echo
|
|||
|
|
|||
|
echo '- - Sparse Array - -'
|
|||
|
echo ${ArrayVar[@]+'Empty'} # An array of 'Empty'(ies)
|
|||
|
echo
|
|||
|
|
|||
|
echo '- - Test 2 for undefined - -'
|
|||
|
|
|||
|
declare -i t
|
|||
|
_incT() {
|
|||
|
t=$t+1
|
|||
|
}
|
|||
|
|
|||
|
# Note:
|
|||
|
# This is the same test used in the sparse array
|
|||
|
#+ listing code fragment.
|
|||
|
|
|||
|
# Null reference, set: t == -1
|
|||
|
t=${#VarNull}-1 # Results in minus-one.
|
|||
|
${VarNull+ _incT } # Does not execute.
|
|||
|
echo $t' Null reference'
|
|||
|
|
|||
|
# Null contents, set: t == 0
|
|||
|
t=${#VarEmpty}-1 # Results in minus-one.
|
|||
|
${VarEmpty+ _incT } # Executes.
|
|||
|
echo $t' Null content'
|
|||
|
|
|||
|
# Contents, set: t == (number of non-null characters)
|
|||
|
t=${#VarSomething}-1 # non-null length minus-one
|
|||
|
${VarSomething+ _incT } # Executes.
|
|||
|
echo $t' Contents'
|
|||
|
|
|||
|
# Exercise: clean up that example.
|
|||
|
unset t
|
|||
|
unset _incT
|
|||
|
|
|||
|
# ${name?err_msg} ${name:?err_msg}
|
|||
|
# These follow the same rules but always exit afterwards
|
|||
|
#+ if an action is specified following the question mark.
|
|||
|
# The action following the question mark may be a literal
|
|||
|
#+ or a function result.
|
|||
|
###
|
|||
|
# ${name?} ${name:?} are test-only, the return can be tested.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# Element operations
|
|||
|
# ------------------
|
|||
|
|
|||
|
echo
|
|||
|
echo '- - Trailing sub-element selection - -'
|
|||
|
|
|||
|
# Strings, Arrays and Positional parameters
|
|||
|
|
|||
|
# Call this script with multiple arguments
|
|||
|
#+ to see the parameter selections.
|
|||
|
|
|||
|
echo '- All -'
|
|||
|
echo ${VarSomething:0} # all non-null characters
|
|||
|
echo ${ArrayVar[@]:0} # all elements with content
|
|||
|
echo ${@:0} # all parameters with content;
|
|||
|
# ignoring parameter[0]
|
|||
|
|
|||
|
echo
|
|||
|
echo '- All after -'
|
|||
|
echo ${VarSomething:1} # all non-null after character[0]
|
|||
|
echo ${ArrayVar[@]:1} # all after element[0] with content
|
|||
|
echo ${@:2} # all after param[1] with content
|
|||
|
|
|||
|
echo
|
|||
|
echo '- Range after -'
|
|||
|
echo ${VarSomething:4:3} # ral
|
|||
|
# Three characters after
|
|||
|
# character[3]
|
|||
|
|
|||
|
echo '- Sparse array gotch -'
|
|||
|
echo ${ArrayVar[@]:1:2} # four - The only element with content.
|
|||
|
# Two elements after (if that many exist).
|
|||
|
# the FIRST WITH CONTENTS
|
|||
|
#+ (the FIRST WITH CONTENTS is being
|
|||
|
#+ considered as if it
|
|||
|
#+ were subscript zero).
|
|||
|
# Executed as if Bash considers ONLY array elements with CONTENT
|
|||
|
# printf %q "${ArrayVar[@]:0:3}" # Try this one
|
|||
|
|
|||
|
# In versions 2.04, 2.05a and 2.05b,
|
|||
|
#+ Bash does not handle sparse arrays as expected using this notation.
|
|||
|
#
|
|||
|
# The current Bash maintainer, Chet Ramey, has corrected this.
|
|||
|
|
|||
|
|
|||
|
echo '- Non-sparse array -'
|
|||
|
echo ${@:2:2} # Two parameters following parameter[1]
|
|||
|
|
|||
|
# New victims for string vector examples:
|
|||
|
stringZ=abcABC123ABCabc
|
|||
|
arrayZ=( abcabc ABCABC 123123 ABCABC abcabc )
|
|||
|
sparseZ=( [1]='abcabc' [3]='ABCABC' [4]='' [5]='123123' )
|
|||
|
|
|||
|
echo
|
|||
|
echo ' - - Victim string - -'$stringZ'- - '
|
|||
|
echo ' - - Victim array - -'${arrayZ[@]}'- - '
|
|||
|
echo ' - - Sparse array - -'${sparseZ[@]}'- - '
|
|||
|
echo ' - [0]==null ref, [2]==null ref, [4]==null content - '
|
|||
|
echo ' - [1]=abcabc [3]=ABCABC [5]=123123 - '
|
|||
|
echo ' - non-null-reference count: '${#sparseZ[@]}' elements'
|
|||
|
|
|||
|
echo
|
|||
|
echo '- - Prefix sub-element removal - -'
|
|||
|
echo '- - Glob-Pattern match must include the first character. - -'
|
|||
|
echo '- - Glob-Pattern may be a literal or a function result. - -'
|
|||
|
echo
|
|||
|
|
|||
|
|
|||
|
# Function returning a simple, Literal, Glob-Pattern
|
|||
|
_abc() {
|
|||
|
echo -n 'abc'
|
|||
|
}
|
|||
|
|
|||
|
echo '- Shortest prefix -'
|
|||
|
echo ${stringZ#123} # Unchanged (not a prefix).
|
|||
|
echo ${stringZ#$(_abc)} # ABC123ABCabc
|
|||
|
echo ${arrayZ[@]#abc} # Applied to each element.
|
|||
|
|
|||
|
# echo ${sparseZ[@]#abc} # Version-2.05b core dumps.
|
|||
|
# Has since been fixed by Chet Ramey.
|
|||
|
|
|||
|
# The -it would be nice- First-Subscript-Of
|
|||
|
# echo ${#sparseZ[@]#*} # This is NOT valid Bash.
|
|||
|
|
|||
|
echo
|
|||
|
echo '- Longest prefix -'
|
|||
|
echo ${stringZ##1*3} # Unchanged (not a prefix)
|
|||
|
echo ${stringZ##a*C} # abc
|
|||
|
echo ${arrayZ[@]##a*c} # ABCABC 123123 ABCABC
|
|||
|
|
|||
|
# echo ${sparseZ[@]##a*c} # Version-2.05b core dumps.
|
|||
|
# Has since been fixed by Chet Ramey.
|
|||
|
|
|||
|
echo
|
|||
|
echo '- - Suffix sub-element removal - -'
|
|||
|
echo '- - Glob-Pattern match must include the last character. - -'
|
|||
|
echo '- - Glob-Pattern may be a literal or a function result. - -'
|
|||
|
echo
|
|||
|
echo '- Shortest suffix -'
|
|||
|
echo ${stringZ%1*3} # Unchanged (not a suffix).
|
|||
|
echo ${stringZ%$(_abc)} # abcABC123ABC
|
|||
|
echo ${arrayZ[@]%abc} # Applied to each element.
|
|||
|
|
|||
|
# echo ${sparseZ[@]%abc} # Version-2.05b core dumps.
|
|||
|
# Has since been fixed by Chet Ramey.
|
|||
|
|
|||
|
# The -it would be nice- Last-Subscript-Of
|
|||
|
# echo ${#sparseZ[@]%*} # This is NOT valid Bash.
|
|||
|
|
|||
|
echo
|
|||
|
echo '- Longest suffix -'
|
|||
|
echo ${stringZ%%1*3} # Unchanged (not a suffix)
|
|||
|
echo ${stringZ%%b*c} # a
|
|||
|
echo ${arrayZ[@]%%b*c} # a ABCABC 123123 ABCABC a
|
|||
|
|
|||
|
# echo ${sparseZ[@]%%b*c} # Version-2.05b core dumps.
|
|||
|
# Has since been fixed by Chet Ramey.
|
|||
|
|
|||
|
echo
|
|||
|
echo '- - Sub-element replacement - -'
|
|||
|
echo '- - Sub-element at any location in string. - -'
|
|||
|
echo '- - First specification is a Glob-Pattern - -'
|
|||
|
echo '- - Glob-Pattern may be a literal or Glob-Pattern function result. - -'
|
|||
|
echo '- - Second specification may be a literal or function result. - -'
|
|||
|
echo '- - Second specification may be unspecified. Pronounce that'
|
|||
|
echo ' as: Replace-With-Nothing (Delete) - -'
|
|||
|
echo
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# Function returning a simple, Literal, Glob-Pattern
|
|||
|
_123() {
|
|||
|
echo -n '123'
|
|||
|
}
|
|||
|
|
|||
|
echo '- Replace first occurrence -'
|
|||
|
echo ${stringZ/$(_123)/999} # Changed (123 is a component).
|
|||
|
echo ${stringZ/ABC/xyz} # xyzABC123ABCabc
|
|||
|
echo ${arrayZ[@]/ABC/xyz} # Applied to each element.
|
|||
|
echo ${sparseZ[@]/ABC/xyz} # Works as expected.
|
|||
|
|
|||
|
echo
|
|||
|
echo '- Delete first occurrence -'
|
|||
|
echo ${stringZ/$(_123)/}
|
|||
|
echo ${stringZ/ABC/}
|
|||
|
echo ${arrayZ[@]/ABC/}
|
|||
|
echo ${sparseZ[@]/ABC/}
|
|||
|
|
|||
|
# The replacement need not be a literal,
|
|||
|
#+ since the result of a function invocation is allowed.
|
|||
|
# This is general to all forms of replacement.
|
|||
|
echo
|
|||
|
echo '- Replace first occurrence with Result-Of -'
|
|||
|
echo ${stringZ/$(_123)/$(_simple)} # Works as expected.
|
|||
|
echo ${arrayZ[@]/ca/$(_simple)} # Applied to each element.
|
|||
|
echo ${sparseZ[@]/ca/$(_simple)} # Works as expected.
|
|||
|
|
|||
|
echo
|
|||
|
echo '- Replace all occurrences -'
|
|||
|
echo ${stringZ//[b2]/X} # X-out b's and 2's
|
|||
|
echo ${stringZ//abc/xyz} # xyzABC123ABCxyz
|
|||
|
echo ${arrayZ[@]//abc/xyz} # Applied to each element.
|
|||
|
echo ${sparseZ[@]//abc/xyz} # Works as expected.
|
|||
|
|
|||
|
echo
|
|||
|
echo '- Delete all occurrences -'
|
|||
|
echo ${stringZ//[b2]/}
|
|||
|
echo ${stringZ//abc/}
|
|||
|
echo ${arrayZ[@]//abc/}
|
|||
|
echo ${sparseZ[@]//abc/}
|
|||
|
|
|||
|
echo
|
|||
|
echo '- - Prefix sub-element replacement - -'
|
|||
|
echo '- - Match must include the first character. - -'
|
|||
|
echo
|
|||
|
|
|||
|
echo '- Replace prefix occurrences -'
|
|||
|
echo ${stringZ/#[b2]/X} # Unchanged (neither is a prefix).
|
|||
|
echo ${stringZ/#$(_abc)/XYZ} # XYZABC123ABCabc
|
|||
|
echo ${arrayZ[@]/#abc/XYZ} # Applied to each element.
|
|||
|
echo ${sparseZ[@]/#abc/XYZ} # Works as expected.
|
|||
|
|
|||
|
echo
|
|||
|
echo '- Delete prefix occurrences -'
|
|||
|
echo ${stringZ/#[b2]/}
|
|||
|
echo ${stringZ/#$(_abc)/}
|
|||
|
echo ${arrayZ[@]/#abc/}
|
|||
|
echo ${sparseZ[@]/#abc/}
|
|||
|
|
|||
|
echo
|
|||
|
echo '- - Suffix sub-element replacement - -'
|
|||
|
echo '- - Match must include the last character. - -'
|
|||
|
echo
|
|||
|
|
|||
|
echo '- Replace suffix occurrences -'
|
|||
|
echo ${stringZ/%[b2]/X} # Unchanged (neither is a suffix).
|
|||
|
echo ${stringZ/%$(_abc)/XYZ} # abcABC123ABCXYZ
|
|||
|
echo ${arrayZ[@]/%abc/XYZ} # Applied to each element.
|
|||
|
echo ${sparseZ[@]/%abc/XYZ} # Works as expected.
|
|||
|
|
|||
|
echo
|
|||
|
echo '- Delete suffix occurrences -'
|
|||
|
echo ${stringZ/%[b2]/}
|
|||
|
echo ${stringZ/%$(_abc)/}
|
|||
|
echo ${arrayZ[@]/%abc/}
|
|||
|
echo ${sparseZ[@]/%abc/}
|
|||
|
|
|||
|
echo
|
|||
|
echo '- - Special cases of null Glob-Pattern - -'
|
|||
|
echo
|
|||
|
|
|||
|
echo '- Prefix all -'
|
|||
|
# null substring pattern means 'prefix'
|
|||
|
echo ${stringZ/#/NEW} # NEWabcABC123ABCabc
|
|||
|
echo ${arrayZ[@]/#/NEW} # Applied to each element.
|
|||
|
echo ${sparseZ[@]/#/NEW} # Applied to null-content also.
|
|||
|
# That seems reasonable.
|
|||
|
|
|||
|
echo
|
|||
|
echo '- Suffix all -'
|
|||
|
# null substring pattern means 'suffix'
|
|||
|
echo ${stringZ/%/NEW} # abcABC123ABCabcNEW
|
|||
|
echo ${arrayZ[@]/%/NEW} # Applied to each element.
|
|||
|
echo ${sparseZ[@]/%/NEW} # Applied to null-content also.
|
|||
|
# That seems reasonable.
|
|||
|
|
|||
|
echo
|
|||
|
echo '- - Special case For-Each Glob-Pattern - -'
|
|||
|
echo '- - - - This is a nice-to-have dream - - - -'
|
|||
|
echo
|
|||
|
|
|||
|
_GenFunc() {
|
|||
|
echo -n ${0} # Illustration only.
|
|||
|
# Actually, that would be an arbitrary computation.
|
|||
|
}
|
|||
|
|
|||
|
# All occurrences, matching the AnyThing pattern.
|
|||
|
# Currently //*/ does not match null-content nor null-reference.
|
|||
|
# /#/ and /%/ does match null-content but not null-reference.
|
|||
|
echo ${sparseZ[@]//*/$(_GenFunc)}
|
|||
|
|
|||
|
|
|||
|
# A possible syntax would be to make
|
|||
|
#+ the parameter notation used within this construct mean:
|
|||
|
# ${1} - The full element
|
|||
|
# ${2} - The prefix, if any, to the matched sub-element
|
|||
|
# ${3} - The matched sub-element
|
|||
|
# ${4} - The suffix, if any, to the matched sub-element
|
|||
|
#
|
|||
|
# echo ${sparseZ[@]//*/$(_GenFunc ${3})} # Same as ${1} here.
|
|||
|
# Perhaps it will be implemented in a future version of Bash.
|
|||
|
|
|||
|
|
|||
|
exit 0</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="TESTEXECTIME"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-59. Testing execution times of various commands</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
# test-execution-time.sh
|
|||
|
# Example by Erik Brandsberg, for testing execution time
|
|||
|
#+ of certain operations.
|
|||
|
# Referenced in the "Optimizations" section of "Miscellany" chapter.
|
|||
|
|
|||
|
count=50000
|
|||
|
echo "Math tests"
|
|||
|
echo "Math via \$(( ))"
|
|||
|
time for (( i=0; i< $count; i++))
|
|||
|
do
|
|||
|
result=$(( $i%2 ))
|
|||
|
done
|
|||
|
|
|||
|
echo "Math via *expr*:"
|
|||
|
time for (( i=0; i< $count; i++))
|
|||
|
do
|
|||
|
result=`expr "$i%2"`
|
|||
|
done
|
|||
|
|
|||
|
echo "Math via *let*:"
|
|||
|
time for (( i=0; i< $count; i++))
|
|||
|
do
|
|||
|
let result=$i%2
|
|||
|
done
|
|||
|
|
|||
|
echo
|
|||
|
echo "Conditional testing tests"
|
|||
|
|
|||
|
echo "Test via case:"
|
|||
|
time for (( i=0; i< $count; i++))
|
|||
|
do
|
|||
|
case $(( $i%2 )) in
|
|||
|
0) : ;;
|
|||
|
1) : ;;
|
|||
|
esac
|
|||
|
done
|
|||
|
|
|||
|
echo "Test with if [], no quotes:"
|
|||
|
time for (( i=0; i< $count; i++))
|
|||
|
do
|
|||
|
if [ $(( $i%2 )) = 0 ]; then
|
|||
|
:
|
|||
|
else
|
|||
|
:
|
|||
|
fi
|
|||
|
done
|
|||
|
|
|||
|
echo "Test with if [], quotes:"
|
|||
|
time for (( i=0; i< $count; i++))
|
|||
|
do
|
|||
|
if [ "$(( $i%2 ))" = "0" ]; then
|
|||
|
:
|
|||
|
else
|
|||
|
:
|
|||
|
fi
|
|||
|
done
|
|||
|
|
|||
|
echo "Test with if [], using -eq:"
|
|||
|
time for (( i=0; i< $count; i++))
|
|||
|
do
|
|||
|
if [ $(( $i%2 )) -eq 0 ]; then
|
|||
|
:
|
|||
|
else
|
|||
|
:
|
|||
|
fi
|
|||
|
done
|
|||
|
|
|||
|
exit $?</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
><DIV
|
|||
|
CLASS="EXAMPLE"
|
|||
|
><A
|
|||
|
NAME="ASSOCARRTEST"
|
|||
|
></A
|
|||
|
><P
|
|||
|
><B
|
|||
|
>Example A-60. Associative arrays vs. conventional arrays (execution
|
|||
|
times)</B
|
|||
|
></P
|
|||
|
><TABLE
|
|||
|
BORDER="0"
|
|||
|
BGCOLOR="#E0E0E0"
|
|||
|
WIDTH="100%"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
><FONT
|
|||
|
COLOR="#000000"
|
|||
|
><PRE
|
|||
|
CLASS="PROGRAMLISTING"
|
|||
|
>#!/bin/bash
|
|||
|
# assoc-arr-test.sh
|
|||
|
# Benchmark test script to compare execution times of
|
|||
|
# numeric-indexed array vs. associative array.
|
|||
|
# Thank you, Erik Brandsberg.
|
|||
|
|
|||
|
count=100000 # May take a while for some of the tests below.
|
|||
|
declare simple # Can change to 20000, if desired.
|
|||
|
declare -a array1
|
|||
|
declare -A array2
|
|||
|
declare -a array3
|
|||
|
declare -A array4
|
|||
|
|
|||
|
echo "===Assignment tests==="
|
|||
|
echo
|
|||
|
|
|||
|
echo "Assigning a simple variable:"
|
|||
|
# References $i twice to equalize lookup times.
|
|||
|
time for (( i=0; i< $count; i++)); do
|
|||
|
simple=$i$i
|
|||
|
done
|
|||
|
|
|||
|
echo "---"
|
|||
|
|
|||
|
echo "Assigning a numeric index array entry:"
|
|||
|
time for (( i=0; i< $count; i++)); do
|
|||
|
array1[$i]=$i
|
|||
|
done
|
|||
|
|
|||
|
echo "---"
|
|||
|
|
|||
|
echo "Overwriting a numeric index array entry:"
|
|||
|
time for (( i=0; i< $count; i++)); do
|
|||
|
array1[$i]=$i
|
|||
|
done
|
|||
|
|
|||
|
echo "---"
|
|||
|
|
|||
|
echo "Linear reading of numeric index array:"
|
|||
|
time for (( i=0; i< $count; i++)); do
|
|||
|
simple=array1[$i]
|
|||
|
done
|
|||
|
|
|||
|
echo "---"
|
|||
|
|
|||
|
echo "Assigning an associative array entry:"
|
|||
|
time for (( i=0; i< $count; i++)); do
|
|||
|
array2[$i]=$i
|
|||
|
done
|
|||
|
|
|||
|
echo "---"
|
|||
|
|
|||
|
echo "Overwriting an associative array entry:"
|
|||
|
time for (( i=0; i< $count; i++)); do
|
|||
|
array2[$i]=$i
|
|||
|
done
|
|||
|
|
|||
|
echo "---"
|
|||
|
|
|||
|
echo "Linear reading an associative array entry:"
|
|||
|
time for (( i=0; i< $count; i++)); do
|
|||
|
simple=array2[$i]
|
|||
|
done
|
|||
|
|
|||
|
echo "---"
|
|||
|
|
|||
|
echo "Assigning a random number to a simple variable:"
|
|||
|
time for (( i=0; i< $count; i++)); do
|
|||
|
simple=$RANDOM
|
|||
|
done
|
|||
|
|
|||
|
echo "---"
|
|||
|
|
|||
|
echo "Assign a sparse numeric index array entry randomly into 64k cells:"
|
|||
|
time for (( i=0; i< $count; i++)); do
|
|||
|
array3[$RANDOM]=$i
|
|||
|
done
|
|||
|
|
|||
|
echo "---"
|
|||
|
|
|||
|
echo "Reading sparse numeric index array entry:"
|
|||
|
time for value in "${array3[@]}"i; do
|
|||
|
simple=$value
|
|||
|
done
|
|||
|
|
|||
|
echo "---"
|
|||
|
|
|||
|
echo "Assigning a sparse associative array entry randomly into 64k cells:"
|
|||
|
time for (( i=0; i< $count; i++)); do
|
|||
|
array4[$RANDOM]=$i
|
|||
|
done
|
|||
|
|
|||
|
echo "---"
|
|||
|
|
|||
|
echo "Reading sparse associative index array entry:"
|
|||
|
time for value in "${array4[@]}"; do
|
|||
|
simple=$value
|
|||
|
done
|
|||
|
|
|||
|
exit $?</PRE
|
|||
|
></FONT
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
></DIV
|
|||
|
><DIV
|
|||
|
CLASS="NAVFOOTER"
|
|||
|
><HR
|
|||
|
ALIGN="LEFT"
|
|||
|
WIDTH="100%"><TABLE
|
|||
|
SUMMARY="Footer navigation table"
|
|||
|
WIDTH="100%"
|
|||
|
BORDER="0"
|
|||
|
CELLPADDING="0"
|
|||
|
CELLSPACING="0"
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
WIDTH="33%"
|
|||
|
ALIGN="left"
|
|||
|
VALIGN="top"
|
|||
|
><A
|
|||
|
HREF="biblio.html"
|
|||
|
ACCESSKEY="P"
|
|||
|
>Prev</A
|
|||
|
></TD
|
|||
|
><TD
|
|||
|
WIDTH="34%"
|
|||
|
ALIGN="center"
|
|||
|
VALIGN="top"
|
|||
|
><A
|
|||
|
HREF="index.html"
|
|||
|
ACCESSKEY="H"
|
|||
|
>Home</A
|
|||
|
></TD
|
|||
|
><TD
|
|||
|
WIDTH="33%"
|
|||
|
ALIGN="right"
|
|||
|
VALIGN="top"
|
|||
|
><A
|
|||
|
HREF="refcards.html"
|
|||
|
ACCESSKEY="N"
|
|||
|
>Next</A
|
|||
|
></TD
|
|||
|
></TR
|
|||
|
><TR
|
|||
|
><TD
|
|||
|
WIDTH="33%"
|
|||
|
ALIGN="left"
|
|||
|
VALIGN="top"
|
|||
|
>Bibliography</TD
|
|||
|
><TD
|
|||
|
WIDTH="34%"
|
|||
|
ALIGN="center"
|
|||
|
VALIGN="top"
|
|||
|
> </TD
|
|||
|
><TD
|
|||
|
WIDTH="33%"
|
|||
|
ALIGN="right"
|
|||
|
VALIGN="top"
|
|||
|
>Reference Cards</TD
|
|||
|
></TR
|
|||
|
></TABLE
|
|||
|
></DIV
|
|||
|
></BODY
|
|||
|
></HTML
|
|||
|
>
|