330pics shell scripts_fifth

 

***@@@life.sh@@@!!!************************************************************************************
#!/bin/bash
# life.sh: "Life in the Slow Lane"
# Version 2: Patched by Daniel Albers
#+           to allow non-square grids as input.

# ##################################################################### #
# 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                                             #
#                       678                                             #
#                                                                       #
# 2) A living cell with either 2 or 3 living neighbors remains alive.   #
# 3) A dead cell with 3 living neighbors becomes alive (a "birth").     #
SURVIVE=2                                                               #
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
#+ "gen0" not present.

E_NOSTARTFILE=68

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 will cause very slow execution).
ROWS=10
COLS=10
#  Change above two variables to match grid size, if necessary.
#  ---------------------------------------------------------- #

GENERATIONS=10          #  How many generations to cycle through.
                        #  Adjust this upwards,
                        #+ if you have time on your hands.

NONE_ALIVE=80           #  Exit status on premature bailout,
                        #+ if no cells left alive.
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.

declare -a initial      # Arrays containing "cells".
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 and change underscores to spaces.
done 

return

}

IsValid ()                            # Test whether 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, 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          # Default.


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 of neighborhood.
    IsValid $t_cen $row                   # Valid cell position?
    if [ $? -eq "$TRUE" ]
    then
      if [ ${array[$t_cen]} = "$ALIVE1" ] # Is it alive?
      then                                # Yes?
        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" ]
      then
        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 cell alive?
  if [ $? -eq "$ALIVE" ]
  then                           #  If alive, then
    array[$i]=.                  #+ represent the cell as a period.
  else 
    array[$i]="_"                #  Otherwise underscore
   fi                            #+ (which 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' |/
