Google

一些shell编程的例子

2007-06-21 22:29 来源: bbs.chinaunix.net 作者:ddie 网友评论 0 条 浏览次数 974

Example 2-1 清除:清除/var/log下的log文件
################################Start Script#######################################
# Cleanup
# 当然要使用root身份来运行这个脚本

cd /var/log
cat /dev/null > messages
cat /dev/null > wtmp
echo "Logs cleaned up."
################################End Script#########################################
Example 2-2 清除:一个改良的清除脚本
################################Start Script#######################################
#!/bin/bash
# 一个Bash脚本的正确的开头部分.

# Cleanup, 版本 2

# 当然要使用root身份来运行.
# 在此处插入代码,来打印错误消息,并且在不是root身份的时候退出.

LOG_DIR=/var/log
# 如果使用变量,当然比把代码写死的好.
cd $LOG_DIR

cat /dev/null > messages
cat /dev/null > wtmp


echo "Logs cleaned up."

exit # 这个命令是一种正确并且合适的退出脚本的方法.
################################End Script#########################################
Example 2-3. cleanup:一个增强的和广义的删除logfile的脚本
################################Start Script#######################################
#!/bin/bash
# 清除, 版本 3

#  Warning:
#  -------
#  这个脚本有好多特征,这些特征是在后边章节进行解释的,大概是进行到本书的一半的
#  时候,
#  你就会觉得它没有什么神秘的了.
#

 

LOG_DIR=/var/log
ROOT_UID=0     # $UID为0的时候,用户才具有根用户的权限
LINES=50       # 默认的保存行数
E_XCD=66       # 不能修改目录?
E_NOTROOT=67   # 非根用户将以error退出


# 当然要使用根用户来运行
if [ "$UID" -ne "$ROOT_UID" ]
then
  echo "Must be root to run this script."
  exit $E_NOTROOT
fi 

if [ -n "$1" ]
# 测试是否有命令行参数(非空).
then
  lines=$1
else 
  lines=$LINES # 默认,如果不在命令行中指定
fi 


#  Stephane Chazelas 建议使用下边
#+ 的更好方法来检测命令行参数.
#+ 但对于这章来说还是有点超前.
#
#    E_WRONGARGS=65  # 非数值参数(错误的参数格式)
#
#    case "$1" in
#    ""      ) lines=50;;
#    *[!0-9]*) echo "Usage: `basename $0` file-to-cleanup"; exit $E_WRONGARGS;;
#    *       ) lines=$1;;
#    esac
#
#* 直到"Loops"的章节才会对上边的内容进行详细的描述.


cd $LOG_DIR

if [ `pwd` != "$LOG_DIR" ]  # 或者 if[ "$PWD" != "$LOG_DIR" ]
                            # 不在 /var/log中?
then
  echo "Can't change to $LOG_DIR."
  exit $E_XCD
fi  # 在处理log file之前,再确认一遍当前目录是否正确.

# 更有效率的做法是
#
# cd /var/log || {
#   echo "Cannot change to necessary directory." >&2
#   exit $E_XCD;
# }

 


tail -$lines messages > mesg.temp # 保存log file消息的最后部分.
mv mesg.temp messages             # 变为新的log目录.


# cat /dev/null > messages
#* 不再需要了,使用上边的方法更安全.

cat /dev/null > wtmp  #  ': > wtmp' 和 '> wtmp'具有相同的作用
echo "Logs cleaned up."

exit 0
#  退出之前返回0,返回0表示成功.
#
################################End Script#########################################
因为你可能希望将系统log全部消灭,这个版本留下了log消息最后的部分.你将不断地找到新
的方法来完善这个脚本,并提高效率.

