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