sed -e 's//.//. /g' -e 's/_/_ /g'` )
# Delete lines containing '#' comment character.
# Remove linefeeds and insert space between elements.

clear          # Clear screen.

echo #         Title
echo "======================="
echo "    $GENERATIONS generations"
echo "           of"
echo "/"Life in the Slow Lane/""
echo "======================="


# -------- Display first generation. --------
Gen0=`echo ${initial[@]}`
display "$Gen0"           # Display only.
echo; echo
echo "Generation $generation  -  $alive alive"
# -------------------------------------------


let "generation += 1"     # Increment generation count.
echo

# ------- Display second generation. -------
Cur=`echo ${initial[@]}`
next_gen "$Cur"          # Update & display.
# ------------------------------------------

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"
done
# ==============================================================

echo

exit 0   # END

 

# 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.
%%%&&&life.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@line-number.sh@@@!!!************************************************************************************
#!/bin/bash
# line-number.sh

# 这个脚本将会echo自身两次, 并显示行号.
                                                             
# 'nl'命令显示的时候你将会看到, 本行是第4行, 因为它不计空行.
# 'cat -n'命令显示的时候你将会看到, 本行是第6行.

nl `basename $0`

echo; echo  # 下边, 让我们试试 'cat -n'

cat -n `basename $0`
# 区别就是'cat -n'对空行也进行计数.
# 注意'nl -ba'也会这么做.

exit 0
# -----------------------------------------------------------------
%%%&&&line-number.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@list-glob.sh@@@!!!************************************************************************************
#!/bin/bash
# list-glob.sh: 使用"globbing", 在for循环中产生[list]

echo

for file in *
#           ^  在表达式中识别文件名匹配时,
#+             Bash将执行文件名扩展.
do
  ls -l "$file"  # 列出在$PWD(当前目录)中的所有文件.
  #  回想一下,通配符"*"能够匹配所有文件,
  #+ 然而,在"globbing"中,是不能比配"."文件的.

  #  如果没匹配到任何文件,那它将扩展成自己.
  #  为了不让这种情况发生,那就设置nullglob选项
  #+   (shopt -s nullglob).
  #  感谢, S.C.
done

echo; echo

for file in [jx]*
do
  rm -f $file    # 只删除当前目录下以"j"或"x"开头的文件.
  echo "Removed file /"$file/"".
done

echo

exit 0
%%%&&&list-glob.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@logevents.sh@@@!!!************************************************************************************
#!/bin/bash
# logevents.sh, 由Stephane Chazelas所编写.

# 把事件记录在一个文件中.
# 必须以root身份运行 (这样才有权限访问/var/log).

ROOT_UID=0     # 只有$UID值为0的用户才具有root权限.
E_NOTROOT=67   # 非root用户的退出错误.


if [ "$UID" -ne "$ROOT_UID" ]
then
  echo "Must be root to run this script."
  exit $E_NOTROOT
fi 


FD_DEBUG1=3
FD_DEBUG2=4
FD_DEBUG3=5

# 去掉下边两行注释中的一行, 来激活脚本.
# LOG_EVENTS=1
# LOG_VARS=1


log()  # 把时间和日期写入日志文件.
{
echo "$(date)  $*" >&7     # 这会把日期*附加*到文件中.
                              # 参考下边的代码.
}

 

case $LOG_LEVEL in
 1) exec 3>&2         4> /dev/null 5> /dev/null;;
 2) exec 3>&2         4>&2         5> /dev/null;;
 3) exec 3>&2         4>&2         5>&2;;
 *) exec 3> /dev/null 4> /dev/null 5> /dev/null;;
esac

FD_LOGVARS=6
if [[ $LOG_VARS ]]
then exec 6>> /var/log/vars.log
else exec 6> /dev/null               # 丢弃输出.
fi

FD_LOGEVENTS=7
if [[ $LOG_EVENTS ]]
then
  # then exec 7 >(exec gawk '{print strftime(), $0}' >> /var/log/event.log)
  # 上面这行不能在2.04版本的Bash上运行.
  exec 7>> /var/log/event.log        # 附加到"event.log".
  log                                      # 记录日期与时间.
else exec 7> /dev/null                  # 丢弃输出.
fi

echo "DEBUG3: beginning" >&${FD_DEBUG3}

ls -l >&5 2>&4                       # command1 >&5 2>&4

echo "Done"                                # command2

echo "sending mail" >&${FD_LOGEVENTS}   # 将字符串"sending mail"写到文件描述符#7.


exit 0
%%%&&&logevents.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@logging-wrapper.sh@@@!!!************************************************************************************
#!/bin/bash
#  通用的shell包装,
#+ 执行一个操作, 然后把所作的操作写入到日志文件中.

# 需要设置如下两个变量.
OPERATION=
#         可以是一个复杂的命令链,
#+        比如awk脚本或者一个管道 . . .
LOGFILE=
#         命令行参数, 不管怎么样, 操作一般都需要参数. (译者注: 这行解释的是下面的OPTIONS变量, 不是LOGFILE.)


OPTIONS="$@"


# 记录下来.
echo "`date` + `whoami` + $OPERATION "$@"" >> $LOGFILE
# 现在, 执行操作.
exec $OPERATION "$@"

# 必须在操作执行之前, 记录到日志文件中.
# 为什么?
%%%&&&logging-wrapper.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@lookup.sh@@@!!!************************************************************************************
#!/bin/bash
# lookup: 对指定数据文件中的每个单词都做一遍字典查询.

file=words.data  # 指定的要搜索的数据文件.

echo

while [ "$word" != end ]  # 数据文件中最后一个单词.
do
  read word      # 从数据文件中读, 因为在循环的后边重定向了.
  look $word > /dev/null  # 不想将字典文件中的行显示出来.
  lookup=$?      # 'look'命令的退出状态.

  if [ "$lookup" -eq 0 ]
  then
    echo "/"$word/" is valid."
  else
    echo "/"$word/" is invalid."
  fi 

done <"$file"    # 将stdin重定向到$file, 所以"reads"来自于$file.

echo

exit 0

# ----------------------------------------------------------------
# 下边的代码行将不会执行, 因为上边已经有"exit"命令了.


# Stephane Chazelas建议使用下边更简洁的方法:

while read word && [[ $word != end ]]
do if look "$word" > /dev/null
   then echo "/"$word/" is valid."
   else echo "/"$word/" is invalid."
   fi
done <"$file"

exit 0
%%%&&&lookup.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@lowercase.sh@@@!!!************************************************************************************
#!/bin/bash
#
#  将当前目录下的所有文全部转换为小写.
#                                     
#  灵感来自于John Dubois的脚本,
#+ Chet Ramey将其转换为Bash脚本,
#+ 然后被本书作者精简了一下.


for filename in *                # 遍历当前目录下的所有文件.
do                                                                        
   fname=`basename $filename`                                             
   n=`echo $fname | tr A-Z a-z`  # 将名字修改为小写.
   if [ "$fname" != "$n" ]       # 只对那些文件名不是小写的文件进行重命名.
   then
     mv $fname $n
   fi 
done  

exit $?


# 下边的代码将不会被执行, 因为上边的"exit".
#-------------------------------------------#
# 删除上边的内容, 来运行下边的内容.
                                                               
# 对于那些文件名中包含空白和新行的文件, 上边的脚本就不能工作了.
# Stephane Chazelas因此建议使用下边的方法:


for filename in *    # 不必非得使用basename命令,
                     # 因为"*"不会返回任何包含"/"的文件.
do n=`echo "$filename/" | tr '[:upper:]' '[:lower:]'`
#                             POSIX 字符集标记法.
#                    添加的斜线是为了在文件名结尾换行不会被
#                    命令替换删掉.
   # 变量替换:
   n=${n%/}          # 从文件名中将上边添加在结尾的斜线删除掉.
   [[ $filename == $n ]] || mv "$filename" "$n"
                     # 检查文件名是否已经是小写.
done

exit $?
%%%&&&lowercase.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@m4.sh@@@!!!************************************************************************************
#!/bin/bash
# m4.sh: 使用m4宏处理器

# 字符串操作
string=abcdA01
echo "len($string)" | m4                           # 7
echo "substr($string,4)" | m4                      # A01
echo "regexp($string,[0-1][0-1],/&Z)" | m4         # 01Z

# 算术操作
echo "incr(22)" | m4                               # 23
echo "eval(99 / 3)" | m4                           # 33

exit 0
%%%&&&m4.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@mailbox_grep.sh@@@!!!************************************************************************************
#!/bin/bash
#  由Francisco Lobo所提供的脚本,
#+ 本文作者进行了少量修改和注释.
#  并且经过授权, 可以使用在本书中.(感谢你!)

# 这个脚本不能运行于比Bash version 3.0更低的版本中.


E_MISSING_ARG=67
if [ -z "$1" ]
then
  echo "Usage: $0 mailbox-file"
  exit $E_MISSING_ARG
fi

mbox_grep()  # 分析邮箱文件.
{
    declare -i body=0 match=0
    declare -a date sender
    declare mail header value


    while IFS= read -r mail
#         ^^^^                 重新设置$IFS.
#  否则"read"会从它的输入中截去开头和结尾的空格.

   do
       if [[ $mail =~ "^From " ]]   # 匹配消息中的"From"域.
       then
          (( body  = 0 ))           # 取消("Zero out"俚语)变量.
          (( match = 0 ))
          unset date

       elif (( body ))
       then
            (( match ))
            # echo "$mail"
            # 如果你想显示整个消息体的话, 那么就打开上面的注释行.

       elif [[ $mail ]]; then
          IFS=: read -r header value <<< "$mail"
          #                          ^^^  "here string"

          case "$header" in
          [Ff][Rr][Oo][Mm] ) [[ $value =~ "$2" ]] && (( match++ )) ;;
          # 匹配"From"行.
          [Dd][Aa][Tt][Ee] ) read -r -a date <<< "$value" ;;
          #                                  ^^^
          # 匹配"Date"行.
          [Rr][Ee][Cc][Ee][Ii][Vv][Ee][Dd] ) read -r -a sender <<< "$value" ;;
          #                                                    ^^^
          # 匹配IP地址(可能被欺骗).
          esac

       else
          (( body++ ))
          (( match  )) &&
          echo "MESSAGE ${date:+of: ${date[*]} }"
       #    整个$date数组                  ^
          echo "IP address of sender: ${sender[1]}"
       #    "Received"行的第二个域             ^

       fi


    done < "$1" # 将文件的stdout重定向到循环中.
}


mbox_grep "$1"  # 将邮箱文件发送到函数中.

exit $?

# 练习:
# -----
# 1) 拆开上面的这个函数, 把它分成多个函数,
#+   这样可以提高代码的可读性.
# 2) 对这个脚本添加额外的分析, 可以分析不同的关键字.

 

$ mailbox_grep.sh scam_mail
--> MESSAGE of Thu, 5 Jan 2006 08:00:56 -0500 (EST)
--> IP address of sender: 196.3.62.4
%%%&&&mailbox_grep.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@mail-format.sh@@@!!!************************************************************************************
#!/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=65
E_NOFILE=66

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.
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 bloated executables.

exit 0
%%%&&&mail-format.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@makedict.sh@@@!!!************************************************************************************
#!/bin/bash
# makedict.sh  [make dictionary]

# Modification of /usr/sbin/mkdict 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 lexicographic research.


E_BADARGS=65

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 $* |                             # Contents of 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
                                     #+ (original script).
        tr -c '/012a-z'  '/012' |    #  Rather than deleting
                                     #+ now change non-alpha to newlines.
        sort |                       # $SORT options unnecessary now.
        uniq |                       # Remove duplicates.
        grep -v '^#' |               # Delete lines beginning with a hashmark.
        grep -v '^$'                 # Delete blank lines.

exit 0 
%%%&&&makedict.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@manview.sh@@@!!!************************************************************************************
#!/bin/bash
# manview.sh: 将man页源文件格式化以方便查看.

#  当你想阅读man页的时候, 这个脚本就有用了.
#  它允许你在运行的时候查看
#+ 中间结果.

E_WRONGARGS=65

if [ -z "$1" ]
then
  echo "Usage: `basename $0` filename"
  exit $E_WRONGARGS
fi

# ---------------------------
groff -Tascii -man $1 | less
# 来自于groff的man页.
# ---------------------------

#  如果man页中包括表或者等式,
#+ 那么上边的代码就够呛了.
#  下边的这行代码可以解决上边的这个问题.
#
#   gtbl < "$1" | geqn -Tlatin1 | groff -Tlatin1 -mtty-char -man
#
#   感谢, S.C.

exit 0
%%%&&&manview.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@match-string.sh@@@!!!************************************************************************************
#!/bin/bash
# match-string.sh: 简单的字符串匹配

match_string ()
{
  MATCH=0
  NOMATCH=90
  PARAMS=2     # 此函数需要2个参数.
  BAD_PARAMS=91

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

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


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


match_string $a     # 参数个数错误.
echo $?             # 91

match_string $a $b  # 不匹配
echo $?             # 90

match_string $b $d  # 匹配
echo $?             # 0


exit 0     
%%%&&&match-string.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@max2.sh@@@!!!************************************************************************************
#!/bin/bash
# max2.sh: 取两个大整数中的最大值.

#  这是前一个例子"max.sh"的修改版,
#+ 这个版本可以比较两个大整数.

EQUAL=0             # 如果两个值相等, 那就返回这个值.
E_PARAM_ERR=-99999  # 没有足够多的参数传递给函数.
#           ^^^^^^    任意超出范围的参数都可以传递进来.

max2 ()             # "返回"两个整数中最大的那个.
{
if [ -z "$2" ]
then
  echo $E_PARAM_ERR
  return
fi

if [ "$1" -eq "$2" ]
then
  echo $EQUAL
  return
else
  if [ "$1" -gt "$2" ]
  then
    retval=$1
  else
    retval=$2
  fi
fi

echo $retval        # 输出(到stdout), 而没有用返回值.
                    # 为什么?
}


return_val=$(max2 33001 33997)
#            ^^^^             函数名
#                 ^^^^^ ^^^^^ 传递进来的参数
#  这其实是命令替换的一种形式:
#+ 可以把函数看作为一个命令,
#+ 然后把函数的stdout赋值给变量"return_val."


# ========================= OUTPUT ========================
if [ "$return_val" -eq "$E_PARAM_ERR" ]
  then
  echo "Error in parameters passed to comparison function!"
elif [ "$return_val" -eq "$EQUAL" ]
  then
    echo "The two numbers are equal."
else
    echo "The larger of the two numbers is $return_val."
fi
# =========================================================
 
exit 0

#  练习:
#  -----
#  1) 找到一种更优雅的方法,
#+    来测试传递给函数的参数.
#  2) 简化"输出"段的if/then结构.
#  3) 重写这个脚本, 使其能够从命令行参数中获得输入.
%%%&&&max2.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@max.sh@@@!!!************************************************************************************
#!/bin/bash
# max.sh: 取两个整数中的最大值.

E_PARAM_ERR=-198    # 如果传给函数的参数少于2个时, 就返回这个值.
EQUAL=-199          # 如果两个整数相等时, 返回这个值.
#  任意超出范围的
#+ 参数值都可能传递到函数中.

max2 ()             # 返回两个整数中的最大值.
{                   # 注意: 参与比较的数必须小于257.
if [ -z "$2" ]
then
  return $E_PARAM_ERR
fi

if [ "$1" -eq "$2" ]
then
  return $EQUAL
else
  if [ "$1" -gt "$2" ]
  then
    return $1
  else
    return $2
  fi
fi
}

max2 33 34
return_val=$?

if [ "$return_val" -eq $E_PARAM_ERR ]
then
  echo "Need to pass two parameters to the function."
elif [ "$return_val" -eq $EQUAL ]
  then
    echo "The two numbers are equal."
else
    echo "The larger of the two numbers is $return_val."
fi 

 
exit 0

#  练习(简单):
#  -----------
#  把这个脚本转化为交互式脚本,
#+ 也就是, 修改这个脚本, 让其要求调用者输入2个数.
%%%&&&max.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@missing-keyword.sh@@@!!!************************************************************************************
#!/bin/bash
# missing-keyword.sh: 这个脚本会产生什么错误?

for a in 1 2 3
do
  echo "$a"
# done     # 第7行上的关键字done'被注释掉了.

exit 0 
%%%&&&missing-keyword.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@monthlypmt.sh@@@!!!************************************************************************************
#!/bin/bash
# monthlypmt.sh: 计算按月偿还贷款的数量.


#  这份代码是一份修改版本, 原始版本在"mcalc"(贷款计算)包中,
#+ 这个包的作者是Jeff Schmidt和Mendel Cooper(本书作者).
#   http://www.ibiblio.org/pub/Linux/apps/financial/mcalc-1.6.tar.gz  [15k]

echo
echo "Given the principal, interest rate, and term of a mortgage,"
echo "calculate the monthly payment."

bottom=1.0

echo
echo -n "Enter principal (no commas) "
read principal
echo -n "Enter interest rate (percent) "  # 如果是12%, 那就键入"12", 而不是".12".
read interest_r
echo -n "Enter term (months) "
read term


 interest_r=$(echo "scale=9; $interest_r/100.0" | bc) # 转换成小数.
                 # "scale"指定了有效数字的个数.
 

 interest_rate=$(echo "scale=9; $interest_r/12 + 1.0" | bc)
 

 top=$(echo "scale=9; $principal*$interest_rate^$term" | bc)

 echo; echo "Please be patient. This may take a while."

 let "months = $term - 1"
# ====================================================================
 for ((x=$months; x > 0; x--))
 do
   bot=$(echo "scale=9; $interest_rate^$x" | bc)
   bottom=$(echo "scale=9; $bottom+$bot" | bc)
#  bottom = $(($bottom + $bot"))
 done
# ====================================================================

# --------------------------------------------------------------------
#  Rick Boivie给出了一个对上边循环的修改方案,
#+ 这个修改更加有效率, 将会节省大概2/3的时间.

# for ((x=1; x <= $months; x++))
# do
#   bottom=$(echo "scale=9; $bottom * $interest_rate + 1" | bc)
# done


#  然后他又想出了一个更加有效率的版本,
#+ 将会节省95%的时间!

# bottom=`{
#     echo "scale=9; bottom=$bottom; interest_rate=$interest_rate"
#     for ((x=1; x <= $months; x++))
#     do
#          echo 'bottom = bottom * interest_rate + 1'
#     done
#     echo 'bottom'
#     } | bc`       # 在命令替换中嵌入一个'for循环'.
# --------------------------------------------------------------------------
#  另一方面, Frank Wang建议:
#  bottom=$(echo "scale=9; ($interest_rate^$term-1)/($interest_rate-1)" | bc)

#  因为 . . .
#  在循环后边的算法
#+ 事实上是一个等比数列的求和公式.
#  求和公式是 e0(1-q^n)/(1-q),
#+ e0是第一个元素, q=e(n+1)/e(n),
#+ n是元素数量.
# --------------------------------------------------------------------------


 # let "payment = $top/$bottom"
 payment=$(echo "scale=2; $top/$bottom" | bc)
 # 使用2位有效数字来表示美元和美分.
 
 echo
 echo "monthly payment = /$$payment"  # 在总和的前边显示美元符号.
 echo


 exit 0


 # 练习:
 #   1) 处理输入允许本金总数中的逗号.
 #   2) 处理输入允许按照百分号和小数点的形式输入利率.
 #   3) 如果你真正想好好编写这个脚本,
 #      那么就扩展这个脚本让它能够打印出完整的分期付款表.
%%%&&&monthlypmt.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@multiple-processes.sh@@@!!!************************************************************************************
#!/bin/bash
# parent.sh
# 在多处理器(SMP box)的机器里运行多进程.
# 作者: Tedman Eng

#  我们下面要介绍两个脚本, 这是第一个,
#+ 这两个脚本都要放到当前工作目录下.

 


LIMIT=$1         # 想要启动的进程总数
NUMPROC=4        # 并发的线程数量(forks?)
PROCID=1         # 启动的进程ID
echo "My PID is $$"

function start_thread() {
        if [ $PROCID -le $LIMIT ] ; then
                ./child.sh $PROCID&
                let "PROCID++"
        else
           echo "Limit reached."
           wait
           exit
        fi
}

while [ "$NUMPROC" -gt 0 ]; do
        start_thread;
        let "NUMPROC--"
done


while true
do

trap "start_thread" SIGRTMIN

done

exit 0

 

# ======== 下面是第二个脚本 ========


#!/bin/bash
# child.sh
# 在SMP box上运行多进程.
# 这个脚本会被parent.sh调用.
# 作者: Tedman Eng

temp=$RANDOM
index=$1
shift
let "temp %= 5"
let "temp += 4"
echo "Starting $index  Time:$temp" "$@"
sleep ${temp}
echo "Ending $index"
kill -s SIGRTMIN $PPID

exit 0


# ======================= 脚本作者注 ======================= #
#  这个脚本并不是一点bug都没有.
#  我使用limit = 500来运行这个脚本, 但是在过了开头的一两百个循环后,
#+ 有些并发线程消失了!
#  还不能确定这是否是由捕捉信号的冲突引起的, 或者是其他什么原因.
#  一旦接收到捕捉的信号, 那么在下一次捕捉到来之前,
#+ 会有一段短暂的时间来执行信号处理程序,
#+ 在信号处理程序处理的过程中, 很有可能会丢失一个想要捕捉的信号, 因此失去一个产生子进程的机会.

#  毫无疑问的, 肯定有人能够找出产生这个bug的原因,
#+ 并且在将来的某个时候. . . 修正它.

 

# ===================================================================== #

 

# ----------------------------------------------------------------------#

 

#################################################################
# 下面的脚本是由Vernia Damiano原创.
# 不幸的是, 它并不能正常工作.
#################################################################

#!/bin/bash

#  要想调用这个脚本, 至少需要一个整形参数
#+ (并发的进程数).
#  所有的其他参数都传递给要启动的进程.


INDICE=8        # 想要启动的进程数目
TEMPO=5         # 每个进程最大的睡眠时间
E_BADARGS=65    # 如果没有参数传到脚本中, 那么就返回这个错误码.

if [ $# -eq 0 ] # 检查一下, 至少要传递一个参数给脚本.
then
  echo "Usage: `basename $0` number_of_processes [passed params]"
  exit $E_BADARGS
fi

NUMPROC=$1              # 并发进程数
shift
PARAMETRI=( "$@" )      # 每个进程的参数

function avvia() {
         local temp
         local index
         temp=$RANDOM
         index=$1
         shift
         let "temp %= $TEMPO"
         let "temp += 1"
         echo "Starting $index Time:$temp" "$@"
         sleep ${temp}
         echo "Ending $index"
         kill -s SIGRTMIN $$
}

function parti() {
         if [ $INDICE -gt 0 ] ; then
              avvia $INDICE "${PARAMETRI[@]}" &
                let "INDICE--"
         else
                trap : SIGRTMIN
         fi
}

trap parti SIGRTMIN

while [ "$NUMPROC" -gt 0 ]; do
         parti;
         let "NUMPROC--"
done

wait
trap - SIGRTMIN

exit $?

: <<SCRIPT_AUTHOR_COMMENTS
我需要使用指定的选项来运行一个程序,
并且能够接受不同的文件, 而且要运行在一个多处理器(SMP)的机器上.
所以我想(我也会)运行指定数目个进程,
并且每个进程终止之后, 都要启动一个新进程.

"wait"命令并没有提供什么帮助, 因为它需要等待一个指定的后台进程,
或者等待*全部*的后台进程. 所以我编写了[这个]bash脚本程序来完成这个工作,
并且使用了"trap"指令.
  --Vernia Damiano
SCRIPT_AUTHOR_COMMENTS
%%%&&&multiple-processes.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@multiplication.sh@@@!!!************************************************************************************
#!/bin/bash
# multiplication.sh

multiply ()                     # 将乘数作为参数传递进来.
{                               # 可以接受多个参数.

  local product=1

  until [ -z "$1" ]             # 直到处理完所有的参数...
  do
    let "product *= $1"
    shift
  done

  echo $product                 #  不会echo到stdout,
}                               #+ 因为要把它赋值给一个变量.

mult1=15383; mult2=25211
val1=`multiply $mult1 $mult2`
echo "$mult1 X $mult2 = $val1"
                                # 387820813

mult1=25; mult2=5; mult3=20
val2=`multiply $mult1 $mult2 $mult3`
echo "$mult1 X $mult2 X $mult3 = $val2"
                                # 2500

mult1=188; mult2=37; mult3=25; mult4=47
val3=`multiply $mult1 $mult2 $mult3 $mult4`
echo "$mult1 X $mult2 X $mult3 X $mult4 = $val3"
                                # 8173300

exit 0
%%%&&&multiplication.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@nested-loop.sh@@@!!!************************************************************************************
#!/bin/bash
# nested-loop.sh: 嵌套的"for"循环.

outer=1             # 设置外部循环计数.

# 开始外部循环.
for a in 1 2 3 4 5
do
  echo "Pass $outer in outer loop."
  echo "---------------------"
  inner=1           # 重置内部循环计数.

  # ===============================================
  # 开始内部循环.
  for b in 1 2 3 4 5
  do
    echo "Pass $inner in inner loop."
    let "inner+=1"  # 增加内部循环计数.
  done
  # 内部循环结束.
  # ===============================================

  let "outer+=1"    # 增加外部循环的计数.
  echo              # 每次外部循环之间的间隔.
done              
# 外部循环结束.

exit 0
%%%&&&nested-loop.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@numbers.sh@@@!!!************************************************************************************
#!/bin/bash
# numbers.sh: 几种不同数制的数字表示法.

# 10进制: 默认情况
let "dec = 32"
echo "decimal number = $dec"             # 32
# 这没什么特别的.


# 8进制: 以'0'(零)开头
let "oct = 032"
echo "octal number = $oct"               # 26
# 表达式结果是用10进制表示的.
# ---------------------------

# 16进制: 以'0x'或者'0X'开头的数字
let "hex = 0x32"
echo "hexadecimal number = $hex"         # 50
# 表达式结果是用10进制表示的.

# 其他进制: BASE#NUMBER
# BASE的范围在2到64之间.
# NUMBER的值必须使用BASE范围内的符号来表示, 具体看下边的示例.


let "bin = 2#111100111001101"
echo "binary number = $bin"              # 31181

let "b32 = 32#77"
echo "base-32 number = $b32"             # 231

let "b64 = 64#@_"
echo "base-64 number = $b64"             # 4031
# 这个表示法只能工作于受限的ASCII字符范围(2 - 64).
# 10个数字 + 26个小写字母 + 26个大写字符 + @ + _


echo

echo $((36#zz)) $((2#10101010)) $((16#AF16)) $((53#1aA))
                                         # 1295 170 44822 3375


#  重要的注意事项:
#  ---------------
#  使用一个超出给定进制的数字的话,
#+ 将会引起一个错误.

let "bad_oct = 081"
# (部分的) 错误消息输出:
#  bad_oct = 081: value too great for base (error token is "081")
#              Octal numbers use only digits in the range 0 - 7.

exit 0       # 感谢, Rich Bartell 和 Stephane Chazelas的指正.
%%%&&&numbers.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@obj-oriented.sh@@@!!!************************************************************************************
#!/bin/bash
# obj-oriented.sh: Object-oriented programming in a shell script.
# Script by Stephane Chazelas.

#  Important Note:
#  --------- ----
#  If running this script under version 3 or later of Bash,
#+ replace all periods in function names with a "legal" character,
#+ for example, an underscore.


person.new()        # Looks almost like a class declaration in C++.
{
  local obj_name=$1 name=$2 firstname=$3 birthdate=$4

  eval "$obj_name.set_name() {
          eval /"$obj_name.get_name() {
                   echo /$1
                 }/"
        }"

  eval "$obj_name.set_firstname() {
          eval /"$obj_name.get_firstname() {
                   echo /$1
                 }/"
        }"

  eval "$obj_name.set_birthdate() {
          eval /"$obj_name.get_birthdate() {
            echo /$1
          }/"
          eval /"$obj_name.show_birthdate() {
            echo /$(date -d /"1/1/1970 0:0:/$1 GMT/")
          }/"
          eval /"$obj_name.get_age() {
            echo /$(( (/$(date +%s) - /$1) / 3600 / 24 / 365 ))
          }/"
        }"

  $obj_name.set_name $name
  $obj_name.set_firstname $firstname
  $obj_name.set_birthdate $birthdate
}