要注意,在每个脚本的开头都使用"#!",这意味着告诉你的系统这个文件的执行需要指定一个解
释器.#!实际上是一个2字节[1]的魔法数字,这是指定一个文件类型的特殊标记, 换句话说, 在
这种情况下,指的就是一个可执行的脚本(键入man magic来获得关于这个迷人话题的更多详细
信息).在#!之后接着是一个路径名.这个路径名指定了一个解释脚本中命令的程序,这个程序可
以是shell,程序语言或者是任意一个通用程序.这个指定的程序从头开始解释并且执行脚本中
的命令(从#!行下边的一行开始),忽略注释.[2]
如:
#!/bin/sh
#!/bin/bash
#!/usr/bin/perl
#!/usr/bin/tcl
#!/bin/sed -f
#!/usr/awk -f

上边每一个脚本头的行都指定了一个不同的命令解释器,如果是/bin/sh,那么就是默认shell
(在Linux系统中默认是Bash).[3]使用#!/bin/sh,在大多数商业发行的UNIX上,默认是Bourne
shell,这将让你的脚本可以正常的运行在非Linux机器上,虽然这将会牺牲Bash一些独特的特征.
脚本将与POSIX[4] 的sh标准相一致.

注意: #! 后边给出的路径名必须是正确的,否则将会出现一个错误消息,通常是
"Command not found",这将是你运行这个脚本时所得到的唯一结果.

当然"#!"也可以被忽略,不过这样你的脚本文件就只能是一些命令的集合,不能够使用shell内建
的指令了,如果不能使用变量的话,当然这也就失去了脚本编程的意义了.

        注意:这个例子鼓励你使用模块化的方式来编写脚本,平时也要注意收集一些零碎的代码,
                这些零碎的代码可能用在你将来编写的脚本中.这样你就可以通过这些代码片段来构
  造一个较大的工程用例. 以下边脚本作为序,来测试脚本被调用的参数是否正确.
################################Start Script#######################################
E_WRONG_ARGS=65
script_parameters="-a -h -m -z"
#                  -a = all, -h = help, 等等.

if [ $# -ne $Number_of_expected_args ]
then
  echo "Usage: `basename $0` $script_parameters"
  # `basename $0`是这个脚本的文件名
  exit $E_WRONG_ARGS
fi
################################End Script#########################################
Example 3-1. 代码块和I/O重定向
################################Start Script#######################################
#!/bin/bash
# 从 /etc/fstab中读行

File=/etc/fstab

{
read line1
read line2
} < $File

echo "First line in $File is:"
echo "$line1"
echo
echo "Second line in $File is:"
echo "$line2"

exit 0

# 现在,你怎么分析每行的分割域
# 暗示: 使用 awk.
################################End Script#########################################
Example 3-2. 将一个代码块的结果保存到文件
################################Start Script#######################################
#!/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
################################End Script#########################################
                注意: 与()中的命令不同的是,{}中的代码块将不能正常地开启一个新shell.[2]

{} \;   路径名.一般都在find命令中使用.这不是一个shell内建命令.
                注意: ";"用来结束find命令序列的-exec选项.

[]              test.
                test的表达式将在[]中.
                值得注意的是[是shell内建test命令的一部分,并不是/usr/bin/test中的扩展命令
                的一个连接.

[[]]    test.
                test表达式放在[[]]中.(shell关键字)
                具体查看[[]]结构的讨论.

[]              数组元素
                Array[1]=slot_1
                echo ${Array[1]}

[]              字符范围
                在正则表达式中使用,作为字符匹配的一个范围

(())    数学计算的扩展
                在(())结构中可以使用一些数字计算.
                具体参阅((...))结构.

>&>>&>><
                重定向.
                scriptname >filename 重定向脚本的输出到文件中.覆盖文件原有内容.
                command &>filename 重定向stdout和stderr到文件中
                command >&2     重定向command的stdout到stderr
                scriptname >>filename 重定向脚本的输出到文件中.添加到文件尾端,如果没有文件,
                则创建这个文件.

                进程替换,具体见"进程替换部分",跟命令替换极其类似.
                (command)>
                <(command)

                <和> 可用来做字符串比较
                <和> 可用在数学计算比较

<<              重定向,用在"here document"

<<<             重定向,用在"here string"

<,>             ASCII比较
                 1 veg1=carrots
                 2 veg2=tomatoes
                 3
                 4 if [[ "$veg1" < "$veg2" ]]
                 5 then
                 6   echo "Although $veg1 precede $veg2 in the dictionary,"
                 7   echo "this implies nothing about my culinary preferences."
                 8 else
                 9   echo "What kind of dictionary are you using, anyhow?"
                10 fi

\<,\>   正则表达式中的单词边界.如:
                bash$grep '\<the\>' textfile

|               管道.分析前边命令的输出,并将输出作为后边命令的输入.这是一种产生命令链的
                好方法.
                1 echo ls -l | sh
                2 #  传递"echo ls -l"的输出到shell中,
                3 #+ 与一个简单的"ls -l"结果相同.
                4
                5
                6 cat *.lst | sort | uniq
                7 # 合并和排序所有的".lst"文件,然后删除所有重复的行.

                管道是进程间通讯的一个典型办法,将一个进程的stdout放到另一个进程的stdin中.
                标准的方法是将一个一般命令的输出,比如cat或echo,传递到一个过滤命令中(在这个
                过滤命令中将处理输入),得到结果,如:
                cat $filename1 | $filename2 | grep $search_word

  当然输出的命令也可以传递到脚本中.如:
################################Start Script#######################################
#!/bin/bash
# uppercase.sh : 修改输出,全部转换为大写

tr 'a-z' 'A-Z'
#  字符范围必须被""引用起来
#+ 来阻止产生单字符的文件名.

exit 0
################################End Script#########################################
Example 3-3. 在后台运行一个循环
################################Start Script#######################################
#!/bin/bash
#background-loop.sh
 3
for i in 1 2 3 4 5 6 7 8 9 10    #第一个循环
do
 6 echo -n "$i"
done&      #在后台运行这个循环
 8        #在第2个循环之后,将在某些时候执行.

echo #这个'echo'某些时候将不会显示.

for i in 11 12 13 14 15 16 17 18 19 20  #第二个循环
do
 echo -n "$i"
done
16
echo #这个'echo'某些时候将不会显示.
18
#--------------------------------------------------------

#期望的输出应该是
#1 2 3 4 5 6 7 8 9 10
#11 12 13 14 15 16 17 18 19 20

#然而实际的结果有可能是
#11 12 13 14 15 16 17 18 19 20
#1 2 3 4 5 6 7 8 9 10 bozo $
#(第2个'echo'没执行,为什么?)

#也可能是
#1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
#(第1个'echo'没执行,为什么?)

#非常少见的执行结果,也有可能是:
#11 12 13 1 2 3 4 5 6 7 8 9 10 14 15 16 17 18 19 20
#前台的循环先于后台的执行

exit 0

# Nasimuddin Ansari 建议加一句 sleep 1
#+ 在 6行和14行的 echo -n "$i"之后加
#+ 将看到一些乐趣
################################End Script#########################################
                注意:在一个脚本内后台运行一个命令,有可能造成这个脚本的挂起,等待一个按键
                        响应.幸运的是,我们可以在Example 11-24附近,看到这个问题的解决办法.

&&              与-逻辑操作.

-               选项,前缀.在所有的命令内如果想使用选项参数的话,前边都要加上"-".

                COMMAND -[Option1][Option2][...]
                ls -al
                sort -dfu $filename
                set -- $variable

                 1 if [ $file1 -ot $file2 ]
                 2 then
                 3   echo "File $file1 is older than $file2."
                 4 fi
                 5
                 6 if [ "$a" -eq "$b" ]
                 7 then
                 8   echo "$a is equal to $b."
                 9 fi
                10
                11 if [ "$c" -eq 24 -a "$d" -eq 47 ]
                12 then
                13   echo "$c equals 24 and $d equals 47."
                14 fi
               
-               用于重定向 stdin 或 stdout.

################################Start Script#######################################
(cd /source/directory && tar cf - . ) | (cd /dest/directory && tar xpvf -)
# 从一个目录移动整个目录树到另一个目录
# [courtesy Alan Cox <a.cox@swansea.ac.uk>, with a minor change]

# 1) cd /source/directory    源目录
# 2) &&                      与操作,如果cd命令成功了,那么就执行下边的命令
# 3) tar cf - .              'c'创建一个新文档,'f'后边跟'-'指定目标文件作为stdout
#                            '-'后边的'f'(file)选项,指明作为stdout的目标文件.
#                            并且在当前目录('.')执行.
# 4) |                       管道...
# 5) ( ... )                 一个子shell
# 6) cd /dest/directory      改变当前目录到目标目录.
# 7) &&                      与操作,同上.
# 8) tar xpvf -              'x'解档,'p'保证所有权和文件属性,
#       'v'发完整消息到stdout
#                            'f'后边跟'-',从stdin读取数据
#
#                            注意:'x' 是一个命令, 'p', 'v', 'f' 是选项.
# Whew!

 

# 更优雅的写法应该是
#   cd source/directory
#   tar cf - . | (cd ../dest/directory; tar xpvf -)
#
#     当然也可以这么写:
# cp -a /source/directory/* /dest/directory
#     或者:
# cp -a /source/directory/* /source/directory/.[^.]* /dest/directory
#     如果在/source/directory中有隐藏文件的话.
################################End Script#########################################

################################Start Script#######################################
bunzip2 linux-2.6.13.tar.bz2 | tar xvf -
# --未解压的tar文件--    | --然后把它传递到"tar"中--
# 如果 "tar" 没能够正常的处理"bunzip2",
# 这就需要使用管道来执行2个单独的步骤来完成它.
# 这个练习的目的是解档"bzipped"的kernel源文件.
################################End Script#########################################
Example 3-4. 备份最后一天所有修改的文件.
################################Start Script#######################################
#!/bin/bash

#  在一个"tarball"中(经过tar和gzip处理过的文件)
#+ 备份最后24小时当前目录下d所有修改的文件.

BACKUPFILE=backup-$(date +%m-%d-%Y)
#                 在备份文件中嵌入时间.
#                 Thanks, Joshua Tschida, for the idea.
archive=${1:-$BACKUPFILE}
#  如果在命令行中没有指定备份文件的文件名,
#+ 那么将默认使用"backup-MM-DD-YYYY.tar.gz".

tar cvf - `find . -mtime -1 -type f -print` > $archive.tar
gzip $archive.tar
echo "Directory $PWD backed up in archive file \"$archive.tar.gz\"."


#  Stephane Chazelas指出上边代码,
#+ 如果在发现太多的文件的时候,或者是如果文件
#+ 名包括空格的时候,将执行失败.

# Stephane Chazelas建议使用下边的两种代码之一
# -------------------------------------------------------------------
#   find . -mtime -1 -type f -print0 | xargs -0 tar rvf "$archive.tar"
#      使用gnu版本的find.


#   find . -mtime -1 -type f -exec tar rvf "$archive.tar" '{}' \;
#         对于其他风格的UNIX便于移植,但是比较慢.
# -------------------------------------------------------------------


exit 0
################################End Script#########################################
                注意:以"-"开头的文件名在使用"-"作为重定向操作符的时候,可能会产生问题.
                应该写一个脚本来检查这个问题,并给这个文件加上合适的前缀.如:
                ./-FILENAME, $PWD/-FILENAME,或$PATHNAME/-FILENAME.

                如果变量的值以"-"开头,可能也会引起问题.
                1 var="-n"
                2 echo $var
                3 #具有"echo -n"的效果了,这样什么都不会输出的.

-               之前工作的目录."cd -"将回到之前的工作目录,具体请参考"$OLDPWD"环境变量.
                注意:一定要和之前讨论的重定向功能分开,但是只能依赖上下文区分.

-               算术减号.

=               算术等号,有时也用来比较字符串.
                1 a=28
                2 echo $a   # 28

+               算术加号,也用在正则表达式中.
+               选项,对于特定的命令来说使用"+"来打开特定的选项,用"-"来关闭特定的选项.

%               算术取模运算.也用在正则表达式中.

~               home目录.相当于$HOME变量.~bozo是bozo的home目录,并且ls ~bozo将列出其中的
                内容. ~/就是当前用户的home目录,并且ls ~/将列出其中的内容,如:
                bash$ echo ~bozo
                /home/bozo

                bash$ echo ~
                /home/bozo

                bash$ echo ~/
                /home/bozo/

                bash$ echo ~:
                /home/bozo:

                bash$ echo ~nonexistent-user
                ~nonexistent-user

~+              当前工作目录,相当于$PWD变量.

~-              之前的工作目录,相当于$OLDPWD内部变量.

=~              用于正则表达式,这个操作将在正则表达式匹配部分讲解,只有version3才支持.

^               行首,正则表达式中表示行首."^"定位到行首.


控制字符
                修改终端或文本显示的行为.控制字符以CONTROL + key组合.
                控制字符在脚本中不能正常使用.
                Ctl-B           光标后退,这应该依赖于bash输入的风格,默认是emacs风格的.
                Ctl-C           Break,终止前台工作.
                Ctl-D           从当前shell登出(和exit很像)
                                        "EOF"(文件结束符).这也能从stdin中终止输入.
                                        在console或者在xterm window中输入的时候,Ctl-D将删除光标下字符.
                                        当没有字符时,Ctrl-D将退出当前会话.在xterm window也有关闭窗口
                                        的效果.
                Ctl-G           beep.在一些老的终端,将响铃.
                Ctl-H           backspace,删除光标前边的字符.如:
                                         1 #!/bin/bash
                                         2 # 在一个变量中插入Ctl-H
                                         3
                                         4 a="^H^H"                  # 两个 Ctl-H (backspaces).
                                         5 echo "abcdef"             # abcdef
                                         6 echo -n "abcdef$a "       # abcd f
                                         7 # 注意结尾的空格 ^              ^ 两个 twice.
                                         8 echo -n "abcdef$a"        # abcdef
                                         9 #  结尾没有空格             没有 backspace 的效果了(why?).
                                        10                           # 结果并不像期望的那样
                                        11 echo; echo
                Ctl-I           就是tab键.
                Ctl-J           新行.
                Ctl-K           垂直tab.(垂直tab?新颖,没听过)
                                        作用就是删除光标到行尾的字符.
                Ctl-L           clear,清屏.
  Ctl-M  回车
################################Start Script#######################################
#!/bin/bash
# Thank you, Lee Maschmeyer, for this example.

read -n 1 -s -p $'Control-M leaves cursor at beginning of this line. Press Enter. \x0d'
                                  #当然,'0d'就是二进制的回车.
echo >&2   #  '-s'参数使得任何输入都不将回显出来
           #+ 所以,明确的重起一行是必要的.

read -n 1 -s -p $'Control-J leaves cursor on next line. \x0a'
echo >&2   #  Control-J 是换行.

###

read -n 1 -s -p $'And Control-K\x0bgoes straight down.'
echo >&2   #  Control-K 是垂直制表符.

# 关于垂直制表符效果的一个更好的例子见下边:

var=$'\x0aThis is the bottom line\x0bThis is the top line\x0a'
echo "$var"
#  这句与上边的例子使用的是同样的办法,然而:
echo "$var" | col
#  这将造成垂直制表符右边的部分在左边部分的上边.
#  这也解释了为什么我们要在行首和行尾加上一个换行符--
#+ 来避免一个混乱的屏幕输出.

# Lee Maschmeyer的解释:
# ---------------------
#  In the [first vertical tab example] . . . the vertical tab
#  在这里[第一个垂直制表符的例子中] . . . 这个垂直制表符
#+ makes the printing go straight down without a carriage return.
#  This is true only on devices, such as the Linux console,
#+ that can't go "backward."
#  The real purpose of VT is to go straight UP, not down.
#  It can be used to print superscripts on a printer.
#  它可以用来在一个打印机上打印上标.
#  col的作用,可以用来模仿VT的合适的行为.

exit 0
################################End Script#########################################
Example 4-1. 变量赋值和替换
################################Start Script#######################################
#!/bin/bash

# 变量赋值和替换

a=375
hello=$a

#-------------------------------------------------------------------------
# 强烈注意,在赋值的前后一定不要有空格.
# 如果有空格会发生什么?

#  如果"VARIABLE =value",
#              ^
#+ 脚本将尝试运行一个"VARIABLE"的命令,带着一个"=value"参数.

#  如果"VARIABLE= value",
#               ^
#+ script tries to run "value" command with
#+ 脚本将尝试运行一个"value"的命令,带着
#+ the environmental variable "VARIABLE" set to "".
#+ 一个被赋成""值的环境变量"VARIABLE".
#-------------------------------------------------------------------------


echo hello    # 没有变量引用,不过是个hello字符串

echo $hello
echo ${hello} # 同上

echo "$hello"
echo "${hello}"

echo

hello="A B  C   D"
echo $hello   # A B C D
echo "$hello" # A B  C   D
# 就象你看到的echo $hello   和    echo "$hello"   将给出不同的结果.
#                                      ^      ^
# Quoting a variable preserves whitespace.
# 引用一个变量将保留其中的空白,当然,如果是变量替换就不会保留了.

echo

echo '$hello'  # $hello
#    ^      ^
# 全引用的作用
#+ 将导致"$"变成一个单独的字符.

# 注意两种引用不同的效果


hello=    # 设置为空值
echo "\$hello (null value) = $hello"
#  注意设置一个变量为空,与unset它,不是一回事,虽然看起来一样
#

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

#  可以在同一行上设置多个变量.
#+ 要以空白分隔
#  小心,这会降低可读性,和可移植性.

var1=21  var2=22  var3=$V3
echo
echo "var1=$var1   var2=$var2   var3=$var3"

# 在老版本的"sh"上,可能会有问题.

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

echo; echo

numbers="one two three"
#           ^   ^
other_numbers="1 2 3"
#               ^ ^
#  如果变量中有空白,那么引用就必要了.
#
echo "numbers = $numbers"
echo "other_numbers = $other_numbers"   # other_numbers = 1 2 3
echo

echo "uninitialized_variable = $uninitialized_variable"
# Uninitialized变量为空值(根本就没赋值).
uninitialized_variable=   #  声明,但是没被初始化
                          #+ 其实和前边设置为空值得作用是一样的.
echo "uninitialized_variable = $uninitialized_variable"
                          # 还是一个空值

uninitialized_variable=23       # 赋值
unset uninitialized_variable    # Unset it.
echo "uninitialized_variable = $uninitialized_variable"
                                # 还是空值
echo

exit 0
################################End Script#########################################
注意: 一个空值变量,或者是根本就没声明的变量,在赋值之前使用它可能会引起问题.
  但是还是可以用来做算术运算
################################Start Script#######################################
echo "$uninitialized"                                # (blank line)
let "uninitialized += 5"                             # Add 5 to it.
echo "$uninitialized"                                # 5

#  结论:
#  对于一个空值变量在做算术操作的时候,就好像它的值为0一样.
#  This is undocumented (and probably non-portable) behavior.
#  这并没被文档化(可能是不可移植)的行为.
################################End Script#########################################
Example 4-2. 一般的变量赋值
################################Start Script#######################################
#!/bin/bash
# "裸体"变量

echo

# 变量什么时候是"裸体"的,比如前边少了$的时候.
# 当它被赋值的时候,而不是被引用的时候.

# 赋值
a=879
echo "The value of \"a\" is $a."

# 使用let赋值
let a=16+5
echo "The value of \"a\" is now $a."

echo

# 在for循环中
echo -n "Values of \"a\" in the loop are: "
for a in 7 8 9 11
do
  echo -n "$a "
done

echo
echo

# 在read命令状态中
echo -n "Enter \"a\" "
read a
echo "The value of \"a\" is now $a."

echo

exit 0
################################End Script#########################################
Example 4-3. 变量赋值,一般的和比较特殊的
################################Start Script#######################################
#!/bin/bash

a=23              # Simple case
echo $a
b=$a
echo $b

# 现在让我们来点小变化

a=`echo Hello!`   # 把echo命令的结果传给变量a
echo $a
#  注意,如果在命令扩展结构中使用一个(!)的话,在命令行中将不能工作
#+ 因为这触发了Bash的"历史机制".
#  但是,在校本里边使用的话,历史功能是被关闭的,所以就能够正常运行.
15

a=`ls -l`         # 把ls -l的结果给a
echo $a           # 别忘了,这么引用的话,ls的结果中的所有空白部分都没了(包括换行)
echo
echo "$a"         # 这么引用就正常了,保留了空白
                  # (具体参阅章节"引用")

exit 0
################################End Script#########################################
Example 4-4 整型还是string?
################################Start Script#######################################
#!/bin/bash
# int-or-string.sh: 整形还是string?

a=2334                   # 整型
let "a += 1"
echo "a = $a "           # a = 2335
echo                     # 还是整型


b=${a/23/BB}             # 将23替换成BB
                         # 这将把b变量从整型变为string
echo "b = $b"            # b = BB35
declare -i b             # 即使使用declare命令也不会对此有任何帮助,9.4节有解释
echo "b = $b"            # b = BB35

let "b += 1"             # BB35 + 1 =
echo "b = $b"            # b = 1
echo

c=BB34
echo "c = $c"            # c = BB34
d=${c/BB/23}             # S将BB替换成23
                         # 这使得$d变为一个整形
echo "d = $d"            # d = 2334
let "d += 1"             # 2334 + 1 =
echo "d = $d"            # d = 2335
echo

# 关于空变量怎么样?
e=""
echo "e = $e"            # e =
let "e += 1"             # 算术操作允许一个空变量?
echo "e = $e"            # e = 1
echo                     # 空变量将转换成一个整型变量

# 关于未声明的变量怎么样?
echo "f = $f"            # f =
let "f += 1"             # 算术操作允许么?
echo "f = $f"            # f = 1
echo                     # 未声明的变量将转换成一个整型变量

 

# 所以说Bash中的变量都是无类型的.

exit 0
################################End Script#########################################
Example 4-5 位置参数
################################Start Script#######################################
#!/bin/bash

# 作为用例,调用这个脚本至少需要10个参数,如
# ./scriptname 1 2 3 4 5 6 7 8 9 10
MINPARAMS=10

echo

echo "The name of this script is \"$0\"."
# 添加./是为了当前目录
echo "The name of this script is \"`basename $0`\"."
# 去掉目录信息,具体见'basename'命令

echo

if [ -n "$1" ]              # 测试变量被被引用
then
 echo "Parameter #1 is $1"  # "#"没被转义
fi

if [ -n "$2" ]
then
 echo "Parameter #2 is $2"
fi

if [ -n "$3" ]
then
 echo "Parameter #3 is $3"
fi

# ...


if [ -n "${10}" ]  # 大于9的参数必须出现在{}中.
then
 echo "Parameter #10 is ${10}"
fi

echo "-----------------------------------"
echo "All the command-line parameters are: "$*""

if [ $# -lt "$MINPARAMS" ]  #$#是传到脚本里的位置参数的个数
then
  echo
  echo "This script needs at least $MINPARAMS command-line arguments!"
fi 

echo

exit 0
################################End Script#########################################
                {}标记法是一种很好的使用位置参数的方法.这也需要间接引用(见Example 34-2)
                1 args=$#           # 位置参数的个数
                2 lastarg=${!args}
                3 # 或:       lastarg=${!#}
                4 # 注意 lastarg=${!$#} 将报错

                一些脚本可能会依赖于使用不同的调用名字,而表现出不同的行为,这样一般都需要
                判断$0,而其他的名字都是通过ln命令产生的链接.(具体参见Example 12-2)

                如果脚本需要一个命令行参数,而调用的时候,没用这个参数,这就有可能造成分配一个
                空变量,这样估计就会引起问题.一种解决办法就是在这个位置参数,和相关的变量后
  边,都添加一个额外的字符.具体见下边的例子.
################################Start Script#######################################
variable1_=$1_  # 而不是 variable1=$1
# 这将阻止一个错误,即使在调用时没使用这个位置参数.

critical_argument01=$variable1_

# 这个扩展的字符是可以被消除掉的,就像这样.
variable1=${variable1_/_/}
# 副作用就是$variable1_多了一个下划线
# 这里使用了一个参数替换模版(后边会有具体的讨论)
# (Leaving out the replacement pattern results in a deletion.)
# (在一个删除动作中,节省了一个替换模式)


# 一个解决这种问题的更简单的做法就是,判断一下这个位置参数是否传递下来了
if [ -z $1 ]
then
  exit $E_MISSING_POS_PARAM
fi


#  但是上边的方法将可能产生一个意外的副作用
#  参数替换的更好的办法应该是:
#         ${1:-$DefaultVal}
#  具体察看"Parameter Substition"节
#+ 在第9章
################################End Script#########################################
Example 4-6 wh,whois节点名字查询
################################Start Script#######################################
#!/bin/bash
# ex18.sh

# Does a 'whois domain-name' lookup on any of 3 alternate servers:
#                    ripe.net, cw.net, radb.net

# 把这个脚本重命名为'wh',然后放到/usr/local/bin下

# 需要3个符号链接
# ln -s /usr/local/bin/wh /usr/local/bin/wh-ripe
# ln -s /usr/local/bin/wh /usr/local/bin/wh-cw
# ln -s /usr/local/bin/wh /usr/local/bin/wh-radb

E_NOARGS=65


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

# Check script name and call proper server.
# 检查脚本名字,然后调用合适的服务器
case `basename $0` in    # Or:    case ${0##*/} in
    "wh"     ) whois $1@whois.ripe.net;;
    "wh-ripe") whois $1@whois.ripe.net;;
    "wh-radb") whois $1@whois.radb.net;;
    "wh-cw"  ) whois $1@whois.cw.net;;
    *        ) echo "Usage: `basename $0` [domain-name]";;
esac

exit $?
################################End Script#########################################
Example 4-7 使用shift
################################Start Script#######################################
#!/bin/bash
# 使用'shift'来穿过所有的位置参数.

#  把这个脚本命名为shft,
#+ 并且使用一些参数来调用它,如:
#          ./shft a b c def 23 skidoo

until [ -z "$1" ]  # 知道所有参数都用光
do
  echo -n "$1 "
  shift
done

echo               # 额外的换行.

exit 0
################################End Script#########################################
Example 5-1 echo一些诡异的变量
################################Start Script#######################################
#!/bin/bash
# weirdvars.sh: echo诡异的变量

var="'(]\\{}\$\""
echo $var        # '(]\{}$"
echo "$var"      # '(]\{}$"    并没有什么不同

echo

IFS='\'
echo $var        # '(] {}$"     \ 转换成空格了?明显和IFS有关系么!又不傻!
echo "$var"      # '(]\{}$"

exit 0
################################End Script#########################################
Example 5-2 转义符
################################Start Script#######################################
#!/bin/bash
# escaped.sh: 转义符

echo; echo

echo "\v\v\v\v"      # 逐字的打印\v\v\v\v .
# 使用-e选项的echo命令来打印转义符
echo "============="
echo "VERTICAL TABS"
echo -e "\v\v\v\v"   # Prints 4 vertical tabs.
echo "=============="