echo

person.new self Bozeman Bozo 101272413
# Create an instance of "person.new" (actually passing args to the function).

self.get_firstname       #   Bozo
self.get_name            #   Bozeman
self.get_age             #   28
self.get_birthdate       #   101272413
self.show_birthdate      #   Sat Mar 17 20:13:33 MST 1973

echo

#  typeset -f
#+ to see the created functions (careful, it scrolls off the page).

exit 0
%%%&&&obj-oriented.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@online.sh@@@!!!************************************************************************************
#!/bin/bash
# logon.sh: 一个检查你是否在线的脚本, 这个脚本实现的很简陋.

umask 177  # 确保temp文件并不是所有用户都有权限访问.


TRUE=1
LOGFILE=/var/log/messages
#  注意: $LOGFILE必须是可读的
#+ (使用root身份来执行, chmod 644 /var/log/messages).
TEMPFILE=temp.$$
#  使用脚本的进程ID, 来创建一个"唯一"的临时文件名.
#     也可以使用'mktemp'.
#     比如:
#     TEMPFILE=`mktemp temp.XXXXXX`
KEYWORD=address
#  登陆时, 把"remote IP address xxx.xxx.xxx.xxx"
#                      添加到/var/log/messages中.
ONLINE=22
USER_INTERRUPT=13
CHECK_LINES=100
#  日志文件有多少行需要检查.

trap 'rm -f $TEMPFILE; exit $USER_INTERRUPT' TERM INT
#  如果脚本被control-c中途中断的话, 那么就清除掉临时文件.

echo

while [ $TRUE ]  #死循环.
do
  tail -$CHECK_LINES $LOGFILE> $TEMPFILE
  #  将系统日志文件的最后100行保存到临时文件中.
  #  这么做很有必要, 因为新版本的内核在登陆的时候, 会产生许多登陆信息.
  search=`grep $KEYWORD $TEMPFILE`
  #  检查是否存在"IP address"片断,
  #+ 它用来提示, 这是一次成功的网络登陆.

  if [ ! -z "$search" ] #  必须使用引号, 因为变量可能会包含一些空白符.
  then
     echo "On-line"
     rm -f $TEMPFILE    #  清除临时文件.
     exit $ONLINE
  else
     echo -n "."        #  echo的-n选项不会产生换行符.
                        #+ 这样你就可以在一行上连续打印.
  fi

  sleep 1 
done 


#  注意: 如果你将变量KEYWORD的值改为"Exit",
#+ 当在线时, 这个脚本就可以被用来检查
#+ 意外的掉线情况.

# 练习: 按照上面"注意"中所说的那样来修改这个脚本,
#       让它表现的更好.

exit 0


# Nick Drage建议使用另一种方法:

while true
  do ifconfig ppp0 | grep UP 1> /dev/null && echo "connected" && exit 0
  echo -n "."   # 不停的打印(.....), 用来提示用户等待, 直到连接上位置.
  sleep 2
done

# 问题: 使用Control-C来终止这个进程可能是不够的.
#+         (可能还会继续打印"...")
# 练习: 修复这个问题.

 

# Stephane Chazelas提出了另一种方法:

CHECK_INTERVAL=1

while ! tail -1 "$LOGFILE" | grep -q "$KEYWORD"
do echo -n .
   sleep $CHECK_INTERVAL
done
echo "On-line"

# 练习: 讨论一下这几种不同方法
#       各自的优缺点.
%%%&&&online.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@paragraph-space.sh@@@!!!************************************************************************************
#!/bin/bash
# paragraph-space.sh

# 在一个单倍行距的文本文件中插入空行.
# Usage: $0 <FILENAME

MINLEN=45        # 可能需要修改这个值.
#  假定行的长度小于$MINLEN所指定的长度的时候
#+ 才认为此段结束.