echo "QUOTATION MARK"
echo -e "\042"       # 打印" (引号, 8进制的ASCII 码就是42).
echo "=============="

# The $'\X' construct makes the -e option unnecessary.
# 如果使用$'\X'结构,那-e选项就不必要了
echo; echo "NEWLINE AND BEEP"
echo $'\n'           # 新行.
echo $'\a'           # Alert (beep).

echo "==============="
echo "QUOTATION MARKS"
# 版本2以后Bash允许使用$'\nnn'结构
# 注意这种情况,'\nnn\是8进制
echo $'\t \042 \t'   # Quote (") framed by tabs.

# 当然,也可以使用16进制的值,使用$'\xhhh' 结构
echo $'\t \x22 \t'  # Quote (") framed by tabs.

# 早一点的Bash版本允许'\x022'这种形式
echo "==============="
echo


# 分配ASCII字符到变量中
# ---------------------
quote=$'\042'        # \042是",分配到变量中
echo "$quote This is a quoted string, $quote and this lies outside the quotes."

echo

# Concatenating ASCII chars in a variable.
# 变量中的连续的ASCII char.
triple_underline=$'\137\137\137'  # 137 是8进制的ASCII 码'_'.
echo "$triple_underline UNDERLINE $triple_underline"

echo

ABC=$'\101\102\103\010'           # 101, 102, 103 是8进制的码A, B, C.
echo $ABC

echo; echo

escape=$'\033'                    # 033 是8进制码for escape.
echo "\"escape\" echoes as $escape"
#"escape" echoes as                  没有变量被输出

echo; echo

exit 0
################################End Script#########################################
        另一个关于$''字符串扩展结果的例子见Example 34-1

\"              表达引号本身
                1 echo "Hello"                  # Hello
                2 echo "\"Hello\", he said."    # "Hello", he said.

\$              $号本身,跟在\$后的变量名,将不能扩展
                1 echo "\$variable01"  # 结果是$variable01
               
\\              \号本身.
                1 echo "\\"  # 结果是\
                2
                3 # 相反的 . . .
                4
                5 echo "\"   # 这会出现第2个命令提示符,说白了就是提示你命令不全,你再补个"就
                6                        # 好了.如果是在脚本里,就会给出一个错误.

 注意:\的行为依赖于它是否被转义,被"",或者是否在"命令替换"和"here document"中.
################################Start Script#######################################
                      #  简单的转义和""
echo \z               #  z
echo \\z              # \z
echo '\z'             # \z
echo '\\z'            # \\z
echo "\z"             # \z
echo "\\z"            # \z

                      #  命令替换
echo `echo \z`        #  z
echo `echo \\z`       #  z
echo `echo \\\z`      # \z
echo `echo \\\\z`     # \z
echo `echo \\\\\\z`   # \z
echo `echo \\\\\\\z`  # \\z
echo `echo "\z"`      # \z
echo `echo "\\z"`     # \z

                      # Here document
cat <<EOF             
\z                     
EOF                   # \z

cat <<EOF             
\\z                    
EOF                   # \z
################################End Script#########################################
 分配给变量的字符串的元素也会被转义,但是只把一个转义符分配给变量将会报错.
################################Start Script#######################################
variable=\
echo "$variable"
# Will not work - gives an error message:
# 将不能正常工作- 将给出一个错误消息:
# test.sh: : command not found
# 一个"裸体的" 转义符将不能够安全的分配给变量.
#
#  What actually happens here is that the "\" escapes the newline and
#  这里其实真正发生的是variable=\,这句被shell认为是没有完成,\被认为是一个续行符
#+ 这样,下边的这句echo,也被认为是上一行的补充.所以,总的来说就是一个非法变量分配

variable=\
23skidoo
echo "$variable"        #  23skidoo
                        #  这句就可以使用,因为这是一个合法的变量分配

variable=\
#        \^   转义一个空格
echo "$variable"        # 显示空格

variable=\\
echo "$variable"        # \

variable=\\\
echo "$variable"
# 不能正常工作,给出一个错误
# test.sh: \: command not found
#
#  第一个转义符把第2个\转义了,但是第3个又变成"裸体的"了,
#+ 与上边的例子的原因相同

variable=\\\\
echo "$variable"        # \\
                        # 转了两个\
                        # 没问题
################################End Script#########################################
转义一个空格,在命令行参数列表中将会阻止单词分隔问题.
################################Start Script#######################################
file_list="/bin/cat /bin/gzip /bin/more /usr/bin/less /usr/bin/emacs-20.7"
# 列出的文件都作为命令的参数.

# Add two files to the list, and list all.
# 加2个文件到list中,并且列出全部.
ls -l /usr/X11R6/bin/xsetroot /sbin/dump $file_list

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

# 如果我们转义2个空格,会发生什么?
ls -l /usr/X11R6/bin/xsetroot\ /sbin/dump\ $file_list
# 错误: 因为前3个路径名被合并成一个参数传给了'ls -l'
#        因为2个转义符阻止了参数(单词)分离
################################End Script#########################################

转义符也提供续行功能.一般,每一行都包含一个不同的命令,但如果在行尾加上\,那就会接受
新行的输入,作为这一行的补充.
(cd /source/directory && tar cf - . ) | \
(cd /dest/directory && tar xpvf -)
# 重复了 Alan Cox的目录树拷贝命令
# 为了增加可读性分成2行.
 
# 也可以使用如下方式:
tar cf - -C /source/directory . |
tar xpvf - -C /dest/directory
# 察看下边的注意事项

注意:如果一个脚本以|(管道字符)结束.那么一个\(转义符),就不用非加上不可了.
 但是一个好的shell脚本编写风格,还是应该在行尾加上\,以增加可读性.
################################Start Script#######################################
echo "foo
bar"
#foo
#bar

echo

echo 'foo
bar'    # 没区别
#foo
#bar

echo

echo foo\
bar     # 续行
#foobar

echo

echo "foo\
bar"     # 与上边一样,\还是作为续行符
#foobar

echo

echo 'foo\
bar'     # 由于是强引用,所以\没被解释成续行符
#foo\
#bar
################################End Script#########################################
Example 6-1 exit/exit状态
################################Start Script#######################################
#!/bin/bash

echo hello
echo $?    # 返回0,因为执行成功

lskdf      # 不认识的命令.
echo $?    # 返回非0值,因为失败了.

echo

exit 113   # 将返回113给shell.
           # To verify this, type "echo $?" after script terminates.
           # 为了验证这个,在脚本结束的地方使用"echo $?"
################################End Script#########################################
Example 6-2 否定一个条件使用!
################################Start Script#######################################
true  # true是shell内建命令,什么事都不做,就是shell返回0
echo "exit status of \"true\" = $?"     # 0

! true
echo "exit status of \"! true\" = $?"   # 1
# 注意:"!"需要一个空格
#    !true   将导致一个"command not found"错误
#
# 如果一个命令以'!'开头,那么将使用Bash的历史机制.就是显示这个命令被使用的历史.

true
!true
# 这次就没有错误了.
# 他不过是重复了之前的命令(true).
################################End Script#########################################
Example 7-1 什么情况下为真?
################################Start Script#######################################
  1 #!/bin/bash
  2
  3 #  技巧:
  4 #  如果你不确定一个特定的条件如何判断.
  5 #+ 在一个if-test结构中测试它.
  6
  7 echo
  8
  9 echo "Testing \"0\""
 10 if [ 0 ]      # zero
 11 then
 12   echo "0 is true."
 13 else
 14   echo "0 is false."
 15 fi            # 0 is true.
 16
 17 echo
 18
 19 echo "Testing \"1\""
 20 if [ 1 ]      # one
 21 then
 22   echo "1 is true."
 23 else
 24   echo "1 is false."
 25 fi            # 1 is true.
 26
 27 echo
 28
 29 echo "Testing \"-1\""
 30 if [ -1 ]     # -1
 31 then
 32   echo "-1 is true."
 33 else
 34   echo "-1 is false."
 35 fi            # -1 is true.
 36
 37 echo
 38
 39 echo "Testing \"NULL\""
 40 if [ ]        # NULL (控状态)
 41 then
 42   echo "NULL is true."
 43 else
 44   echo "NULL is false."
 45 fi            # NULL is false.
 46
 47 echo
 48
 49 echo "Testing \"xyz\""
 50 if [ xyz ]    # 字符串
 51 then
 52   echo "Random string is true."
 53 else
 54   echo "Random string is false."
 55 fi            # Random string is true.
 56
 57 echo
 58
 59 echo "Testing \"\$xyz\""
 60 if [ $xyz ]   # 测试$xyz是否为null,但是...(明显没人定义么!)
 61               # 只不过是一个未定义的变量
 62 then
 63   echo "Uninitialized variable is true."
 64 else
 65   echo "Uninitialized variable is false."
 66 fi            # Uninitialized variable is false.
 67
 68 echo
 69
 70 echo "Testing \"-n \$xyz\""
 71 if [ -n "$xyz" ]            # 更学究的的检查
 72 then
 73   echo "Uninitialized variable is true."
 74 else
 75   echo "Uninitialized variable is false."
 76 fi            # Uninitialized variable is false.
 77
 78 echo
 79
 80
 81 xyz=          # 初始化了,但是将其设为空值
 82
 83 echo "Testing \"-n \$xyz\""
 84 if [ -n "$xyz" ]
 85 then
 86   echo "Null variable is true."
 87 else
 88   echo "Null variable is false."
 89 fi            # Null variable is false.
 90
 91
 92 echo
 93
 94
 95 # 什么时候"flase"为true?
 96
 97 echo "Testing \"false\""
 98 if [ "false" ]              #  看起来"false"只不过是个字符串而已.
 99 then