while read line  # 提供和输入文件一样多的行...
do
  echo "$line"   # 输入所读入的行本身.

  len=${#line}
  if [ "$len" -lt "$MINLEN" ]
    then echo    # 在短行(译者注: 也就是小于$MINLEN个字符的行)后面添加一个空行.
  fi 
done

exit 0
%%%&&¶graph-space.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@param-sub.sh@@@!!!************************************************************************************
#!/bin/bash
# param-sub.sh

#  一个变量是否被声明或设置,
#+ 将会影响这个变量是否使用默认值,
#+ 即使这个变量值为空(null).

username0=
echo "username0 has been declared, but is set to null."
echo "username0 = ${username0-`whoami`}"
# 不会有输出.

echo

echo username1 has not been declared.
echo "username1 = ${username1-`whoami`}"
# 将会输出默认值.

username2=
echo "username2 has been declared, but is set to null."
echo "username2 = ${username2:-`whoami`}"
#                            ^
# 会输出, 因为:-会比-多一个条件测试.
# 可以与上边的例子比较一下.


#

# 再来一个:

variable=
# 变量已经被声明, 但是设为空值.

echo "${variable-0}"    # (没有输出)
echo "${variable:-1}"   # 1
#               ^

unset variable

echo "${variable-2}"    # 2
echo "${variable:-3}"   # 3

exit 0
%%%&&¶m-sub.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@patt-matching.sh@@@!!!************************************************************************************
#!/bin/bash
# patt-matching.sh

# 使用# ## % %%来进行参数替换操作的模式匹配. parameter substitution operators.

var1=abcd12345abc6789
pattern1=a*c  # *(通配符)匹配a - c之间的任意字符.

echo
echo "var1 = $var1"           # abcd12345abc6789
echo "var1 = ${var1}"         # abcd12345abc6789
                              # (另一种形式)
echo "Number of characters in ${var1} = ${#var1}"
echo

echo "pattern1 = $pattern1"   # a*c  (匹配'a'到'c'之间的任意字符)
echo "--------------"
echo '${var1#$pattern1}  =' "${var1#$pattern1}"    #         d12345abc6789
# 最短的可能匹配, 去掉abcd12345abc6789的前3个字符.
#                     |-|               ^^^^^
echo '${var1##$pattern1} =' "${var1##$pattern1}"   #                  6789
# 最长的可能匹配, 去掉abcd12345abc6789的前12个字符
#                     |----------|      ^^^^^^

echo; echo; echo

pattern2=b*9            # 匹配'b'到'9'之间的任意字符
echo "var1 = $var1"     # 还是abcd12345abc6789
echo
echo "pattern2 = $pattern2"
echo "--------------"
echo '${var1%pattern2}  =' "${var1%$pattern2}"     #     abcd12345a
# 最短的可能匹配, 去掉abcd12345abc6789的最后6个字符
#                               |----|  ^^^^^^^
echo '${var1%%pattern2} =' "${var1%%$pattern2}"    #     a
# 最长的可能匹配, 去掉abcd12345abc6789的最后12个字符
#                      |-------------|  ^^^^^^^^

# 牢记, #和##是从字符串左边开始, 并且去掉左边的字符串,
#       %和%%从字符串的右边开始, 并且去掉右边的字符串.
# (译者注: 有个好记的方法, 那就是察看键盘顺序, 记住#在%的左边. ^_^)
echo

exit 0
%%%&&&patt-matching.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@pb.sh@@@!!!************************************************************************************
#!/bin/bash
# pb.sh: 电话本

# 由Rick Boivie编写, 已经得到作者授权, 可以在本书中使用.
# 本书作者做了一些修改.

MINARGS=1     #  脚本至少需要一个参数.
DATAFILE=./phonebook
              #  当前目录下,
              #+ 必须有一个名字为"phonebook"的数据文件.
PROGNAME=$0
E_NOARGS=70   #  未传递参数错误.

if [ $# -lt $MINARGS ]; then
      echo "Usage: "$PROGNAME" data"
      exit $E_NOARGS
fi     


if [ $# -eq $MINARGS ]; then
      grep $1 "$DATAFILE"
      # 如果文件$DATAFILE不存在, 'grep'就会打印一个错误信息.
else
      ( shift; "$PROGNAME" $* ) | grep $1
      # 脚本递归调用自身.
fi

exit 0        #  脚本在此退出.
              #  因此, 在这句之后,
     #+ 即使不加"#"号, 也可以添加注释和数据.

# ------------------------------------------------------------------------
"phonebook"数据文件的例子:

John Doe        1555 Main St., Baltimore, MD 21228          (410) 222-3333
Mary Moe        9899 Jones Blvd., Warren, NH 03787          (603) 898-3232
Richard Roe     856 E. 7th St., New York, NY 10009          (212) 333-4567
Sam Roe         956 E. 8th St., New York, NY 10009          (212) 444-5678
Zoe Zenobia     4481 N. Baker St., San Francisco, SF 94338  (415) 501-1631
# ------------------------------------------------------------------------

$bash pb.sh Roe
Richard Roe     856 E. 7th St., New York, NY 10009          (212) 333-4567
Sam Roe         956 E. 8th St., New York, NY 10009          (212) 444-5678

$bash pb.sh Roe Sam
Sam Roe         956 E. 8th St., New York, NY 10009          (212) 444-5678

#  如果给脚本传递的参数超过了一个,
#+ 那这个脚本就*只*会打印包含所有参数的行.
%%%&&&pb.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@pick-card.sh@@@!!!************************************************************************************
#!/bin/bash
# pick-card.sh

# 这是一个从数组中取出随机元素的一个例子.


# 抽取一张牌, 任何一张.

Suites="Clubs
Diamonds
Hearts
Spades"

Denominations="2
3
4
5
6
7
8
9
10
Jack
Queen
King
Ace"

# 注意变量的多行展开.


suite=($Suites)                # 读入一个数组.
denomination=($Denominations)

num_suites=${#suite[*]}        # 计算有多少个数组元素.
num_denominations=${#denomination[*]}

echo -n "${denomination[$((RANDOM%num_denominations))]} of "
echo ${suite[$((RANDOM%num_suites))]}


# $bozo sh pick-cards.sh
# Jack of Clubs


# 感谢, "jipe," 指出$RANDOM的这个用法.
exit 0
%%%&&&pick-card.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@pid-identifier.sh@@@!!!************************************************************************************
#!/bin/bash
# pid-identifier.sh: 给出与指定pid相关联进程的完整路径名.

ARGNO=1  # 期望的参数个数.
E_WRONGARGS=65
E_BADPID=66
E_NOSUCHPROCESS=67
E_NOPERMISSION=68
PROCFILE=exe

if [ $# -ne $ARGNO ]
then
  echo "Usage: `basename $0` PID-number" >&2  # Error message >stderr(错误信息重定向到标准错误).
  exit $E_WRONGARGS
fi 

pidno=$( ps ax | grep $1 | awk '{ print $1 }' | grep $1 )
# 从"ps"命令的输出中搜索带有pid的行, pid位置在第一列#1, 由awk过滤出来.
# 然后再次确认这就是我们所要找的进程, 而不是由这个脚本调用所产生的进程.
# 最后的"grep $1"就是用来过滤掉这种可能性.
#
#    pidno=$( ps ax | awk '{ print $1 }' | grep $1 )
#    这么写就可以了, 这一点由Teemu Huovila指出.

if [ -z "$pidno" ]  # 如果经过所有的过滤之后, 得到的结果是一个长度为0的字符串,
then                # 那就说明这个pid没有相应的进程在运行.
  echo "No such process running."
  exit $E_NOSUCHPROCESS
fi 

# 也可以这么写:
#   if ! ps $1 > /dev/null 2>&1
#   then                # 没有与给定pid相匹配的进程在运行.
#     echo "No such process running."
#     exit $E_NOSUCHPROCESS
#    fi

# 为了简化整个过程, 可以使用"pidof".


if [ ! -r "/proc/$1/$PROCFILE" ]  # 检查读权限.
then
  echo "Process $1 running, but..."
  echo "Can't get read permission on /proc/$1/$PROCFILE."
  exit $E_NOPERMISSION  # 一般用户不能访问/proc目录下的某些文件.
fi 

# 最后两个测试可以使用下面的语句来代替:
#    if ! kill -0 $1 > /dev/null 2>&1 # '0'不是一个信号, but
                                      # 但是这么做, 可以测试一下是否
                                      # 可以向该进程发送信号.
#    then echo "PID doesn't exist or you're not its owner" >&2
#    exit $E_BADPID
#    fi

 

exe_file=$( ls -l /proc/$1 | grep "exe" | awk '{ print $11 }' )
# 或       exe_file=$( ls -l /proc/$1/exe | awk '{print $11}' )
#
# /proc/pid-number/exe是一个符号链接,
# 指向这个调用进程的完整路径名.

if [ -e "$exe_file" ]  # 如果/proc/pid-number/exe存在...
then                 # 那么相应的进程就存在.
  echo "Process #$1 invoked by $exe_file."
else
  echo "No such process running."
fi 


# 这个精心制作的脚本, *几乎*能够被下边这一行所替代:
# ps ax | grep $1 | awk '{ print $5 }'
# 但是, 这样并不会工作...
# 因为'ps'输出的第5列是进程的argv[0](译者注: 这是命令行第一个参数, 即调用时程序用的程序路径本身.)
# 而不是可执行文件的路径.
#
# 然而, 下边这两种方法都能正确地完成这个任务.
#       find /proc/$1/exe -printf '%l/n'
#       lsof -aFn -p $1 -d txt | sed -ne 's/^n//p'

# 附加注释, 是Stephane Chazelas添加的.

exit 0
%%%&&&pid-identifier.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@poem.sh@@@!!!************************************************************************************
#!/bin/bash
# poem.sh: 将本书作者非常喜欢的一首诗, 漂亮的打印出来.

# 诗的行数(单节).
Line[1]="I do not know which to prefer,"
Line[2]="The beauty of inflections"
Line[3]="Or the beauty of innuendoes,"
Line[4]="The blackbird whistling"
Line[5]="Or just after."

# 出处.
Attrib[1]=" Wallace Stevens"
Attrib[2]="/"Thirteen Ways of Looking at a Blackbird/""
# 这首诗已经是公共版权了(版权已经过期了).

echo

for index in 1 2 3 4 5    # 5行.
do
  printf "     %s/n" "${Line[index]}"
done

for index in 1 2          # 出处为2行.
do
  printf "          %s/n" "${Attrib[index]}"
done

echo

exit 0

# 练习:
# -----
# 修改这个脚本, 使其能够从一个文本数据文件中提取出一首诗的内容, 然后将其漂亮的打印出来.
%%%&&&poem.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@pr-asc.sh@@@!!!************************************************************************************
#!/bin/bash
# pr-ascii.sh: 打印ASCII码的字符表.

START=33   # 可打印的ASCII字符的范围(十进制).
END=125

echo " Decimal   Hex     Character"   # 表头.
echo " -------   ---     ---------"

for ((i=START; i<=END; i++))
do
  echo $i | awk '{printf("  %3d       %2x         %c/n", $1, $1, $1)}'
# 在这种上下文中, 不会运行Bash内建的printf命令:
#     printf "%c" "$i"
done

exit 0


#  十进制   16进制     字符
#  -------  ------   ---------
#    33       21         !
#    34       22         "
#    35       23         #
#    36       24         $
#
#    . . .
#
#   122       7a         z
#   123       7b         {
#   124       7c         |
#   125       7d         }


#  将脚本的输出重定向到一个文件中,
#+ 或者通过管道传递给"more":  sh pr-asc.sh | more
%%%&&&pr-asc.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@prepend.sh@@@!!!************************************************************************************
#!/bin/bash
# prepend.sh: 在文件的开头添加文本.
#
#  Kenny Stauffer所捐助的脚本例子,
#+ 本文作者对这个脚本进行了少量修改.


E_NOSUCHFILE=65

read -p "File: " file   #  'read'命令的-p参数用来显示提示符.
if [ ! -e "$file" ]
then   # 如果这个文件不存在, 那就进来.
  echo "File $file not found."
  exit $E_NOSUCHFILE
fi

read -p "Title: " title
cat - $file <<<$title > $file.new

echo "Modified file is $file.new"

exit 0

# 下边是'man bash'中的一段:
# Here String
#  here document的一种变形,形式如下:
#
#   <<<word
#
#  word被扩展并且被提供到command的标准输入中.
%%%&&&prepend.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@primes.sh@@@!!!************************************************************************************
#!/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 uses 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" gets 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 0

#  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, for faster execution.
%%%&&&primes.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@protect_literal.sh@@@!!!************************************************************************************
#! /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
%%%&&&protect_literal.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@pw.sh@@@!!!************************************************************************************
#!/bin/bash
# May need to be invoked with  #!/bin/bash2  on older machines.
#
# Random password generator for Bash 2.x by Antek Sawicki <[email protected]>,
# who generously gave permission to the document author to use it here.
#
# ==> 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
%%%&&&pw.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@q-function.sh@@@!!!************************************************************************************
#!/bin/bash

# Douglas Hofstadter的声名狼藉的序列"Q-series":

# Q(1) = Q(2) = 1
# Q(n) = Q(n - Q(n-1)) + Q(n - Q(n-2)), 当n>2时

# 这是一个令人感到陌生的, 没有规律的"乱序"整数序列.
# 序列的头20项, 如下所示:
# 1 1 2 3 3 4 5 5 6 6 6 8 8 8 10 9 10 11 11 12

#  请参考相关书籍, Hofstadter的, "_Goedel, Escher, Bach: An Eternal Golden Braid_",
#+ 第137页.


LIMIT=100     # 需要计算的数列长度.
LINEWIDTH=20  # 每行打印的个数.

Q[1]=1        # 数列的头两项都为1.
Q[2]=1

echo
echo "Q-series [$LIMIT terms]:"
echo -n "${Q[1]} "             # 输出数列头两项.
echo -n "${Q[2]} "

for ((n=3; n <= $LIMIT; n++))  # C风格的循环条件.
do   # Q[n] = Q[n - Q[n-1]] + Q[n - Q[n-2]]  当n>2时
#  需要将表达式拆开, 分步计算,
#+ 因为Bash不能够很好的处理复杂数组的算术运算.

  let "n1 = $n - 1"        # n-1
  let "n2 = $n - 2"        # n-2
 
  t0=`expr $n - ${Q[n1]}`  # n - Q[n-1]
  t1=`expr $n - ${Q[n2]}`  # n - Q[n-2]
 
  T0=${Q[t0]}              # Q[n - Q[n-1]]
  T1=${Q[t1]}              # Q[n - Q[n-2]]

Q[n]=`expr $T0 + $T1`      # Q[n - Q[n-1]] + Q[n - Q[n-2]]
echo -n "${Q[n]} "

if [ `expr $n % $LINEWIDTH` -eq 0 ]    # 格式化输出.
then   #      ^ 取模操作
  echo # 把每行都拆为20个数字的小块.
fi

done

echo

exit 0

# 这是Q-series的一个迭代实现.
# 更直接明了的实现是使用递归, 请读者作为练习完成.
# 警告: 使用递归的方法来计算这个数列的话, 会花费非常长的时间.
%%%&&&q-function.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@quote-fetch.sh@@@!!!************************************************************************************
#!/bin/bash
# quote-fetch.sh: 下载一份股票报价.


E_NOPARAMS=66

if [ -z "$1" ]  #必须指定需要获取的股票(代号).
  then echo "Usage: `basename $0` stock-symbol"
  exit $E_NOPARAMS
fi

stock_symbol=$1

file_suffix=.html
# 获得一个HTML文件, 所以要正确命名它.
URL='http://finance.yahoo.com/q?s='
# Yahoo金融板块, 后缀是股票查询.

# -----------------------------------------------------------
wget -O ${stock_symbol}${file_suffix} "${URL}${stock_symbol}"
# -----------------------------------------------------------


# 在http://search.yahoo.com上查询相关材料:
# -----------------------------------------------------------
# URL="http://search.yahoo.com/search?fr=ush-news&p=${query}"
# wget -O "$savefilename" "${URL}"
# -----------------------------------------------------------
# 保存相关URL的列表.

exit $?

# 练习:
# -----
#
# 1) 添加一个测试来验证用户是否在线.
#    (暗示: 对"ppp"或"connect"来分析'ps -ax'的输出.
#
# 2) 修改这个脚本, 让这个脚本具有获得本地天气预报的能力,
#+   将用户的zip code作为参数.
%%%&&"e-fetch.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@ra2ogg.sh@@@!!!************************************************************************************
#!/bin/bash
# ra2ogg.sh: 将音频流文件(*.ra)转换为ogg格式的文件.

# 使用"mplayer"媒体播放器程序:
#      http://www.mplayerhq.hu/homepage
#      可能需要安装合适的编解码程序(codec)才能够正常的运行这个脚本.
# 需要使用"ogg"库和"oggenc":
#      http://www.xiph.org/


OFILEPREF=${1%%ra}      # 去掉"ra"后缀.
OFILESUFF=wav           # wav文件的后缀.
OUTFILE="$OFILEPREF""$OFILESUFF"
E_NOARGS=65

if [ -z "$1" ]          # 必须要指定一个需要转换的文件名.
then
  echo "Usage: `basename $0` [filename]"
  exit $E_NOARGS
fi


##########################################################################
mplayer "$1" -ao pcm:file=$OUTFILE
oggenc "$OUTFILE"  # oggenc编码后会自动加上正确的文件扩展名.
##########################################################################

rm "$OUTFILE"      # 删除中介的*.wav文件.
                   # 如果你想保留这个文件的话, 可以把上边这行注释掉.

exit $?

#  注意:
#  ----
#  在网站上, 简单的在*.ram流音频文件上单击的话,
#+ 一般都只会下载真正音频流文件(就是*.ra文件)的URL.
#  你可以使用"wget"或者一些类似的工具
#+ 来下载*.ra文件本身.


#  练习:
#  -----
#  像上面所看到的, 这个脚本只能够转换*.ra文件.
#  给这个脚本添加一些灵活性, 让它能够转换*.ram and other filenames.
#
#  如果你觉得这还不过瘾, 那么你可以扩展这个脚本,
#+ 让它自动下载并转换音频流文件.
#  给出一个URL, (使用"wget")批处理下载音频流文件,
#+ 然后转换它们.
%%%&&&ra2ogg.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@ramdisk.sh@@@!!!************************************************************************************
#!/bin/bash
# ramdisk.sh

#  一个"ramdisk"就是系统RAM内存中的一部分,
#+ 只不过它被当作文件系统来操作.
#  它的优点是访问速度非常快(读/写时间快).
#  缺点: 易失性, 当机器重启或关机时, 会丢失数组.
#+                而且会减少系统可用的RAM.
#
#  那么ramdisk有什么用呢?
#  保存一个大数据集, 比如保存表格或字典.
#+ 这样的话, 可以增加查询速度, 因为访问内存比访问硬盘快得多.


E_NON_ROOT_USER=70             # 必须以root身份来运行.
ROOTUSER_NAME=root

MOUNTPT=/mnt/ramdisk
SIZE=2000                      # 2K个块(可以进行适当的修改)
BLOCKSIZE=1024                 # 每块的大小为1K(1024字节)
DEVICE=/dev/ram0               # 第一个ram设备

username=`id -nu`
if [ "$username" != "$ROOTUSER_NAME" ]
then
  echo "Must be root to run /"`basename $0`/"."
  exit $E_NON_ROOT_USER
fi

if [ ! -d "$MOUNTPT" ]         #  测试挂载点是否已经存在,
then                           #+ 如果做了这个判断的话, 当脚本运行多次的时候,
  mkdir $MOUNTPT               #+ 就不会报错了. (译者注: 主要是为了避免多次创建目录.)
fi

dd if=/dev/zero of=$DEVICE count=$SIZE bs=$BLOCKSIZE  # 把RAM设备的内容用0填充.
                                                      # 为什么必须这么做?
mke2fs $DEVICE                 # 在RAM上创建一个ext2文件系统.
mount $DEVICE $MOUNTPT         # 挂载上.
chmod 777 $MOUNTPT             # 使一般用户也可以访问这个ramdisk.
                               # 然而, 只能使用root身份来卸载它.

echo "/"$MOUNTPT/" now available for use."
# 现在ramdisk就可以访问了, 即使是普通用户也可以访问.

#  小心, ramdisk存在易失性,
#+ 如果重启或关机的话, 保存的内容就会消失.
#  所以, 还是要将你想保存的文件, 保存到常规磁盘目录下.

# 重启之后, 运行这个脚本, 将会再次建立一个ramdisk.
# 如果你仅仅重新加载/mnt/ramdisk, 而没有运行其他步骤的话, 那就不会正常工作.

#  如果对这个脚本进行适当的改进, 就可以将其放入/etc/rc.d/rc.local中,
#+ 这样, 在系统启动的时候就会自动建立一个ramdisk.
#  这么做非常适合于那些对速度要求很高的数据库服务器.

exit 0
%%%&&&ramdisk.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@random2.sh@@@!!!************************************************************************************
#!/bin/bash
# random2.sh: 产生一个范围在 0 - 1 之间的伪随机数.
# 使用了awk的rand()函数.

AWKSCRIPT=' { srand(); print rand() } '
#            Command(s) / 传递到awk中的参数
# 注意, srand()是awk中用来产生伪随机数种子的函数.


echo -n "Random number between 0 and 1 = "

echo | awk "$AWKSCRIPT"
# 如果你省去'echo', 会怎样?

exit 0


# 练习:
# -----

# 1) 使用循环结构, 打印出10个不同的随机数.
#      (提示: 在每次循环过程中, 你必须使用"srand()"函数来生成不同的种子,
#+     如果你不这么做会怎样?)

# 2) 使用整数乘法作为一个比例因子, 在10到100的范围之间,
#+   来产生随机数.

# 3) 同上边的练习#2, 但是这次产生随机整数.
%%%&&&random2.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@random-between.sh@@@!!!************************************************************************************
#!/bin/bash
# random-between.sh
# 产生两个指定值之间的随机数.
# 由Bill Gradwohl编写, 本书作者做了一些修改.
# 脚本作者允许在这里使用.


randomBetween() {
   #  在$min和$max之间,
   #+ 产生一个正的或负的随机数.
   #+ 并且可以被$divisibleBy所整除.
   #  给出一个合理的随机分配的返回值.
   #
   #  Bill Gradwohl - Oct 1, 2003

   syntax() {
   # 在函数中内嵌函数
      echo
      echo    "Syntax: randomBetween [min] [max] [multiple]"
      echo
      echo    "Expects up to 3 passed parameters, but all are completely optional."
      echo    "min is the minimum value"
      echo    "max is the maximum value"
      echo    "multiple specifies that the answer must be a multiple of this value."
      echo    "    i.e. answer must be evenly divisible by this number."
      echo   
      echo    "If any value is missing, defaults area supplied as: 0 32767 1"
      echo    "Successful completion returns 0, unsuccessful completion returns"
      echo    "function syntax and 1."
      echo    "The answer is returned in the global variable randomBetweenAnswer"
      echo    "Negative values for any passed parameter are handled correctly."
   }

   local min=${1:-0}
   local max=${2:-32767}
   local divisibleBy=${3:-1}
   # 默认值分配, 用来处理没有参数传递进来的情况.

   local x
   local spread

   # 确认divisibleBy是正值.
   [ ${divisibleBy} -lt 0 ] && divisibleBy=$((0-divisibleBy))

   # 完整性检查.
   if [ $# -gt 3 -o ${divisibleBy} -eq 0 -o  ${min} -eq ${max} ]; then
      syntax
      return 1
   fi

   # 查看min和max是否颠倒了.
   if [ ${min} -gt ${max} ]; then
      # 交换它们.
      x=${min}
      min=${max}
      max=${x}
   fi

   #  如果min自己并不能够被$divisibleBy所整除,
   #+ 那么就调整max的值, 使其能够被$divisibleBy所整除, 前提是不能放大范围.
   if [ $((min/divisibleBy*divisibleBy)) -ne ${min} ]; then
      if [ ${min} -lt 0 ]; then
         min=$((min/divisibleBy*divisibleBy))
      else
         min=$((((min/divisibleBy)+1)*divisibleBy))
      fi
   fi

   #  如果min自己并不能够被$divisibleBy所整除,
   #+ 那么就调整max的值, 使其能够被$divisibleBy所整除, 前提是不能放大范围.
   if [ $((max/divisibleBy*divisibleBy)) -ne ${max} ]; then
      if [ ${max} -lt 0 ]; then
         max=$((((max/divisibleBy)-1)*divisibleBy))
      else
         max=$((max/divisibleBy*divisibleBy))
      fi
   fi

   #  ---------------------------------------------------------------------
   #  现在, 来做点真正的工作.

   #  注意, 为了得到对于端点来说合适的分配,
   #+ 随机值的范围不得不落在
   #+ 0 和 abs(max-min)+divisibleBy 之间, 而不是 abs(max-min)+1.

   #  对于端点来说,
   #+ 这个少量的增加将会产生合适的分配.

   #  如果修改这个公式, 使用 abs(max-min)+1 来代替 abs(max-min)+divisibleBy的话,
   #+ 也能够得到正确的答案, 但是在这种情况下所生成的随机值对于正好为端点倍数
   #+ 的这种情况来说将是不完美的, 因为正好为端点倍数情况下的随机率比较低,
   #+ 因为你才加1而已, 这比正常的公式下所产生的几率要小的多(正常为加divisibleBy).
   #  ---------------------------------------------------------------------

   spread=$((max-min))
   [ ${spread} -lt 0 ] && spread=$((0-spread))
   let spread+=divisibleBy
   randomBetweenAnswer=$(((RANDOM%spread)/divisibleBy*divisibleBy+min))  

   return 0

   #  然而, Paulo Marcel Coelho Aragao 指出
   #+ 当 $max 和 $min 不能够被$divisibleBy所整除时,
   #+ 这个公式将会失败.
   #
   #  他建议使用如下公式:
   #    rnumber = $(((RANDOM%(max-min+1)+min)/divisibleBy*divisibleBy))

}

# 让我们测试一下这个函数.
min=-14
max=20
divisibleBy=3


#  产生一个所期望的数组answers, 数组下标用来表示在范围内可能出现的值,
#+ 而元素内容记录的是这个值所出现的次数, 如果我们循环足够多次, 那么我们一定会得到至少一次出现机会.

declare -a answer
minimum=${min}
maximum=${max}
   if [ $((minimum/divisibleBy*divisibleBy)) -ne ${minimum} ]; then
      if [ ${minimum} -lt 0 ]; then
         minimum=$((minimum/divisibleBy*divisibleBy))
      else
         minimum=$((((minimum/divisibleBy)+1)*divisibleBy))
      fi
   fi


132    #  如果max本身并不能够被$divisibleBy整除,
133    #+ 那么就调整max的值, 使其能够被$divisibleBy整除, 前提是不能放大范围.

   if [ $((maximum/divisibleBy*divisibleBy)) -ne ${maximum} ]; then
      if [ ${maximum} -lt 0 ]; then
         maximum=$((((maximum/divisibleBy)-1)*divisibleBy))
      else
         maximum=$((maximum/divisibleBy*divisibleBy))
      fi
   fi


#  我们需要产生一个下标全部为正的数组.
#+ 所以我们需要一个displacement,
#+ 这样就可以保证结果都为正.

displacement=$((0-minimum))
for ((i=${minimum}; i<=${maximum}; i+=divisibleBy)); do
   answer[i+displacement]=0
done


# 现在, 让我们循环足够多的次数, 来得到我们想要的答案.
loopIt=1000   #  脚本作者建议循环 100000 次,
              #+ 但是这需要的时间太长了.

for ((i=0; i<${loopIt}; ++i)); do

   #  注意, 我们在这里调用randomBetweenAnswer函数时, 估计将min和max颠倒顺序.
   #+ 这是为了测试在这种情况下, 此函数是否还能正确的运行.

   randomBetween ${max} ${min} ${divisibleBy}

   # 如果答案不是我们所期望的, 就报错.
   [ ${randomBetweenAnswer} -lt ${min} -o ${randomBetweenAnswer} -gt ${max} ] && echo MIN or MAX error - ${randomBetweenAnswer}!
   [ $((randomBetweenAnswer%${divisibleBy})) -ne 0 ] && echo DIVISIBLE BY error - ${randomBetweenAnswer}!

   # 将统计值保存到answer中.
   answer[randomBetweenAnswer+displacement]=$((answer[randomBetweenAnswer+displacement]+1))
done

 

# 让我们来察看一下结果.

for ((i=${minimum}; i<=${maximum}; i+=divisibleBy)); do
   [ ${answer[i+displacement]} -eq 0 ] && echo "We never got an answer of $i." || echo "${i} occurred ${answer[i+displacement]} times."
done


exit 0
%%%&&&random-between.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@random-test.sh@@@!!!************************************************************************************
#!/bin/bash
# RANDOM到底有多随机?

RANDOM=$$       # 使用脚本的进程ID来作为随机数的种子.

PIPS=6          # 一个骰子有6个面.
MAXTHROWS=600   # 如果你没别的事做, 可以增加这个数值.
throw=0         # 抛骰子的次数.

ones=0          #  必须把所有的count都初始化为0,
twos=0          #+ 因为未初始化的变量为null, 不是0.
threes=0
fours=0
fives=0
sixes=0

print_result ()
{
echo
echo "ones =   $ones"
echo "twos =   $twos"
echo "threes = $threes"
echo "fours =  $fours"
echo "fives =  $fives"
echo "sixes =  $sixes"
echo
}

update_count()
{
case "$1" in
  0) let "ones += 1";;   # 因为骰子没有"零", 所以给1.
  1) let "twos += 1";;   # 把这个设为2, 后边也一样.
  2) let "threes += 1";;
  3) let "fours += 1";;
  4) let "fives += 1";;
  5) let "sixes += 1";;
esac
}

echo


while [ "$throw" -lt "$MAXTHROWS" ]
do
  let "die1 = RANDOM % $PIPS"
  update_count $die1
  let "throw += 1"
done 

print_result

exit 0

#  如果RANDOM是真正的随机, 那么摇出来结果应该是平均的.
#  把$MAXTHROWS设为600, 那么每个面应该是100, 上下的出入不应该超过20.
#
#  记住RANDOM毕竟是一个伪随机数,
#+ 并且不是十分完美.

#  随机数的生成是一个十分深奥并复杂的问题.
#  足够长的随机序列, 不但会展现其杂乱无章的一面,
#+ 同样也会展现其机会均等的一面.

# 练习 (很简单):
# --------------
# 重写这个脚本, 做成抛1000次硬币的形式.
# 分为"头"和"字"两面.
%%%&&&random-test.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@read-novar.sh@@@!!!************************************************************************************
#!/bin/bash
# read-novar.sh

echo

# -------------------------- #
echo -n "Enter a value: "
read var
echo "/"var/" = "$var""
# 到这里为止, 都与期望的一样.
# -------------------------- #

echo

# ------------------------------------------------------------------- #
echo -n "Enter another value: "
read           #  没有变量分配给'read'命令, 所以...
               #+ 输入将分配给默认变量, $REPLY.
var="$REPLY"
echo "/"var/" = "$var""
# 这部分代码和上边的代码等价.
# ------------------------------------------------------------------- #

echo

exit 0
%%%&&&read-novar.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@readpipe.sh@@@!!!************************************************************************************
#!/bin/sh
# readpipe.sh
# 这个例子是由Bjon Eriksson所编写的.

last="(null)"
cat $0 |
while read line
do
    echo "{$line}"
    last=$line
done
printf "/nAll done, last:$last/n"

exit 0  # 代码结束.
        # 下边是脚本的(部分)输出.
        # 'echo'出了多余的大括号.

#############################################

./readpipe.sh

{#!/bin/sh}
{last="(null)"}
{cat $0 |}
{while read line}
{do}
{echo "{$line}"}
{last=$line}
{done}
{printf "nAll done, last:$lastn"}


All done, last:(null)

变量(last)被设置在子shell中, 并没有被设置在外边.
%%%&&&readpipe.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@read-redir.sh@@@!!!************************************************************************************
#!/bin/bash

read var1 <data-file
echo "var1 = $var1"
# var1将会把"data-file"的第一行的全部内容都为它的值.

read var2 var3 <data-file
echo "var2 = $var2   var3 = $var3"
# 注意, 这里的"read"命令将会产生一种不直观的行为.
# 1) 重新从文件的开头开始读入变量.
# 2) 每个变量都设置成了以空白分割的字符串.
#    而不是之前的以整行的内容作为变量的值.
# 3) 而最后一个变量将会取得第一行剩余的全部部分(译者注: 不管是否以空白分割).
# 4) 如果需要赋值的变量个数比文件中第一行以空白分割的字符串个数还多的话,
#    那么这些变量将会被赋空值.

echo "------------------------------------------------"

# 如何用循环来解决上边所提到的问题:
while read line
do
  echo "$line"
done <data-file
# 感谢, Heiner Steven 指出了这点.

echo "------------------------------------------------"

# 使用$IFS(内部域分隔变量)来将每行的输入单独的放到"read"中,
# 前提是如果你不想使用默认空白的话.

echo "List of all users:"
OIFS=$IFS; IFS=:       # /etc/passwd 使用 ":" 作为域分隔符.
while read name passwd uid gid fullname ignore
do
  echo "$name ($fullname)"
done </etc/passwd   # I/O 重定向.
IFS=$OIFS              # 恢复原始的$IFS.
# 这段代码也是Heiner Steven编写的.

 

#  在循环内部设置$IFS变量,
#+ 而不用把原始的$IFS
#+ 保存到临时变量中.
#  感谢, Dim Segebart, 指出了这点.
echo "------------------------------------------------"
echo "List of all users:"

while IFS=: read name passwd uid gid fullname ignore
do
  echo "$name ($fullname)"
done </etc/passwd   # I/O 重定向.

echo
echo "/$IFS still $IFS"

exit 0
%%%&&&read-redir.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@read-r.sh@@@!!!************************************************************************************
#!/bin/bash

echo

echo "Enter a string terminated by a //, then press <ENTER>."
echo "Then, enter a second string, and again press <ENTER>."
read var1     # 当 read $var1 时, "/" 将会阻止产生新行.
              #     first line /
              #     second line

echo "var1 = $var1"
#     var1 = first line second line

#  对于每个以 "/" 结尾的行,
#+ 你都会看到一个下一行的提示符, 让你继续向var1输入内容.

echo; echo

echo "Enter another string terminated by a // , then press <ENTER>."
read -r var2  # -r 选项会让 "/" 转义.
              #     first line /

echo "var2 = $var2"
#     var2 = first line /

# 第一个 <ENTER> 就会结束var2变量的录入.

echo

exit 0
%%%&&&read-r.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@realname.sh@@@!!!************************************************************************************
#!/bin/bash
# realname.sh
#
# 依靠username, 从/etc/passwd中获得"真名".


ARGCOUNT=1       # 需要一个参数.
E_WRONGARGS=65

file=/etc/passwd
pattern=$1

if [ $# -ne "$ARGCOUNT" ]
then
  echo "Usage: `basename $0` USERNAME"
  exit $E_WRONGARGS
fi 

file_excerpt ()  # 按照要求的模式来扫描文件, 然后打印文件相关的部分.
{
while read line  # "while"并不一定非得有"[ condition ]"不可.
do
  echo "$line" | grep $1 | awk -F":" '{ print $5 }'  # awk用":"作为界定符.
done
} <$file  # 重定向到函数的stdin.

file_excerpt $pattern

# 是的, 整个脚本其实可以被缩减为
#       grep PATTERN /etc/passwd | awk -F":" '{ print $5 }'
# 或
#       awk -F: '/PATTERN/ {print $5}'
# 或
#       awk -F: '($1 == "username") { print $5 }' # 从username中获得真名.
# 但是, 这些起不到示例的作用.

exit 0
%%%&&&realname.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@reassign-stdout.sh@@@!!!************************************************************************************
#!/bin/bash
# reassign-stdout.sh

LOGFILE=logfile.txt

exec 6>&1           # 将fd #6与stdout链接起来.
                    # 保存stdout.

exec > $LOGFILE     # stdout就被文件"logfile.txt"所代替了.

# ----------------------------------------------------------- #
# 在这块中所有命令的输出都会发送到文件$LOGFILE中.

echo -n "Logfile: "
date
echo "-------------------------------------"
echo

echo "Output of /"ls -al/" command"
echo
ls -al
echo; echo
echo "Output of /"df/" command"
echo
df

# ----------------------------------------------------------- #

exec 1>&6 6>&-      # 恢复stdout, 然后关闭文件描述符#6.

echo
echo "== stdout now restored to default == "
echo
ls -al
echo

exit 0
%%%&&&reassign-stdout.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@recurse.sh@@@!!!************************************************************************************
#!/bin/bash
# recurse.sh

#  脚本能否递归地调用自己?
#  是的, 但这有什么实际的用处吗?
#  (看下面的.)

RANGE=10
MAXVAL=9

i=$RANDOM
let "i %= $RANGE"  # 在0到$RANGE - 1之间, 产生一个随机数.

if [ "$i" -lt "$MAXVAL" ]
then
  echo "i = $i"
  ./$0             #  脚本递归地产生自己的一个新实例, 并调用.
fi                 #  每个子脚本都做同样的事情, until
                   #+ 直到产生的变量$i等于$MAXVAL为止.

#  如果使用"while"循环来代替"if/then"测试结构的话, 会产生问题.
#  解释一下为什么.

exit 0

# 注意:
# -----
# 脚本想要正常的工作, 就必须具备可执行权限.
# 即使使用"sh"命令来调用它, 但是没有设置正确的权限一样会导致问题.
# 解释一下原因.
%%%&&&recurse.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@redir1.sh@@@!!!************************************************************************************
#!/bin/bash
# 使用'exec'重定向stdin.


exec 6<&0          # 将文件描述符#6与stdin链接起来.
                   # 保存stdin.

exec < data-file   # stdin被文件"data-file"所代替.

read a1            # 读取文件"data-file"的第一行.
read a2            # 读取文件"data-file"的第二行.

echo
echo "Following lines read from file."
echo "-------------------------------"
echo $a1
echo $a2

echo; echo; echo

exec 0<&6 6<&-
#  现在将stdin从fd #6中恢复, 因为刚才我们把stdin重定向到#6了,
#+ 然后关闭fd #6 ( 6<&- ), 好让这个描述符继续被其他进程所使用.
#
# <&6 6<&-    这么做也可以.

echo -n "Enter data  "
read b1  # 现在"read"已经恢复正常了, 就是能够正常的从stdin中读取.
echo "Input read from stdin."
echo "----------------------"
echo "b1 = $b1"

echo

exit 0
%%%&&&redir1.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@redir2a.sh@@@!!!************************************************************************************
#!/bin/bash

# 这是上个脚本的另一个版本.

#  Heiner Steven建议,
#+ 为了避免重定向循环运行在子shell中(老版本的shell会这么做), 最好让重定向循环运行在当前工作区内,
#+ 这样的话, 需要提前进行文件描述符重定向,
#+ 因为变量如果在(子shell上运行的)循环中被修改的话, 循环结束后并不会保存修改后的值.


if [ -z "$1" ]
then
  Filename=names.data     # 如果没有指定文件名则使用默认值.
else
  Filename=$1
fi 


exec 3<&0                 # 将stdin保存到文件描述符3.
exec 0<"$Filename"        # 重定向标准输入.

count=0
echo


while [ "$name" != Smith ]
do
  read name               # 从stdin(现在已经是$Filename了)中读取.
  echo $name
  let "count += 1"
done                      #  从文件$Filename中循环读取
                          #+ 因为文件(译者注:指默认文件, 在本节最后)有20行.

#  这个脚本原先在"while"循环的结尾还有一句:
#+      done <"$Filename"
#  练习:
#  为什么不需要这句了?


exec 0<&3                 # 恢复保存的stdin.
exec 3<&-                 # 关闭临时文件描述符3.

echo; echo "$count names read"; echo

exit 0
%%%&&&redir2a.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@redir2.sh@@@!!!************************************************************************************
#!/bin/bash
# redir2.sh

if [ -z "$1" ]
then
  Filename=names.data       # 如果没有指定文件名, 则使用这个默认值.
else
  Filename=$1
fi 
#+ Filename=${1:-names.data}
#  这句可代替上面的测试(参数替换).

count=0

echo

while [ "$name" != Smith ]  # 为什么变量$name要用引号?
do
  read name                 # 从$Filename文件中读取输入, 而不是在stdin中读取输入.
  echo $name
  let "count += 1"
done <"$Filename"           # 重定向stdin到文件$Filename.
#    ^^^^^^^^^^^^

echo; echo "$count names read"; echo

exit 0

#  注意在一些比较老的shell脚本编程语言中,
#+ 重定向的循环是放在子shell里运行的.
#  因此, $count 值返回后会是 0, 此值是在循环开始前的初始值.
#  *如果可能的话*, 尽量避免在Bash或ksh中使用子shell,
#+ 所以这个脚本能够正确的运行.
#  (多谢Heiner Steven指出这个问题.)

#  然而 . . .
#  Bash有时还是*会*在一个使用管道的"while-read"循环中启动一个子shell,
#+ 与重定向的"while"循环还是有区别的.

abc=hi
echo -e "1/n2/n3" | while read l
     do abc="$l"
        echo $abc
     done
echo $abc

#  感谢, Bruno de Oliveira Schneider
#+ 给出上面的代码片段来演示此问题.
#  同时, 感谢, Brian Onn, 修正了一个注释错误.
%%%&&&redir2.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@redir3.sh@@@!!!************************************************************************************
#!/bin/bash
# 和前面的例子相同, 但使用的是"until"循环.

if [ -z "$1" ]
then
  Filename=names.data         # 如果没有指定文件名那就使用默认值.
else
  Filename=$1
fi 

# while [ "$name" != Smith ]
until [ "$name" = Smith ]     # 把!=改为=.
do
  read name                   # 从$Filename中读取, 而不是从stdin中读取.
  echo $name
done <"$Filename"             # 重定向stdin到文件$Filename.
#    ^^^^^^^^^^^^

# 结果和前面例子的"while"循环相同.

exit 0
%%%&&&redir3.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@redir4a.sh@@@!!!************************************************************************************
#!/bin/bash

if [ -z "$1" ]
then
  Filename=names.data          # 如果没有指定文件名, 则使用默认值.
else
  Filename=$1
fi 

Savefile=$Filename.new         # 保存最终结果的文件名.
FinalName=Jonah                # 终止"read"时的名称.

line_count=`wc $Filename | awk '{ print $1 }'`  # 目标文件的行数.


for name in `seq $line_count`
do
  read name
  echo "$name"
  if [ "$name" = "$FinalName" ]
  then
    break
  fi 
done < "$Filename" > "$Savefile"     # 重定向stdin到文件$Filename,
#    ^^^^^^^^^^^^^^^^^^^^^^^^^^^       并且将它保存到备份文件中.

exit 0
%%%&&&redir4a.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@redir4.sh@@@!!!************************************************************************************
#!/bin/bash

if [ -z "$1" ]
then
  Filename=names.data          # 如果没有指定文件名就使用默认值.
else
  Filename=$1
fi 

line_count=`wc $Filename | awk '{ print $1 }'`
#           目标文件的行数.
#
#  此处的代码太过做作, 并且写得很难看,
#+ 但至少展示了"for"循环的stdin可以重定向...
#+ 当然, 你得足够聪明, 才能看得出来.
#
# 更简洁的写法是     line_count=$(wc -l < "$Filename")


for name in `seq $line_count`  # "seq"打印出数字序列.
# while [ "$name" != Smith ]   --   比"while"循环更复杂   --
do
  read name                    # 从$Filename中, 而非从stdin中读取.
  echo $name
  if [ "$name" = Smith ]       # 因为用for循环, 所以需要这个多余测试.
  then
    break
  fi 
done <"$Filename"              # 重定向stdin到文件$Filename.
#    ^^^^^^^^^^^^

exit 0
%%%&&&redir4.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@redir5.sh@@@!!!************************************************************************************
#!/bin/bash

if [ -z "$1" ]
then
  Filename=names.data   # 如果文件名没有指定, 使用默认值.
else
  Filename=$1
fi 

TRUE=1

if [ "$TRUE" ]          # if true    和   if :   都可以.
then
 read name
 echo $name
fi <"$Filename"
#  ^^^^^^^^^^^^

# 只读取了文件的第一行.
# An "if/then"测试结构不能自动地反复地执行, 除非把它们嵌到循环里.

exit 0
%%%&&&redir5.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@ref-params.sh@@@!!!************************************************************************************
#!/bin/bash
# ref-params.sh: 解除传递给函数的参数引用.
#                (复杂的例子)

ITERATIONS=3  # 取得输入的次数.
icount=1

my_read () {
  #  用my_read varname这种形式来调用,
  #+ 将之前用括号括起的值作为默认值输出,
  #+ 然后要求输入一个新值.

  local local_var

  echo -n "Enter a value "
  eval 'echo -n "[$'$1'] "'  #  之前的值.
# eval echo -n "[/$$1] "     #  更容易理解,
                             #+ 但会丢失用户在尾部输入的空格.
  read local_var
  [ -n "$local_var" ] && eval $1=/$local_var

  # "与列表": 如果"local_var"的测试结果为true, 则把变量"$1"的值赋给它.
}

echo

while [ "$icount" -le "$ITERATIONS" ]
do
  my_read var
  echo "Entry #$icount = $var"
  let "icount += 1"
  echo
done 


# 感谢Stephane Chazelas提供这个例子.

exit 0
%%%&&&ref-params.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@remote.bash@@@!!!************************************************************************************
#!/bin/bash
# remote.bash: 使用ssh.

# 这个例子是Michael Zick编写的.
# 授权在本书中使用.


#   假设的一些前提:
#   ---------------
#   fd-2(文件描述符2)的内容并没有被丢弃( '2>/dev/null' ).
#   ssh/sshd假设stderr ('2')将会显示给用户.
#
#   假设sshd正运行在你的机器上.
#   对于绝大多数'标准'的发行版, 都是有sshd的,
#+  并且没有稀奇古怪的ssh-keygen.

# 在你的机器上从命令行中试着运行一下ssh:
#
# $ ssh $HOSTNAME
# 不需要特别的设置, 也会要求你输入密码.
#   接下来输入密码,
#   完成后, $ exit
#
# 能够正常运行么? 如果正常的话, 接下来你可以获得更多的乐趣了.

# 尝试在你的机器上以'root'身份来运行ssh:
#
#   $  ssh -l root $HOSTNAME
#   当要求询问密码时, 输入root的密码, 注意别输入你的用户密码.
#          Last login: Tue Aug 10 20:25:49 2004 from localhost.localdomain
#   完成后键入'exit'.

#  上边的动作将会带给你一个交互的shell.
#  也可以在'single command'模式下建立sshd,
#+ 但是这已经超出本例所讲解的范围了.
#  唯一需要注意的是, 下面的命令都可以运行在
#+ 'single command'模式下.


# 基本的, 写stdout(本地)命令.

ls -l

# 这样远端机器上就会执行相同的命令.
# 如果你想的话, 可以传递不同的'USERNAME'和'HOSTNAME':
USER=${USERNAME:-$(whoami)}
HOST=${HOSTNAME:-$(hostname)}

#  现在, 在远端主机上执行上边的命令,
#+ 当然, 所有的传输都会被加密.

ssh -l ${USER} ${HOST} " ls -l "

#  期望的结果就是在远端主机上列出
#+ 你的用户名所拥有的主目录下的所有文件.
#  如果想看点不一样的东西,
#+ 那就在别的地方运行这个脚本, 别在你自己的主目录下运行这个脚本.

#  换句话说, Bash命令已经作为一个引用行
#+ 被传递到了远端shell中, 这样远端机器就会运行它.
#  在这种情况下, sshd代表你运行了' bash -c "ls -l" '.

#  如果你想不输入密码,
#+ 或者想更详细的了解相关的问题, 请参考:
#+    man ssh
#+    man ssh-keygen
#+    man sshd_config.

exit 0
%%%&&&remote.bash&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@reply.sh@@@!!!************************************************************************************
#!/bin/bash
# reply.sh

# REPLY是提供给'read'命令的默认变量.

echo
echo -n "What is your favorite vegetable? "
read

echo "Your favorite vegetable is $REPLY."
#  当且仅当没有变量提供给"read"命令时,
#+ REPLY才保存最后一个"read"命令读入的值.

echo
echo -n "What is your favorite fruit? "
read fruit
echo "Your favorite fruit is $fruit."
echo "but..."
echo "Value of /$REPLY is still $REPLY."
#  $REPLY还是保存着上一个read命令的值,
#+ 因为变量$fruit被传入到了这个新的"read"命令中.

echo

exit 0
%%%&&&reply.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@resistor-inventory.sh@@@!!!************************************************************************************
#!/bin/bash
# resistor-inventory.sh
# 使用间接变量引用的简单数据库应用.

# ============================================================== #
# 数据

B1723_value=470                                   # 欧姆
B1723_powerdissip=.25                             # 瓦特
B1723_colorcode="yellow-violet-brown"             # 颜色
B1723_loc=173                                     # 位置
B1723_inventory=78                                # 数量

B1724_value=1000
B1724_powerdissip=.25
B1724_colorcode="brown-black-red"
B1724_loc=24N
B1724_inventory=243

B1725_value=10000
B1725_powerdissip=.25
B1725_colorcode="brown-black-orange"
B1725_loc=24N
B1725_inventory=89

# ============================================================== #


echo

PS3='Enter catalog number: '

echo

select catalog_number in "B1723" "B1724" "B1725"
do
  Inv=${catalog_number}_inventory
  Val=${catalog_number}_value
  Pdissip=${catalog_number}_powerdissip
  Loc=${catalog_number}_loc
  Ccode=${catalog_number}_colorcode

  echo
  echo "Catalog number $catalog_number:"
  echo "There are ${!Inv} of [${!Val} ohm / ${!Pdissip} watt] resistors in stock."
  echo "These are located in bin # ${!Loc}."
  echo "Their color code is /"${!Ccode}/"."

  break
done

echo; echo

# 练习:
# -----
# 1) 重写脚本, 使其从外部文件读取数据.
# 2) 重写脚本,
#+   用数组来代替间接变量引用,
#    因为使用数组更简单, 更易懂.


# 注:
# ---
#  除了最简单的数据库应用, 事实上, Shell脚本本身并不适合于数据库应用.
#+ 因为它太依赖于工作环境和机器的运算能力.
#  更好的办法还是使用支持数据结构的本地语言,
#+ 比如C++或者Java(或者甚至可以是Perl).

exit 0
%%%&&&resistor-inventory.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@restricted.sh@@@!!!************************************************************************************
#!/bin/bash

#  脚本开头以"#!/bin/bash -r"来调用,
#+ 会使整个脚本在受限模式下运行.

echo

echo "Changing directory."
cd /usr/local
echo "Now in `pwd`"
echo "Coming back home."
cd
echo "Now in `pwd`"
echo

# 非受限的模式下,所有操作都正常.

set -r
# set --restricted    也具有相同的效果.
echo "==> Now in restricted mode. <=="

echo
echo

echo "Attempting directory change in restricted mode."
cd ..
echo "Still in `pwd`"

echo
echo

echo "/$SHELL = $SHELL"
echo "Attempting to change shell in restricted mode."
SHELL="/bin/ash"
echo
echo "/$SHELL= $SHELL"

echo
echo

echo "Attempting to redirect output in restricted mode."
ls -l /usr/bin > bin.files
ls -l bin.files    # 尝试列出刚才创建的文件.

echo

exit 0
%%%&&&restricted.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@return-test.sh@@@!!!************************************************************************************
#!/bin/bash
# return-test.sh

# 函数所能返回的最大正整数为255.

return_test ()         # 传给函数什么值, 就返回什么值.
{
  return $1
}

return_test 27         # o.k.
echo $?                # 返回27.
 
return_test 255        # 依然是o.k.
echo $?                # 返回255.

return_test 257        # 错误!
echo $?                # 返回1 (对应各种错误的返回码).

# ======================================================
return_test -151896    # 能返回一个大负数么?
echo $?                # 能否返回-151896?
                       # 显然不行! 只返回了168.
#  Bash 2.05b以前的版本
#+ 允许返回大负数.
#  Bash的新版本(2.05b之后)修正了这个漏洞.
#  这可能会影响以前所编写的脚本.
#  一定要小心!
# ======================================================

exit 0
%%%&&&return-test.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@revposparams.sh@@@!!!************************************************************************************
#!/bin/bash
# revposparams.sh: 反转位置参数.
# 本脚本由Dan Jacobson所编写, 本书作者做了一些格式上的修正.


set a/ b c d/ e;
#     ^      ^     转义的空格
#       ^ ^        未转义的空格
OIFS=$IFS; IFS=:;
#              ^   保存旧的IFS, 然后设置新的IFS.

echo

until [ $# -eq 0 ]
do          #      步进位置参数.
  echo "### k0 = "$k""     # 步进之前
  k=$1:$k;  #      将每个位置参数都附加在循环变量的后边.
#     ^
  echo "### k = "$k""      # 步进之后
  echo
  shift;
done

set $k  #  设置一个新的位置参数.
echo -
echo $# #  察看位置参数的个数.
echo -
echo

for i   #  省略 "in list" 结构,
        #+ 为位置参数设置变量 -- i --.
do
  echo $i  # 显示新的位置参数.
done

IFS=$OIFS  # 恢复 IFS.

#  问题:
#  是否有必要设置新的IFS, 内部域分隔符,
#+ 才能够让这个脚本正常运行? (译者注: 当然有必要.)
#  如果你没设置新的IFS, 会发生什么? 试一下.
#  并且, 在第17行, 为什么新的IFS要使用 -- 一个冒号 -- ,
#+ 来将位置参数附加到循环变量中?
#  这么做的目的是什么?

exit 0

$ ./revposparams.sh

### k0 =
### k = a b

### k0 = a b
### k = c a b

### k0 = c a b
### k = d e c a b

-
3
-

d e
c
a b
%%%&&&revposparams.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@rfe.sh@@@!!!************************************************************************************
#!/bin/bash
# rfe.sh: 修改文件扩展名.
#
# 用法:  rfe old_extension new_extension
#
# 示例:
# 将指定目录中所有的*.gif文件都重命名为*.jpg,
# 用法:  rfe gif jpg


E_BADARGS=65

case $# in
  0|1)             # 竖线"|"在这里表示"或"操作.
  echo "Usage: `basename $0` old_file_suffix new_file_suffix"
  exit $E_BADARGS  # 如果只有0个或1个参数的话, 那么就退出脚本.
  ;;
esac


for filename in *.$1
# 以第一个参数为扩展名的全部文件的列表.
do
  mv $filename ${filename%$1}$2
  #  把筛选出来的文件的扩展名去掉, 因为筛选出来的文件的扩展名都是第一个参数,
  #+ 然后把第2个参数作为扩展名, 附加到这些文件的后边.
done

exit 0
%%%&&&rfe.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@rnd.sh@@@!!!************************************************************************************
#!/bin/bash
# rnd.sh: 输出一个10进制随机数

# 由Stephane Chazelas所编写的这个脚本.

head -c4 /dev/urandom | od -N4 -tu4 | sed -ne '1s/.* //p'


# =================================================================== #

# 分析
# ----

# head:
# -c4 选项将取得前4个字节.
                                              
# od:
# -N4 选项将限制输出为4个字节.
# -tu4 选项将使用无符号10进制格式来输出.
                                              
# sed:
# -n 选项, 使用"s"命令与"p"标志组合的方式,
# 将会只输出匹配的行.
                                              
                                              
                                              
# 本脚本作者解释'sed'命令的行为如下.

# head -c4 /dev/urandom | od -N4 -tu4 | sed -ne '1s/.* //p'
# ----------------------------------> |

# 假设一直处理到"sed"命令时的输出--> |
# 为 0000000 1198195154/n
                                                                                 
#  sed命令开始读取字串: 0000000 1198195154/n.
#  这里它发现一个换行符,
#+ 所以sed准备处理第一行 (0000000 1198195154).
#  sed命令开始匹配它的<range>和<action>. 第一个匹配的并且只有这一个匹配的:
                                                                                 
#   range     action
#   1         s/.* //p
                                                                                 
#  因为行号在range中, 所以sed开始执行action:
#+ 替换掉以空格结束的最长的字符串, 在这行中这个字符串是
#  ("0000000 "), 用空字符串(//)将这个匹配到的字串替换掉, 如果成功, 那就打印出结果
#  ("p"在这里是"s"命令的标志, 这与单独的"p"命令是不同的).
                                                                                 
#  sed命令现在开始继续读取输入. (注意在继续之前,
#+ continuing, 如果没使用-n选项的话, sed命令将再次
#+ 将这行打印一遍).
                                                                                 
# 现在, sed命令读取剩余的字符串, 并且找到文件的结尾.
# sed命令开始处理第2行(这行也被标记为'$'
# 因为这已经是最后一行).
# 所以这行没被匹配到<range>中, 这样sed命令就结束了.
                                                                                 
#  这个sed命令的简短的解释是:
#  "在第一行中删除第一个空格左边全部的字符,
#+ 然后打印出来."
                                                                                 
# 一个更好的来达到这个目的的方法是:
#           sed -e 's/.* //;q'
                                                                                 
# 这里, <range>和<action>分别是(也可以写成
#           sed -e 's/.* //' -e q):
                                                                                 
#   range                    action
#   nothing (matches line)   s/.* //
#   nothing (matches line)   q (quit)
                                                                                 
#  这里, sed命令只会读取第一行的输入.
#  将会执行2个命令, 并且会在退出之前打印出(已经替换过的)这行(因为"q" action),
#+ 因为没使用"-n"选项.
                                                                                 
# =================================================================== #
                                                                                 
# 也可以使用如下一个更简单的语句来代替:
#           head -c4 /dev/urandom| od -An -tu4

exit 0
%%%&&&rnd.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@rn.sh@@@!!!************************************************************************************
#! /bin/bash
#
# Very simpleminded filename "rename" utility (based on "lowercase.sh").
#
#  The "ren" utility, by Vladimir Lanin ([email protected]),
#+ does a much better job of this.


ARGS=2
E_BADARGS=65
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 0


# Exercises:
# ---------
# What type of files will this not work on?
# How can this be fixed?
#
#  Rewrite this script to process all the files in a directory
#+ containing spaces in their names, and to rename them,
#+ substituting an underscore for each space.
%%%&&&rn.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@rot13a.sh@@@!!!************************************************************************************
#!/bin/bash
# rot13a.sh: 与"rot13.sh"脚本相同, 但是会将输出写到"安全"文件中.

# 用法: ./rot13a.sh filename
# 或     ./rot13a.sh <filename
# 或     ./rot13a.sh同时提供键盘输入(stdin)

umask 177               #  文件创建掩码.
                        #  被这个脚本所创建的文件
                        #+ 将具有600权限.

OUTFILE=decrypted.txt   #  结果保存在"decrypted.txt"中
                        #+ 这个文件只能够被
                        #  这个脚本的调用者(或者root)所读写.

cat "$@" | tr 'a-zA-Z' 'n-za-mN-ZA-M' > $OUTFILE
#    ^^ 从stdin 或文件中输入.         ^^^^^^^^^^ 输出重定向到文件中.

exit 0
%%%&&&rot13a.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@rot13.sh@@@!!!************************************************************************************
#!/bin/bash
# rot13.sh: 典型的rot13算法,
#           使用这种方法加密至少可以愚弄一下3岁小孩.

# 用法: ./rot13.sh filename
# 或     ./rot13.sh <filename
# 或     ./rot13.sh and supply keyboard input (stdin)

cat "$@" | tr 'a-zA-Z' 'n-za-mN-ZA-M'   # "a"变为"n", "b"变为"o", 等等.
#  'cat "$@"'结构
#+ 允许从stdin或者从文件中获得输入.

exit 0
%%%&&&rot13.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@rot14.sh@@@!!!************************************************************************************
#!/bin/bash
# 使用'eval'的一个"rot13"的版本,(译者:rot13就是把26个字母,从中间分为2半,各13个).
# 与脚本"rot13.sh" 比较一下.

setvar_rot_13()              # "rot13" 函数
{
  local varname=$1 varvalue=$2
  eval $varname='$(echo "$varvalue" | tr a-z n-za-m)'
}


setvar_rot_13 var "foobar"   # 将 "foobar" 传递到 rot13函数中.
echo $var                    # sbbone

setvar_rot_13 var "$var"     # 传递 "sbbone" 到rot13函数中.
                             # 又变成了原始值.
echo $var                    # foobar

# 这个例子是Segebart Chazelas编写的.
# 作者又修改了一下.

exit 0
%%%&&&rot14.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@rpm-check.sh@@@!!!************************************************************************************
#!/bin/bash
# rpm-check.sh

# 这个脚本的目的是为了描述, 列表, 和确定是否可以安装一个rpm包.
# 在一个文件中保存输出.
#
# 这个脚本使用一个代码块来展示.

SUCCESS=0
E_NOARGS=65

if [ -z "$1" ]
then
  echo "Usage: `basename $0` rpm-file"
  exit $E_NOARGS
fi 

{
  echo
  echo "Archive Description:"
  rpm -qpi $1       # 查询说明.
  echo
  echo "Archive Listing:"
  rpm -qpl $1       # 查询列表.
  echo
  rpm -i --test $1  # 查询rpm包是否可以被安装.
  if [ "$?" -eq $SUCCESS ]
  then
    echo "$1 can be installed."
  else
    echo "$1 cannot be installed."
  fi 
  echo
} > "$1.test"       # 把代码块中的所有输出都重定向到文件中.

echo "Results of rpm test in file $1.test"

# 查看rpm的man页来查看rpm的选项.

exit 0
%%%&&&rpm-check.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@script-array.sh@@@!!!************************************************************************************
#!/bin/bash
# script-array.sh: 将这个脚本的内容赋值给数组.
# 这个脚本的灵感来自于Chris Martin的e-mail(感谢!).

script_contents=( $(cat "$0") )  #  将这个脚本的内容($0)
                                 #+ 赋值给数组.

for element in $(seq 0 $((${#script_contents[@]} - 1)))
  do                #  ${#script_contents[@]}
                    #+ 表示数组元素的个数.
                    #
                    #  一个小问题:
                    #  为什么必须使用seq 0?
                    #  用seq 1来试一下.
  echo -n "${script_contents[$element]}"
                    # 在同一行上显示脚本中每个域的内容.
  echo -n " -- "    # 使用 " -- " 作为域分割符.
done

echo

exit 0

# 练习:
# -----
#  修改这个脚本,
#+ 让这个脚本能够按照它原本的格式输出,
#+ 连同空白, 换行, 等等.
%%%&&&script-array.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@script-detector.sh@@@!!!************************************************************************************
#!/bin/bash
# script-detector.sh: 在一个目录中检查所有的脚本文件.

TESTCHARS=2    # 测试前两个字符.
SHABANG='#!'   # 脚本都是以"#!"开头的.

for file in *  # 遍历当前目录下的所有文件.
do
  if [[ `head -c$TESTCHARS "$file"` = "$SHABANG" ]]
  #      head -c2                      #!
  #  '-c' 选项将从文件头输出指定个数的字符,
  #+ 而不是默认的行数.
  then
    echo "File /"$file/" is a script."
  else
    echo "File /"$file/" is *not* a script."
  fi
done
 
exit 0

#  练习:
#  -----
#  1) 修改这个脚本,
#+    让它可以指定扫描的路径.
#+    (而不是只搜索当前目录).
#
#  2) 以这个脚本目前的状况, 它不能正确识别出
#+    Perl, awk, 和其他一些脚本语言的脚本文件.
#     修正这个问题.
%%%&&&script-detector.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@seconds.sh@@@!!!************************************************************************************
#!/bin/bash

TIME_LIMIT=10
INTERVAL=1

echo
echo "Hit Control-C to exit before $TIME_LIMIT seconds."
echo

while [ "$SECONDS" -le "$TIME_LIMIT" ]
do
  if [ "$SECONDS" -eq 1 ]
  then
    units=second
  else 
    units=seconds
  fi

  echo "This script has been running $SECONDS $units."
  #  在一台比较慢或者是附载过大的机器上,
  #+ 在单次循环中, 脚本可能会忽略计数.
  sleep $INTERVAL
done

echo -e "/a"  # Beep!(哔哔声!)

exit 0
%%%&&&seconds.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@secret-pw.sh@@@!!!************************************************************************************
#!/bin/bash
# secret-pw.sh: 保护密码不被显示

echo
echo -n "Enter password "
read passwd
echo "password is $passwd"
echo -n "If someone had been looking over your shoulder, "
echo "your password would have been compromised."

echo && echo  # 在一个"与列表"中产生两个换行.


stty -echo    # 关闭屏幕的echo.

echo -n "Enter password again "
read passwd
echo
echo "password is $passwd"
echo

stty echo     # 恢复屏幕的echo.

exit 0

# 详细的阅读stty命令的info页, 以便于更好的掌握这个有用并且狡猾的工具.
%%%&&&secret-pw.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


***@@@seeding-random.sh@@@!!!************************************************************************************
#!/bin/bash
# seeding-random.sh: 设置RANDOM变量作为种子.

MAXCOUNT=25       # 决定产生多少个随机数.

random_numbers ()
{
count=0
while [ "$count" -lt "$MAXCOUNT" ]
do
  number=$RANDOM
  echo -n "$number "
  let "count += 1"
done 
}

echo; echo

RANDOM=1          # 为随机数的产生来设置RANDOM种子.
random_numbers

echo; echo

RANDOM=1          # 设置同样的种子...
random_numbers    # ...将会和上边产生的随机序列相同.
                  #
                  # 复制一个相同的"随机"序列在什么情况下有用呢?

echo; echo

RANDOM=2          # 在试一次, 但是这次使用不同的种子...
random_numbers    # 这次将得到一个不同的随机序列.

echo; echo

# RANDOM=$$  使用脚本的进程ID来作为产生随机数的种子.
# 从 'time' 或 'date' 命令中取得RANDOM作为种子也是常用的做法.

# 一个很有想象力的方法...
SEED=$(head -1 /dev/urandom | od -N 1 | awk '{ print $2 }')
#  首先从/dev/urandom(系统伪随机设备文件)中取出一行,
#+ 然后将这个可打印行转换为8进制数, 使用"od"命令来转换.
#+ 最后使用"awk"来获得一个数,
#+ 这个数将作为产生随机数的种子.
RANDOM=$SEED
random_numbers

echo; echo

exit 0
%%%&&&seeding-random.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

 

你可能感兴趣的:(330pics shell scripts_fifth)