100   echo "\"false\" is true." #+ 并且它test的结果就是true.
101 else
102   echo "\"false\" is false."
103 fi            # "false" is true.
104
105 echo
106
107 echo "Testing \"\$false\""  # 再来一个,未声明的变量
108 if [ "$false" ]
109 then
110   echo "\"\$false\" is true."
111 else
112   echo "\"\$false\" is false."
113 fi            # "$false" is false.
114               # 现在我们终于得到了期望的结果
115
116 #  如果我们test这个变量"$true"会发生什么结果?答案是和"$flase"一样,都为空,因为我
117 #+ 们并没有定义它.
118 echo
119
120 exit 0
################################End Script#########################################
Example 7-2 几个等效命令test,/usr/bin/test,[],和/usr/bin/[
################################Start Script#######################################
  1 #!/bin/bash
  2
  3 echo
  4
  5 if test -z "$1"
  6 then
  7   echo "No command-line arguments."
  8 else
  9   echo "First command-line argument is $1."
 10 fi
 11
 12 echo
 13
 14 if /usr/bin/test -z "$1"      # 与内建的test结果相同
 15 then
 16   echo "No command-line arguments."
 17 else
 18   echo "First command-line argument is $1."
 19 fi
 20
 21 echo
 22
 23 if [ -z "$1" ]                # 与上边代码的作用相同
 24 #   if [ -z "$1"                应该工作,但是...
 25 #+  Bash相应一个缺少关闭中括号的错误消息.
 26 then
 27   echo "No command-line arguments."
 28 else
 29   echo "First command-line argument is $1."
 30 fi
 31
 32 echo
 33
 34
 35 if /usr/bin/[ -z "$1" ]       # 再来一个,与上边代码的作用相同
 36 # if /usr/bin/[ -z "$1"       # 工作,但是给个错误消息
 37 #                             # 注意:
 38 #                               This has been fixed in Bash, version 3.x.
 38 #                               在ver 3.x上,这个bug已经被Bash修正了.
 39 then
 40   echo "No command-line arguments."
 41 else
 42   echo "First command-line argument is $1."
 43 fi
 44
 45 echo
 46
 47 exit 0
###############################End Script#########################################
Example 7-3 算数测试使用(( ))
################################Start Script#######################################
#!/bin/bash
# 算数测试

# The (( ... )) construct evaluates and tests numerical expressions.
# (( ... ))结构计算并测试算数表达式的结果.
# 退出码将与[ ... ]结构相反!

(( 0 ))
echo "Exit status of \"(( 0 ))\" is $?."         # 1

(( 1 ))
echo "Exit status of \"(( 1 ))\" is $?."         # 0

(( 5 > 4 ))                                      # true
echo "Exit status of \"(( 5 > 4 ))\" is $?."     # 0

(( 5 > 9 ))                                      # false
echo "Exit status of \"(( 5 > 9 ))\" is $?."     # 1

(( 5 - 5 ))                                      # 0
echo "Exit status of \"(( 5 - 5 ))\" is $?."     # 1

(( 5 / 4 ))                                      # 除法也行
echo "Exit status of \"(( 5 / 4 ))\" is $?."     # 0

(( 1 / 2 ))                                      # 出发结果<1
echo "Exit status of \"(( 1 / 2 ))\" is $?."     # 结果将为0
                                                 # 1

(( 1 / 0 )) 2>/dev/null                          # 除数为0的错误
#           ^^^^^^^^^^^
echo "Exit status of \"(( 1 / 0 ))\" is $?."     # 1

# What effect does the "2>/dev/null" have?
# "2>/dev/null"的作用是什么?
# 如果删除"2>dev/null"将会发生什么?
# Try removing it, then rerunning the script.
# 尝试删除它,然后再运行脚本.

exit 0
################################End Script#########################################
Example 7-4 test死的链接文件
################################Start Script#######################################
#!/bin/bash
# broken-link.sh
# Written by Lee bigelow <ligelowbee@yahoo.com>
# Used with permission.

#一个真正有用的shell脚本来找出死链接文件并且输出它们的引用
#以便于它们可以被输入到xargs命令中进行处理 :)
#比如: broken-link.sh /somedir /someotherdir|xargs rm
#
#这里,不管怎么说,是一种更好的方法
#
#find "somedir" -type l -print0|\
#xargs -r0 file|\
#grep "broken symbolic"|
#sed -e 's/^\|: *broken symbolic.*$/"/g'
#
#但这不是一个纯粹的bash,最起码现在不是.
#小心:小心/proc文件系统和任何的循环链接文件.
##############################################################


#如果没对这个脚本传递参数,那么就使用当前目录.
#否则就使用传递进来的参数作为目录来搜索.
#
####################
[ $# -eq 0 ] && directorys=`pwd` || directorys=$@

#建立函数linkchk来检查传进来的目录或文件是否是链接和是否存在,
#并且打印出它们的引用
#如果传进来的目录有子目录,
#那么把子目录也发送到linkchk函数中处理,就是递归目录.
##########
linkchk () {
    for element in $1/*; do
    [ -h "$element" -a ! -e "$element" ] && echo \"$element\"
    [ -d "$element" ] && linkchk $element
    # Of course, '-h' tests for symbolic link, '-d' for directory.
    # 当然'-h'是测试链接,'-d'是测试目录.
    done
}

#如果是个可用目录,那就把每个从脚本传递进来的参数都送到linkche函数中.
#如果不是,那就打印出错误消息和使用信息.
#
################
for directory in $directorys; do
    if [ -d $directory ]
 then linkchk $directory
 else
     echo "$directory is not a directory"
     echo "Usage: $0 dir1 dir2 ..."
    fi
done

exit 0
################################End Script#########################################
Example 7-5 数字和字符串比较
################################Start Script#######################################
#!/bin/bash

a=4
b=5

#  这里的变量a和b既可以当作整型也可以当作是字符串.
#  这里在算术比较和字符串比较之间有些混淆,
#+ 因为Bash变量并不是强类型的.

#  Bash允许对整型变量操作和比较
#+ 当然变量中只包含数字字符.
#  但是还是要考虑清楚再做.

echo

if [ "$a" -ne "$b" ]
then
  echo "$a is not equal to $b"
  echo "(arithmetic comparison)"
fi

echo

if [ "$a" != "$b" ]
then
  echo "$a is not equal to $b."
  echo "(string comparison)"
  #     "4"  != "5"
  # ASCII 52 != ASCII 53
fi

# 在这个特定的例子中,"-ne"和"!="都可以.

echo

exit 0
################################End Script#########################################
Example 7-6 测试字符串是否为null
################################Start Script#######################################
#!/bin/bash
#  str-test.sh: 测试null字符串和非引用字符串,
#+ but not strings and sealing wax, not to mention cabbages and kings . . .
#+ 上边这句没看懂
# Using   if [ ... ]


# 如果一个字符串没被初始化,那么它就没有定义的值(像这种话,总感觉像屁话)
# 这种状态叫做"null"(与zero不同)

if [ -n $string1 ]    # $string1 没被声明和初始化
then
  echo "String \"string1\" is not null."
else 
  echo "String \"string1\" is null."
fi 
# 错误的结果.
# 显示$string1为非空,虽然他没被初始化.


echo


# 让我们再试一下.

if [ -n "$string1" ]  # 这次$string1被引用了.
then
  echo "String \"string1\" is not null."
else 
  echo "String \"string1\" is null."
fi                    # ""的字符串在[]结构中


echo


if [ $string1 ]       # 这次$string1变成"裸体"的了
then
  echo "String \"string1\" is not null."
else 
  echo "String \"string1\" is null."
fi 
# 这工作得很好.
# 这个[]test操作检测string是否为null.
# 然而,使用("$string1")是一种很好的习惯
#
# As Stephane Chazelas points out,
#    if [ $string1 ]    有1个参数 "]"
#    if [ "$string1" ]  有2个参数,空的"$string1"和"]"

 

echo

 

string1=initialized

if [ $string1 ]       # 再来,$string1"裸体了"
then
  echo "String \"string1\" is not null."
else 
  echo "String \"string1\" is null."
fi 
# 再来,给出了正确的结果.
# 不过怎么说("$string1")还是好很多,因为. . .


string1="a = b"

if [ $string1 ]       # 再来,$string1 再次裸体了.
then
  echo "String \"string1\" is not null."
else 
  echo "String \"string1\" is null."
fi 
# 非引用的"$string1"现在给出了一个错误的结果!

exit 0
# Thank you, also, Florian Wisser, for the "heads-up".
################################End Script#########################################
Example 7-7 zmore
################################Start Script#######################################
#!/bin/bash
# zmore

#使用'more'来查看gzip文件

NOARGS=65
NOTFOUND=66
NOTGZIP=67

if [ $# -eq 0 ] # 与 if [ -z "$1" ]同样的效果
# 应该是说前边的那句注释有问题,$1是可以存在的,比如:zmore "" arg2 arg3
then
  echo "Usage: `basename $0` filename" >&2
  # 错误消息到stderr
  exit $NOARGS
  # 脚本返回65作为退出码.
fi 

filename=$1

if [ ! -f "$filename" ]   # 将$filename ""起来,来允许可能的空白
then
  echo "File $filename not found!" >&2
   # 错误消息到stderr
  exit $NOTFOUND
fi 

if [ ${filename##*.} != "gz" ]
# 在变量替换中使用中括号
then
  echo "File $1 is not a gzipped file!"
  exit $NOTGZIP
fi 

zcat $1 | more

# 使用过滤命令'more'
# 如果你想的话也可使用'less'


exit $?   # 脚本将返回pipe的结果作为退出码
# 事实上,不用非的有"exit $?",但是不管怎么说,有了这句,能正规一些
# 将最后一句命令的执行状态作为退出码返回
################################End Script#########################################
Example 8-1 最大公约数
################################Start Script#######################################
#!/bin/bash
# gcd.sh: 最大公约数
#         使用Euclid's 算法

#  最大公约数,就是2个数能够同时整除的最大的数.
#

#  Euclid's算法采用连续除法.
#  在每个循环中
#+ 被除数 <---  除数
#+ 除数 <---  余数
#+ 直到余数= 0.
#+ 在最后的循环中The gcd = 被除数
#
#  关于这个算法更精彩的讨论
#  见Jim Loy's site, http://www.jimloy.com/number/euclids.htm.


# ------------------------------------------------------
# 参数检查
ARGS=2
E_BADARGS=65

if [ $# -ne "$ARGS" ]
then
  echo "Usage: `basename $0` first-number second-number"
  exit $E_BADARGS
fi
# ------------------------------------------------------


gcd ()
{

  dividend=$1                    #  随便给值
  divisor=$2                     #+ 即使$2大,也没关系.
                                 #  Why not?

  remainder=1                    #  如果再循环中使用为初始化的变量.
                                 #+ 那将在第一次循环中产生一个错误消息.
                                

  until [ "$remainder" -eq 0 ]
  do
    let "remainder = $dividend % $divisor"
    dividend=$divisor            # 现在使用2个最小的数重复.
    divisor=$remainder
  done                           # Euclid's algorithm

}                                # Last $dividend is the gcd.
}                                # 最后的$dividend就是gcd.


gcd $1 $2

echo; echo "GCD of $1 and $2 = $dividend"; echo


# 练习:
# --------
#  检查命令行参数来确定它们都是整数,
#+ and exit the script with an appropriate error message if not.
#+ 否则就选择合适的错误消息退出.

exit 0
################################End Script#########################################
Example 8-2 使用算术操作符
################################Start Script#######################################
#!/bin/bash
# Counting to 11 in 10 different ways.

n=1; echo -n "$n "

let "n = $n + 1"   # let "n = n + 1"  这么写也行
echo -n "$n "


: $((n = $n + 1))
#  ":" 是必须的,这是因为,如果没有":"的话,Bash将
#+ 尝试把"$((n = $n + 1))"解释成一个命令
echo -n "$n "

(( n = n + 1 ))
#  对于上边的方法的一个更简单的选则.
#  Thanks, David Lombard, for pointing this out.
echo -n "$n "

n=$(($n + 1))
echo -n "$n "

: $[ n = $n + 1 ]
#  ":" 是必须的,这是因为,如果没有":"的话,Bash将
#+ 尝试把"$[ n = $n + 1 ]" 解释成一个命令
#  即使"n"被初始化成为一个字符串,这句也能工作.
echo -n "$n "

n=$[ $n + 1 ]
#  即使"n"被初始化成为一个字符串,这句也能工作.
#* Avoid this type of construct, since it is obsolete and nonportable.
#* 尽量避免这种类型的结果,因为这已经被废弃了,并且不具可移植性.
#  Thanks, Stephane Chazelas.
echo -n "$n "

# 现在来个C风格的增量操作.
# Thanks, Frank Wang, for pointing this out.

let "n++"          # let "++n"  also works.
echo -n "$n "

(( n++ ))          # (( ++n )  also works.
echo -n "$n "

: $(( n++ ))       # : $(( ++n )) also works.
echo -n "$n "

: $[ n++ ]         # : $[ ++n ]] also works
echo -n "$n "

echo

exit 0
################################End Script#########################################
Example 8-3 使用&&和||进行混合状态的test
################################Start Script#######################################
#!/bin/bash

a=24
b=47

if [ "$a" -eq 24 ] && [ "$b" -eq 47 ]
then
  echo "Test #1 succeeds."
else
  echo "Test #1 fails."
fi

# 错误:   if [ "$a" -eq 24 && "$b" -eq 47 ]
#+         尝试执行' [ "$a" -eq 24 '
#+         因为没找到']'所以失败了.
#
#  注意:  如果 [[ $a -eq 24 && $b -eq 24 ]]  能够工作.
#  那这个[[]]的test结构就比[]结构更灵活了.
#
#    (在17行的"&&"与第6行的"&&"意义不同)
#    Thanks, Stephane Chazelas, for pointing this out.


if [ "$a" -eq 98 ] || [ "$b" -eq 47 ]
then
  echo "Test #2 succeeds."
else
  echo "Test #2 fails."
fi


#  -a和-o选项提供了
#+ 一种可选的混合test方法.
#  Thanks to Patrick Callahan for pointing this out.


if [ "$a" -eq 24 -a "$b" -eq 47 ]
then
  echo "Test #3 succeeds."
else
  echo "Test #3 fails."
fi


if [ "$a" -eq 98 -o "$b" -eq 47 ]
then
  echo "Test #4 succeeds."
else
  echo "Test #4 fails."
fi


a=rhino
b=crocodile
if [ "$a" = rhino ] && [ "$b" = crocodile ]
then
  echo "Test #5 succeeds."
else
  echo "Test #5 fails."
fi

exit 0
################################End Script#########################################
Example 8-4 数字常量的处理
################################Start Script#######################################
#!/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 between 2 and 64.
# 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
# 这种64进制的表示法中的每位数字都必须在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: too great for base (error token is "081")
#              Octal numbers use only digits in the range 0 - 7.

exit 0       # Thanks, Rich Bartell and Stephane Chazelas, for clarification.
################################End Script#########################################
Example 9-1 $IFS和空白
################################Start Script#######################################
#!/bin/bash
# $IFS 处理空白的方法,与处理其它字符不同.

output_args_one_per_line()
{
  for arg
  do echo "[$arg]"
  done
}

echo; echo "IFS=\" \""
echo "-------"

IFS=" "
var=" a  b c   "
output_args_one_per_line $var  # output_args_one_per_line `echo " a  b c   "`
#
# [a]
# [b]
# [c]


echo; echo "IFS=:"
echo "-----"

IFS=:
var=":a::b:c:::"               # 与上边的一样,但是用" "替换了":"
output_args_one_per_line $var
#
# []
# [a]
# []
# [b]
# [c]
# []
# []
# []

# 同样的事情也会发生在awk中的"FS"域分隔符.

# Thank you, Stephane Chazelas.

echo

exit 0
################################End Script#########################################
                Example 12-37也是使用$IFS的另一个启发性的例子.

        $IGNOREEOF
                忽略EOF: 告诉shell在log out之前要忽略多少文件结束符(control-D).

        $LC_COLLATE
                常在.bashrc或/etc/profile中设置,这个变量用来在文件名扩展和模式匹配校对顺序.
                如果$LC_COLLATE被错误的设置,那么将会在filename globbing中引起错误的结果.

                注意:在2.05以后的Bash版本中,filename globbing将不在对[]中的字符区分大小写.
                        比如:ls [A-M]* 将即匹配File1.txt也会匹配file1.txt.为了恢复[]的习惯用法,
                        设置$LC_COLLATE的值为c,使用export LC_COLLATE=c 在/etc/profile或者是
                        ~/.bashrc中.

        $LC_CTYPE
                这个内部变量用来控制globbing和模式匹配的字符串解释.

        $LINENO
                这个变量记录它所在的shell脚本中它所在行的行号.这个变量一般用于调试目的.
                1 # *** BEGIN DEBUG BLOCK ***
                2 last_cmd_arg=$_  # Save it.
                3
                4 echo "At line number $LINENO, variable \"v1\" = $v1"
                5 echo "Last command argument processed = $last_cmd_arg"
                6 # *** END DEBUG BLOCK ***

        $MACHTYPE
                系统类型
                提示系统硬件
                bash$ echo $MACHTYPE
                i686

        $OLDPWD
                老的工作目录("OLD-print-working-directory",你所在的之前的目录)

        $OSTYPE
                操作系统类型.
                bash$ echo $OSTYPE
                linux

        $PATH
                指向Bash外部命令所在的位置,一般为/usr/bin,/usr/X11R6/bin,/usr/local/bin等.
                当给出一个命令时,Bash将自动对$PATH中的目录做一张hash表.$PATH中以":"分隔的
                目录列表将被存储在环境变量中.一般的,系统存储的$PATH定义在/ect/processed或
                ~/.bashrc中(见Appendix G).

                bash$ echo $PATH
                /bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin:/sbin:/usr/sbin

                PATH=${PATH}:/opt/bin将把/opt/bin目录附加到$PATH变量中.在脚本中,这是一个
                添加目录到$PATH中的便捷方法.这样在这个脚本退出的时候,$PATH将会恢复(因为这个
                shell是个子进程,像这样的一个脚本是不会将它的父进程的环境变量修改的)

                注意:当前的工作目录"./"一般都在$PATH中被省去.

        $PIPESTATUS
                数组变量将保存最后一个运行的前台管道的退出码.有趣的是,这个退出码和最后一个命令
                运行的退出码并不一定相同.
                bash$ echo $PIPESTATUS
                0

                bash$ ls -al | bogus_command
                bash: bogus_command: command not found
                bash$ echo $PIPESTATUS
                141

                bash$ ls -al | bogus_command
                bash: bogus_command: command not found
                bash$ echo $?
                127

                $PIPESTATUS数组的每个成员都会保存一个管道命令的退出码,$PIPESTATUS[0]保存第
                一个管道命令的退出码,$PIPESTATUS[1]保存第2个,以此类推.

                注意:$PIPESTATUS变量在一个login shell中可能会包含一个错误的0值(3.0以下版本)
                tcsh% bash

                bash$ who | grep nobody | sort
                bash$ echo ${PIPESTATUS[*]}
                0
                包含在脚本中的上边这行将会产生一个期望的输出0 1 0.

                注意:在某些上下文$PIPESTATUS可能不会给出正确的结果.
                bash$ echo $BASH_VERSION
                3.00.14(1)-release

                bash$ $ ls | bogus_command | wc
                bash: bogus_command: command not found
                0       0       0

                bash$ echo ${PIPESTATUS[@]}
                141 127 0

                Chet Ramey把上边输出不成确原因归咎于ls的行为.因为如果把ls的结果放到管道上,
                并且这个输出没被读取,那么SIGPIPE将会kill掉它,并且退出码变为141,而不是我们期
                望的0.这种情况也会发生在tr命令中.

                注意:$PIPESTATUS是一个"volatile"变量.在任何命令插入之前,并且在pipe询问之后,
                这个变量需要立即被捕捉.
                bash$ $ ls | bogus_command | wc
                bash: bogus_command: command not found
                0       0       0

                bash$ echo ${PIPESTATUS[@]}
                0 127 0

                bash$ echo ${PIPESTATUS[@]}
                0

        $PPID
                一个进程的$PPID就是它的父进程的进程id(pid).[1]
                使用pidof命令对比一下.

        $PROMPT_COMMAND
                这个变量保存一个在主提示符($PS1)显示之前需要执行的命令.

        $PS1
                主提示符,具体见命令行上的显示.

        $PS2
                第2提示符,当你需要额外的输入的时候将会显示,默认为">".

        $PS3
                第3提示符,在一个select循环中显示(见Example 10-29).

        $PS4
                第4提示符,当使用-x选项调用脚本时,这个提示符将出现在每行的输出前边.
                默认为"+".

        $PWD
                工作目录(你当前所在的目录).
  与pwd内建命令作用相同.
################################Start Script#######################################
#!/bin/bash

E_WRONG_DIRECTORY=73

clear # 清屏.

TargetDirectory=/home/bozo/projects/GreatAmericanNovel

cd $TargetDirectory
echo "Deleting stale files in $TargetDirectory."

if [ "$PWD" != "$TargetDirectory" ]
then    # 防止偶然删除错误的目录
  echo "Wrong directory!"
  echo "In $PWD, rather than $TargetDirectory!"
  echo "Bailing out!"
  exit $E_WRONG_DIRECTORY
fi 

rm -rf *
rm .[A-Za-z0-9]*    # Delete dotfiles.
rm .[A-Za-z0-9]*    # 删除"."文件(隐含文件).
# rm -f .[^.]* ..?*   为了删除以多个"."开头的文件.
# (shopt -s dotglob; rm -f *)   也行.
# Thanks, S.C. for pointing this out.

# 文件名能够包含0-255范围的所有字符,除了"/".
# 删除以各种诡异字符开头的文件将作为一个练习留给大家.

# 这里预留给其他的必要操作.

echo
echo "Done."
echo "Old files deleted in $TargetDirectory."
echo


exit 0
################################End Script#########################################
        $REPLY
                read命令如果没有给变量,那么输入将保存在$REPLY中.在select菜单中也可用,但是只
  提供选择的变量的项数,而不是变量本身的值.
################################Start Script#######################################
#!/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
################################End Script#########################################

        $SECONDS
  这个脚本已经运行的时间(单位为秒).
################################Start Script#######################################
#!/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."
  #  在一台比较慢的或者是负载很大的机器上,这个脚本可能会跳过几次循环
  #+ 在一个while循环中.
  sleep $INTERVAL
done

echo -e "\a"  # Beep!

exit 0
################################End Script#########################################
Example 9-2 时间输入
################################Start Script#######################################
   1 #!/bin/bash
   2 # timed-input.sh
   3
   4 # TMOUT=3    在新版本的Bash上也能工作.
   5
   6
   7 TIMELIMIT=3  # 在这个例子上是3秒,也可以设其他的值.
   8
   9 PrintAnswer()
  10 {
  11   if [ "$answer" = TIMEOUT ]
  12   then
  13     echo $answer
  14   else       # 别想混合着两个例子.
  15     echo "Your favorite veggie is $answer"
  16     kill $!  # kill将不再需要TimerOn函数运行在后台.
  17              # $! 是运行在后台的最后一个工作的PID.
  18   fi
  19
  20 } 
  21
  22
  23
  24 TimerOn()
  25 {
  26   sleep $TIMELIMIT && kill -s 14 $$ &
  27   # 等待3秒,然后发送一个信号给脚本.
  28 } 
  29
  30 Int14Vector()
  31 {
  32   answer="TIMEOUT"
  33   PrintAnswer
  34   exit 14
  35 } 
  36
  37 trap Int14Vector 14   # 为了我们的目的,时间中断(14)被破坏了.
  38
  39 echo "What is your favorite vegetable "
  40 TimerOn
  41 read answer
  42 PrintAnswer
  43
  44
  45 #  很明显的,这是一个拼凑的实现.
  46 #+ 然而使用"-t"选项来"read"的话,将会简化这个任务.
  47 #  见"t-out.sh",在下边.
  48
  49 #  如果你需要一个真正的幽雅的写法...
  50 #+ 建议你使用c/c++来写这个应用,
  51 #+ 使用合适的库来完成这个任务,比如'alarm'和'setitimer'.
  52
  53 exit 0
################################End Script#########################################
Example 9-3 再来一个时间输入
################################Start Script#######################################
#!/bin/bash
# timeout.sh

#  Stephane Chazelas编写,
#+ 本书作者进行了一些修改.

INTERVAL=5                # timeout间隔

timedout_read() {
  timeout=$1
  varname=$2
  old_tty_settings=`stty -g`
  stty -icanon min 0 time ${timeout}0
  eval read $varname      # 或者就是 read $varname
  stty "$old_tty_settings"
  # 察看"stty"的man页.
}

echo; echo -n "What's your name? Quick! "
timedout_read $INTERVAL your_name

#  这种方法可能不是每个终端类型都可以正常使用的.
#  最大的timeout依赖于具体的终端.
#+ (一般都是25.5秒).

echo

if [ ! -z "$your_name" ]  # If name input before timeout...
then
  echo "Your name is $your_name."
else
  echo "Timed out."
fi

echo

# 这个脚本的行为可能与"timed-input.sh"有点不同.
# 在每次按键的时候,计数器都会重置.

exit 0
################################End Script#########################################
Example 9-4 Timed read