***@@@ex36.sh@@@!!!************************************************************************************
#!/bin/bash
# "Reading" 变量.
echo -n "Enter the value of variable 'var1': "
# -n 选项, 阻止换行.
read var1
# 注意: 在var1前面没有'$', 因为变量正在被设置.
echo "var1 = $var1"
echo
# 一个单独的'read'语句可以设置多个变量.
echo -n "Enter the values of variables 'var2' and 'var3' (separated by a space or tab): "
read var2 var3
echo "var2 = $var2 var3 = $var3"
# 如果你只输入了一个值, 那么其他的变量还是处于未设置状态(null).
exit 0
%%%&&&ex36.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex37.sh@@@!!!************************************************************************************
#!/bin/bash
dir1=/usr/local
dir2=/var/spool
pushd $dir1
# 将自动运行一个 'dirs' (把目录栈的内容列到stdout上).
echo "Now in directory `pwd`." # 使用后置引用的 'pwd'.
# 现在对'dir1'做一些操作.
pushd $dir2
echo "Now in directory `pwd`."
# 现在对'dir2'做一些操作.
echo "The top entry in the DIRSTACK array is $DIRSTACK."
popd
echo "Now back in directory `pwd`."
# 现在, 对'dir1'做更多的操作.
popd
echo "Now back in original working directory `pwd`."
exit 0
# 如果你不使用 'popd' 将会发生什么 -- 然后退出这个脚本?
# 你最后将落在哪个目录中? 为什么?
%%%&&&ex37.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex38.sh@@@!!!************************************************************************************
#!/bin/bash
. data-file # 加载一个数据文件.
# 与"source data-file"效果相同, 但是更具可移植性.
# 文件"data-file"必须存在于当前工作目录,
#+ 因为这个文件是使用'basename'来引用的.
# 现在, 引用这个文件中的一些数据.
echo "variable1 (from data-file) = $variable1"
echo "variable3 (from data-file) = $variable3"
let "sum = $variable2 + $variable4"
echo "Sum of variable2 + variable4 (from data-file) = $sum"
echo "message1 (from data-file) is /"$message1/""
# 注意: 将双引号转义
print_message This is the message-print function in the data-file.
exit 0
%%%&&&ex38.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex39.sh@@@!!!************************************************************************************
#!/bin/bash
ROOT_UID=0 # 只有$UID为0的用户才拥有root权限.
E_NOTROOT=65
E_NOPARAMS=66
if [ "$UID" -ne "$ROOT_UID" ]
then
echo "Must be root to run this script."
# "Run along kid, it's past your bedtime."
exit $E_NOTROOT
fi
if [ -z "$1" ]
then
echo "Usage: `basename $0` find-string"
exit $E_NOPARAMS
fi
echo "Updating 'locate' database..."
echo "This may take a while."
updatedb /usr & # 必须使用root身份来运行.
wait
# 将不会继续向下运行, 除非'updatedb'命令执行完成.
# 你希望在查找文件名之前更新database.
locate $1
# 如果没有'wait'命令的话, 而且在比较糟的情况下,
#+ 脚本可能在'updatedb'命令还在运行的时候退出,
#+ 这将会导致'updatedb'成为一个孤儿进程.
exit 0
%%%&&&ex39.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex3.sh@@@!!!************************************************************************************
#!/bin/bash
# 这个简单的脚本可以把文件中所有的空行删除.
# 没做参数检查.
#
# 你或许想添加如下代码:
#
# E_NOARGS=65
# if [ -z "$1" ]
# then
# echo "Usage: `basename $0` target-file"
# exit $E_NOARGS
# fi
# 这个脚本调用起来的效果,
# 等价于从命令行上调用:
# sed -e '/^$/d' filename.
sed -e /^$/d "$1"
# '-e'意味着后边跟的是"编辑"命令. (在这里是可选的).
# '^'匹配行首, '$'匹配行尾.
# 这条语句用来匹配行首与行尾之间什么都没有的行,
#+ 即空白行.
# 'd'为删除命令.
# 将命令行参数引用起来,
#+ 就意味着可以在文件名中使用空白字符或者特殊字符.
# 注意, 这个脚本其实并不能真正的修改目标文件.
# 如果你想保存修改, 可以将它的输出重定向.
exit 0
%%%&&&ex3.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex40.sh@@@!!!************************************************************************************
#!/bin/bash
# ex40.sh (burn-cd.sh)
# 自动刻录CDR的脚本.
SPEED=2 # 如果你的硬件支持的话, 你可以选用更高的速度.
IMAGEFILE=cdimage.iso
CONTENTSFILE=contents
DEVICE=cdrom
# DEVICE="0,0" 为了是用老版本的CDR
DEFAULTDIR=/opt # 这是包含需要被刻录内容的目录.
# 必须保证目录存在.
# 小练习: 测试一下目录是否存在.
# 使用 Joerg Schilling 的 "cdrecord" 包:
# http://www.fokus.fhg.de/usr/schilling/cdrecord.html
# 如果一般用户调用这个脚本的话, 可能需要root身份
#+ chmod u+s /usr/bin/cdrecord
# 当然, 这会产生安全漏洞, 虽然这是一个比较小的安全漏洞.
if [ -z "$1" ]
then
IMAGE_DIRECTORY=$DEFAULTDIR
# 如果命令行没指定的话, 那么这个就是默认目录.
else
IMAGE_DIRECTORY=$1
fi
# 创建一个"内容列表"文件.
ls -lRF $IMAGE_DIRECTORY > $IMAGE_DIRECTORY/$CONTENTSFILE
# "l" 选项将给出一个"长"文件列表.
# "R" 选项将使这个列表递归.
# "F" 选项将标记出文件类型 (比如: 目录是以 /结尾, 而可执行文件以 *结尾).
echo "Creating table of contents."
# 在烧录到CDR之前创建一个镜像文件.
mkisofs -r -o $IMAGEFILE $IMAGE_DIRECTORY
echo "Creating ISO9660 file system image ($IMAGEFILE)."
# 烧录CDR.
echo "Burning the disk."
echo "Please be patient, this will take a while."
cdrecord -v -isosize speed=$SPEED dev=$DEVICE $IMAGEFILE
exit $?
%%%&&&ex40.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex41.sh@@@!!!************************************************************************************
#!/bin/bash
# 从/var/log/messagesGenerates的尾部开始
# 产生当前目录下的一个lof文件.
# 注意: 如果这个脚本被一个一般用户调用的话,
# /var/log/messages 必须是全部可读的.
# #root chmod 644 /var/log/messages
LINES=5
( date; uname -a ) >>logfile
# 时间和机器名
echo --------------------------------------------------------------------- >>logfile
tail -$LINES /var/log/messages | xargs | fmt -s >>logfile
echo >>logfile
echo >>logfile
exit 0
# 注意:
# -----
# 像 Frank Wang 所指出,
#+ 在原文件中的任何不匹配的引号(包括单引号和双引号)
#+ 都会给xargs造成麻烦.
#
# 他建议使用下边的这行来替换上边的第15行:
# tail -$LINES /var/log/messages | tr -d "/"'" | xargs | fmt -s >>logfile
# 练习:
# -----
# 修改这个脚本, 使得这个脚本每个20分钟
#+ 就跟踪一下 /var/log/messages 的修改记录.
# 提示: 使用 "watch" 命令.
%%%&&&ex41.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex42.sh@@@!!!************************************************************************************
#!/bin/bash
# copydir.sh
# 将当前目录下($PWD)的所有文件都拷贝到
#+ 命令行所指定的另一个目录中去.
E_NOARGS=65
if [ -z "$1" ] # 如果没有参数传递进来那就退出.
then
echo "Usage: `basename $0` directory-to-copy-to"
exit $E_NOARGS
fi
ls . | xargs -i -t cp ./{} $1
# ^^ ^^ ^^
# -t 是 "verbose" (输出命令行到stderr) 选项.
# -i 是"替换字符串"选项.
# {} 是输出文本的替换点.
# 这与在"find"命令中使用{}的情况很相像.
#
# 列出当前目录下的所有文件(ls .),
#+ 将 "ls" 的输出作为参数传递到 "xargs"(-i -t 选项) 中,
#+ 然后拷贝(cp)这些参数({})到一个新目录中($1).
#
# 最终的结果和下边的命令等价,
#+ cp * $1
#+ 除非有文件名中嵌入了"空白"字符.
exit 0
%%%&&&ex42.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex43.sh@@@!!!************************************************************************************
#!/bin/bash
y=`eval ls -l` # 与 y=`ls -l` 很相似
echo $y #+ 但是换行符将会被删除, 因为"echo"的变量未被""引用.
echo
echo "$y" # 用""将变量引用起来, 换行符就不会被空格替换了.
echo; echo
y=`eval df` # 与 y=`df` 很相似
echo $y #+ 换行符又被空格替换了.
# 当没有LF(换行符)出现时, 如果使用"awk"这样的工具来分析输出的结果,
#+ 应该能更容易一些.
echo
echo "==========================================================="
echo
# 现在,来看一下怎么用"eval"命令来"扩展"一个变量 . . .
for i in 1 2 3 4 5; do
eval value=$i
# value=$i 具有相同的效果, 在这里并不是非要使用"eval"不可.
# 一个缺乏特殊含义的变量将被评价为自身 -- 也就是说,
#+ 这个变量除了能够被扩展成自身所表示的字符外, 不能被扩展成任何其他的含义.
echo $value
done
echo
echo "---"
echo
for i in ls df; do
value=eval $i
# value=$i 在这里就与上边这句有了本质上的区别.
# "eval" 将会评价命令 "ls" 和 "df" . . .
# 术语 "ls" 和 "df" 就具有特殊含义,
#+ 因为它们被解释成命令,
#+ 而不是字符串本身.
echo $value
done
exit 0
%%%&&&ex43.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex44.sh@@@!!!************************************************************************************
#!/bin/bash
# 结束ppp进程来强制登出log-off.
# 本脚本应该以root用户的身份来运行.
killppp="eval kill -9 `ps ax | awk '/ppp/ { print $1 }'`"
# -------- ppp的进程ID -------
$killppp # 这个变量现在成为了一个命令.
# 下边的命令必须以root用户的身份来运行.
chmod 666 /dev/ttyS3 # 恢复读写权限,否则什么?
# 因为在ppp上执行一个SIGKILL将会修改串口的权限,
#+ 我们把权限恢复到之前的状态.
rm /var/lock/LCK..ttyS3 # 删除串口琐文件.为什么?
exit 0
# 练习:
# -----
# 1) 编写一个脚本来验证是否root用户正在运行它.
# 2) 做一个检查, 在杀掉某个进程之前,
#+ 检查一下这个将要被杀掉的进程是否正在运行.
# 3) 基于'fuser'来编写达到这个目的的另一个版本的脚本
#+ if [ fuser -s /dev/modem ]; then . . .
%%%&&&ex44.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex45a.sh@@@!!!************************************************************************************
#!/bin/bash
echo
echo "String operations using /"expr /$string : /" construct"
echo "==================================================="
echo
a=1234zipper5FLIPPER43231
echo "The string being operated upon is /"`expr "$a" : '/(.*/)'`/"."
# 转义括号对的操作. == ==
# ***************************
#+ 转义括号对
#+ 用来匹配一个子串
# ***************************
# 如果不转义括号的话...
#+ 那么'expr'将把string操作转换为一个整数.
echo "Length of /"$a/" is `expr "$a" : '.*'`." # 字符串长度
echo "Number of digits at the beginning of /"$a/" is `expr "$a" : '[0-9]*'`."
# ------------------------------------------------------------------------- #
echo
echo "The digits at the beginning of /"$a/" are `expr "$a" : '/([0-9]*/)'`."
# == ==
echo "The first 7 characters of /"$a/" are `expr "$a" : '/(......./)'`."
# ===== == ==
# 再来一个, 转义括号对强制一个子串匹配.
#
echo "The last 7 characters of /"$a/" are `expr "$a" : '.*/(......./)'`."
# ==== 字符串操作的结尾 ^^
# (最后这个模式的意思是忽略前边的任何字符,直到最后7个字符,
#+ 最后7个点就是需要匹配的任意7个字符的字串)
echo
exit 0
%%%&&&ex45a.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex45.sh@@@!!!************************************************************************************
#!/bin/bash
# 展示一些使用'expr'的例子
# ========================
echo
# 算术 操作
# ---- ----
echo "Arithmetic Operators"
echo
a=`expr 5 + 3`
echo "5 + 3 = $a"
a=`expr $a + 1`
echo
echo "a + 1 = $a"
echo "(incrementing a variable)"
a=`expr 5 % 3`
# 取模操作
echo
echo "5 mod 3 = $a"
echo
echo
# 逻辑 操作
# ---- ----
# true返回1, false返回0,
#+ 而Bash的使用惯例则相反.
echo "Logical Operators"
echo
x=24
y=25
b=`expr $x = $y` # 测试相等.
echo "b = $b" # 0 ( $x -ne $y )
echo
a=3
b=`expr $a /> 10`
echo 'b=`expr $a /> 10`, therefore...'
echo "If a > 10, b = 0 (false)"
echo "b = $b" # 0 ( 3 ! -gt 10 )
echo
b=`expr $a /< 10`
echo "If a < 10, b = 1 (true)"
echo "b = $b" # 1 ( 3 -lt 10 )
echo
# 注意转义操作.
b=`expr $a /<= 3`
echo "If a <= 3, b = 1 (true)"
echo "b = $b" # 1 ( 3 -le 3 )
# 也有 "/>=" 操作 (大于等于).
echo
echo
# 字符串 操作
# ------ ----
echo "String Operators"
echo
a=1234zipper43231
echo "The string being operated upon is /"$a/"."
# 长度: 字符串长度
b=`expr length $a`
echo "Length of /"$a/" is $b."
# 索引: 从字符串的开头查找匹配的子串,
# 并取得第一个匹配子串的位置.
b=`expr index $a 23`
echo "Numerical position of first /"2/" in /"$a/" is /"$b/"."
# substr: 从指定位置提取指定长度的字串.
b=`expr substr $a 2 6`
echo "Substring of /"$a/", starting at position 2,/
and 6 chars long is /"$b/"."
# 'match' 操作的默认行为就是从字符串的开始进行搜索,
#+ 并匹配第一个匹配的字符串.
#
# 使用正则表达式
b=`expr match "$a" '[0-9]*'` # 数字的个数.
echo Number of digits at the beginning of /"$a/" is $b.
b=`expr match "$a" '/([0-9]*/)'` # 注意, 需要转义括号
# == == + 这样才能触发子串的匹配.
echo "The digits at the beginning of /"$a/" are /"$b/"."
echo
exit 0
%%%&&&ex45.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex46.sh@@@!!!************************************************************************************
#!/bin/bash
echo
let a=11 # 与 'a=11' 相同
let a=a+5 # 等价于 let "a = a + 5"
# (双引号和空格是这句话更具可读性.)
echo "11 + 5 = $a" # 16
let "a <<= 3" # 等价于 let "a = a << 3"
echo "/"/$a/" (=16) left-shifted 3 places = $a"
# 128
let "a /= 4" # 等价于 let "a = a / 4"
echo "128 / 4 = $a" # 32
let "a -= 5" # 等价于 let "a = a - 5"
echo "32 - 5 = $a" # 27
let "a *= 10" # 等价于 let "a = a * 10"
echo "27 * 10 = $a" # 270
let "a %= 8" # 等价于 let "a = a % 8"
echo "270 modulo 8 = $a (270 / 8 = 33, remainder $a)"
# 6
echo
exit 0
%%%&&&ex46.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex47.sh@@@!!!************************************************************************************
#!/bin/bash
# printf 示例
PI=3.14159265358979
DecimalConstant=31373
Message1="Greetings,"
Message2="Earthling."
echo
printf "Pi to 2 decimal places = %1.2f" $PI
echo
printf "Pi to 9 decimal places = %1.9f" $PI # 都能够正确的结束.
printf "/n" # 打印一个换行,
# 等价于 'echo' . . .
printf "Constant = /t%d/n" $DecimalConstant # 插入一个 tab (/t).
printf "%s %s /n" $Message1 $Message2
echo
# ==========================================#
# 模拟C函数, sprintf().
# 使用一个格式化的字符串来加载一个变量.
echo
Pi12=$(printf "%1.12f" $PI)
echo "Pi to 12 decimal places = $Pi12"
Msg=`printf "%s %s /n" $Message1 $Message2`
echo $Msg; echo $Msg
# 像我们所看到的一样, 现在'sprintf'可以
#+ 作为一个可被加载的模块,
#+ 但是不具可移植性.
exit 0
%%%&&&ex47.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex48.sh@@@!!!************************************************************************************
#!/bin/bash
# 使用cpio来拷贝目录树.
# 使用'cpio'的优点:
# 加速拷贝. 比通过管道使用'tar'命令快一些.
# 很适合拷贝一些'cp'命令
#+ 搞不定的的特殊文件(比如名字叫pipes的文件, 等等)
ARGS=2
E_BADARGS=65
if [ $# -ne "$ARGS" ]
then
echo "Usage: `basename $0` source destination"
exit $E_BADARGS
fi
source=$1
destination=$2
find "$source" -depth | cpio -admvp "$destination"
# ^^^^^ ^^^^^
# 阅读'find'和'cpio'的man页来了解这些选项的意义.
# 练习:
# -----
# 添加一些代码来检查'find | cpio'管道命令的退出码($?)
#+ 并且如果出现错误的时候输出合适的错误码.
exit 0
%%%&&&ex48.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex49.sh@@@!!!************************************************************************************
#!/bin/bash
# 把一个文件的内容全部转换为大写.
E_BADARGS=65
if [ -z "$1" ] # 检查命令行参数.
then
echo "Usage: `basename $0` filename"
exit $E_BADARGS
fi
tr a-z A-Z <"$1"
# 与上边的作用相同, 但是使用了POSIX字符集标记方法:
# tr '[:lower:]' '[:upper:]' <"$1"
# 感谢, S.C.
exit 0
# 练习:
# 重写这个脚本, 通过选项可以控制脚本或者
#+ 转换为大写或者转换为小写.
%%%&&&ex49.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex4.sh@@@!!!************************************************************************************
#!/bin/bash
# "替换", 这个脚本的用途:
#+ 将一个文件中的某个字符串(或匹配模式), 替换为另一个字符串(或匹配模式),
#+ 比如, "subst Smith Jones letter.txt".
ARGS=3 # 这个脚本需要3个参数.
E_BADARGS=65 # 传递给脚本的参数个数不对.
if [ $# -ne "$ARGS" ]
# 测试脚本的参数个数(这是个好办法).
then
echo "Usage: `basename $0` old-pattern new-pattern filename"
exit $E_BADARGS
fi
old_pattern=$1
new_pattern=$2
if [ -f "$3" ]
then
file_name=$3
else
echo "File /"$3/" does not exist."
exit $E_BADARGS
fi
# 下面是实现功能的代码.
# -----------------------------------------------
sed -e "s/$old_pattern/$new_pattern/g" $file_name
# -----------------------------------------------
# 's'在sed中是替换命令,
#+ /pattern/表示匹配模式.
# "g", 即全局标志, 用来自动替换掉每行中
#+ 出现的全部$old_pattern模式, 而不仅仅替换掉第一个匹配.
# 如果想深入了解, 可以参考'sed'命令的相关书籍.
exit 0 # 成功调用脚本, 将会返回0.
%%%&&&ex4.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex50.sh@@@!!!************************************************************************************
#!/bin/bash
WIDTH=40 # 设为40列宽.
b=`ls /usr/local/bin` # 取得文件列表...
echo $b | fmt -w $WIDTH
# 也可以使用如下方法, 作用是相同的.
# echo $b | fold - -s -w $WIDTH
exit 0
%%%&&&ex50.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex51.sh@@@!!!************************************************************************************
#!/bin/bash
# 练习'date'命令
echo "The number of days since the year's beginning is `date +%j`."
# 需要在调用格式的前边加上一个'+'号.
# %j用来给出今天是本年度的第几天.
echo "The number of seconds elapsed since 01/01/1970 is `date +%s`."
# %s将产生从"UNIX 元年"到现在为止的秒数,
#+ 但是这东西现在还有用么?
prefix=temp
suffix=$(date +%s) # 'date'命令的"+%s"选项是GNU特性.
filename=$prefix.$suffix
echo $filename
# 这是一种非常好的产生"唯一"临时文件的办法,
#+ 甚至比使用$$都强.
# 如果想了解'date'命令的更多选项, 请查阅这个命令的man页.
exit 0
%%%&&&ex51.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex52.sh@@@!!!************************************************************************************
#!/bin/bash
# 在当前目录下用Uudecode解码所有用uuencode编码的文件.
lines=35 # 允许读头部的35行(范围很宽).
for File in * # 测试所有$PWD下的文件.
do
search1=`head -$lines $File | grep begin | wc -w`
search2=`tail -$lines $File | grep end | wc -w`
# 用Uuencode编码过的文件在文件开始的地方都有个"begin",
#+ 在文件结尾的地方都有"end".
if [ "$search1" -gt 0 ]
then
if [ "$search2" -gt 0 ]
then
echo "uudecoding - $File -"
uudecode $File
fi
fi
done
# 小心不要让这个脚本运行自己,
#+ 因为它也会把自身也认为是一个经过uuencode编码过的文件,
#+ 这都是因为这个脚本自身也包含"begin"和"end".
# 练习:
# -----
# 修改这个脚本, 让它可以检查一个新闻组的每个文件,
#+ 并且如果下一个没找到的话就跳过.
exit 0
%%%&&&ex52.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex53.sh@@@!!!************************************************************************************
#!/bin/bash
# 使用"seq"
echo
for a in `seq 80` # or for a in $( seq 80 )
# 与 for a in 1 2 3 4 5 ... 80 相同(少敲了好多字!).
# 也可以使用'jot'(如果系统上有的话).
do
echo -n "$a "
done # 1 2 3 4 5 ... 80
# 这也是一个通过使用命令输出
# 来产生"for"循环中[list]列表的例子.
echo; echo
COUNT=80 # 当然, 'seq'也可以使用一个可替换的参数.
for a in `seq $COUNT` # 或者 for a in $( seq $COUNT )
do
echo -n "$a "
done # 1 2 3 4 5 ... 80
echo; echo
BEGIN=75
END=80
for a in `seq $BEGIN $END`
# 传给"seq"两个参数, 从第一个参数开始增长,
#+ 一直增长到第二个参数为止.
do
echo -n "$a "
done # 75 76 77 78 79 80
echo; echo
BEGIN=45
INTERVAL=5
END=80
for a in `seq $BEGIN $INTERVAL $END`
# 传给"seq"三个参数, 从第一个参数开始增长,
#+ 并以第二个参数作为增量,
#+ 一直增长到第三个参数为止.
do
echo -n "$a "
done # 45 50 55 60 65 70 75 80
echo; echo
exit 0
%%%&&&ex53.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex54.sh@@@!!!************************************************************************************
#!/bin/bash
exec echo "Exiting /"$0/"." # 脚本应该在这里退出.
# ----------------------------------
# The following lines never execute.
echo "This echo will never echo."
exit 99 # 脚本是不会在这里退出的.
# 脚本退出后会使用'echo $?'
#+ 来检查一下退出码.
# 一定 *不是* 99.
%%%&&&ex54.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex55.sh@@@!!!************************************************************************************
#!/bin/sh
# --> 本书作者所做的注释全部以"# -->"开头.
# --> 这是由Miquel van Smoorenburg所编写的
# --> 'rc'脚本包的一部分, <miquels@drinkel.nl.mugnet.org>.
# --> 这个特殊的脚本看起来是Red Hat/FC专用的,
# --> (在其它的发行版中可能不会出现).
# 停止所有正在运行的不必要的服务
#+ (不会有多少, 所以这是个合理性检查)
for i in /var/lock/subsys/*; do
# --> 标准的for/in循环, 但是由于"do"在同一行上,
# --> 所以必须添加";".
# 检查脚本是否在那里.
[ ! -f $i ] && continue
# --> 这是一种使用"与列表"的聪明方法, 等价于:
# --> if [ ! -f "$i" ]; then continue
# 取得子系统的名字.
subsys=${i#/var/lock/subsys/}
# --> 匹配变量名, 在这里就是文件名.
# --> 与subsys=`basename $i`完全等价.
# --> 从锁定文件名中获得
# -->+ (如果那里有锁定文件的话,
# -->+ 那就证明进程正在运行).
# --> 参考一下上边所讲的"锁定文件"的内容.
# 终止子系统.
if [ -f /etc/rc.d/init.d/$subsys.init ]; then
/etc/rc.d/init.d/$subsys.init stop
else
/etc/rc.d/init.d/$subsys stop
# --> 挂起运行的作业和幽灵进程.
# --> 注意"stop"只是一个位置参数,
# -->+ 并不是shell内建命令.
fi
done
%%%&&&ex55.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex56.sh@@@!!!************************************************************************************
#!/bin/bash
# Shell命令可以放到Perl脚本的前面.
echo "This precedes the embedded Perl script within /"$0/"."
echo "==============================================================="
perl -e 'print "This is an embedded Perl script./n";'
# 类似于sed, Perl也可以使用"-e"选项.
echo "==============================================================="
echo "However, the script may also contain shell and system commands."
exit 0
%%%&&&ex56.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex57.sh@@@!!!************************************************************************************
#!/bin/bash
# badname.sh
# 删除当前目录下文件名中包含一些特殊字符的文件.(这些特殊字符指的是不应该出现在文件名中的字符)
for filename in *
do
badname=`echo "$filename" | sed -n /[/+/{/;/"///=/?~/(/)/</>/&/*/|/$]/p`
# badname=`echo "$filename" | sed -n '/[+{;"/=?~()<>&*|$]/p'` 这句也行.
# 删除文件名包含这些字符的文件: + { ; " / = ? ~ ( ) < > & * | $
#
rm $badname 2>/dev/null
# ^^^^^^^^^^^ 错误消息将被抛弃.
done
# 现在, 处理文件名中以任何方式包含空白的文件.
find . -name "* *" -exec rm -f {} /;
# "find"命令匹配到的目录名将替换到"{}"的位置.
# '/'是为了保证';'被正确的转义, 并且放到命令的结尾.
exit 0
#---------------------------------------------------------------------
# 这行下边的命令将不会运行, 因为有 "exit" 命令.
# 下边这句可以用来替换上边的脚本:
find . -name '*[+{;"//=?~()<>&*|$ ]*' -exec rm -f '{}' /;
# (感谢, S.C.)
%%%&&&ex57.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex58.sh@@@!!!************************************************************************************
#!/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
%%%&&&ex58.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex59.sh@@@!!!************************************************************************************
#!/bin/bash
JUST_A_SECOND=1
funky ()
{ # 这是一个最简单的函数.
echo "This is a funky function."
echo "Now exiting funky function."
} # 函数必须在调用前声明.
fun ()
{ # 一个稍微复杂一些的函数.
i=0
REPEATS=30
echo
echo "And now the fun really begins."
echo
sleep $JUST_A_SECOND # 嘿, 暂停一秒!
while [ $i -lt $REPEATS ]
do
echo "----------FUNCTIONS---------->"
echo "<------------ARE-------------"
echo "<------------FUN------------>"
echo
let "i+=1"
done
}
# 现在, 调用这两个函数.
funky
fun
exit 0
%%%&&&ex59.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex5.sh@@@!!!************************************************************************************
#!/bin/bash
echo hello
echo $? # 退出状态为0, 因为命令执行成功.
lskdf # 无效命令.
echo $? # 非零的退出状态, 因为命令执行失败.
echo
exit 113 # 返回113退出状态给shell.
# 为了验证这个结果, 可以在脚本结束的地方使用"echo $?".
# 一般的, 'exit 0' 表示成功,
#+ 而一个非零的退出码表示一个错误, 或者是反常的条件.
%%%&&&ex5.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex60.sh@@@!!!************************************************************************************
#!/bin/bash
# 函数和参数
DEFAULT=default # 默认参数值.
func2 () {
if [ -z "$1" ] # 第一个参数是否长度为零?
then
echo "-Parameter #1 is zero length.-" # 或者没有参数被传递进来.
else
echo "-Param #1 is /"$1/".-"
fi
variable=${1-$DEFAULT} # 这里的参数替换
echo "variable = $variable" #+ 表示什么?
# ---------------------------
# 为了区分没有参数的情况,
#+ 和只有一个null参数的情况.
if [ "$2" ]
then
echo "-Parameter #2 is /"$2/".-"
fi
return 0
}
echo
echo "Nothing passed."
func2 # 不带参数调用
echo
echo "Zero-length parameter passed."
func2 "" # 使用0长度的参数进行调用
echo
echo "Null parameter passed."
func2 "$uninitialized_param" # 使用未初始化的参数进行调用
echo
echo "One parameter passed."
func2 first # 带一个参数调用
echo
echo "Two parameters passed."
func2 first second # 带两个参数调用
echo
echo "/"/" /"second/" passed."
func2 "" second # 带两个参数调用,
echo # 第一个参数长度为0, 第二个参数是由ASCII码组成的字符串.
exit 0
%%%&&&ex60.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex61.sh@@@!!!************************************************************************************
#!/bin/bash
# 将阿拉伯数字转化为罗马数字
# 范围: 0 - 200
# 比较粗糙, 但可以正常工作.
# 扩展范围, 并且完善这个脚本, 作为练习.
# 用法: roman number-to-convert
LIMIT=200
E_ARG_ERR=65
E_OUT_OF_RANGE=66
if [ -z "$1" ]
then
echo "Usage: `basename $0` number-to-convert"
exit $E_ARG_ERR
fi
num=$1
if [ "$num" -gt $LIMIT ]
then
echo "Out of range!"
exit $E_OUT_OF_RANGE
fi
to_roman () # 在第一次调用函数前必须先定义它.
{
number=$1
factor=$2
rchar=$3
let "remainder = number - factor"
while [ "$remainder" -ge 0 ]
do
echo -n $rchar
let "number -= factor"
let "remainder = number - factor"
done
return $number
# 练习:
# -----
# 解释这个函数是如何工作的.
# 提示: 依靠不断的除, 来分割数字.
}
to_roman $num 100 C
num=$?
to_roman $num 90 LXXXX
num=$?
to_roman $num 50 L
num=$?
to_roman $num 40 XL
num=$?
to_roman $num 10 X
num=$?
to_roman $num 9 IX
num=$?
to_roman $num 5 V
num=$?
to_roman $num 4 IV
num=$?
to_roman $num 1 I
echo
exit 0
%%%&&&ex61.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex62.sh@@@!!!************************************************************************************
#!/bin/bash
# 函数内部的局部变量与全局变量.
func ()
{
local loc_var=23 # 声明为局部变量.
echo # 使用'local'内建命令.
echo "/"loc_var/" in function = $loc_var"
global_var=999 # 没有声明为局部变量.
# 默认为全局变量.
echo "/"global_var/" in function = $global_var"
}
func
# 现在, 来看看局部变量"loc_var"在函数外部是否可见.
echo
echo "/"loc_var/" outside function = $loc_var"
# $loc_var outside function =
# 不行, $loc_var不是全局可见的.
echo "/"global_var/" outside function = $global_var"
# 在函数外部$global_var = 999
# $global_var是全局可见的.
echo
exit 0
# 与C语言相比, 在函数内声明的Bash变量
#+ 除非它被明确声明为local时, 它才是局部的.
%%%&&&ex62.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex63.sh@@@!!!************************************************************************************
#!/bin/bash
# 阶乘
# ----
# bash允许递归吗?
# 嗯, 允许, 但是...
# 他太慢了, 所以恐怕你难以忍受.
MAX_ARG=5
E_WRONG_ARGS=65
E_RANGE_ERR=66
if [ -z "$1" ]
then
echo "Usage: `basename $0` number"
exit $E_WRONG_ARGS
fi
if [ "$1" -gt $MAX_ARG ]
then
echo "Out of range (5 is maximum)."
# 现在让我们来了解一些实际情况.
# 如果你想计算比这个更大的范围的阶乘,
#+ 应该用真正的编程语言来重写它.
exit $E_RANGE_ERR
fi
fact ()
{
local number=$1
# 变量"number"必须声明为局部变量,
#+ 否则不能正常工作.
if [ "$number" -eq 0 ]
then
factorial=1 # 0的阶乘为1.
else
let "decrnum = number - 1"
fact $decrnum # 递归的函数调用(就是函数调用自己).
let "factorial = $number * $?"
fi
return $factorial
}
fact $1
echo "Factorial of $1 is $?."
exit 0
%%%&&&ex63.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex64.sh@@@!!!************************************************************************************
#!/bin/bash
# "与列表"
if [ ! -z "$1" ] && echo "Argument #1 = $1" && [ ! -z "$2" ] && echo "Argument #2 = $2"
then
echo "At least 2 arguments passed to script."
# 所有连接起来的命令都返回true.
else
echo "Less than 2 arguments passed to script."
# 整个命令列表中至少有一个命令返回false.
fi
# 注意, "if [ ! -z $1 ]"也可以, 但它是有所假定的等价物.
# if [ -n $1 ] 这个不行.
# 然而, 如果加了引用就行了.
# if [ -n "$1" ] 这样就行了.
# 小心!
# 最好将你要测试的变量引用起来, 这么做是非常好的习惯.
# 下面这段代码与上面代码是等价的, 不过下面这段代码使用的是"纯粹"的if/then结构.
if [ ! -z "$1" ]
then
echo "Argument #1 = $1"
fi
if [ ! -z "$2" ]
then
echo "Argument #2 = $2"
echo "At least 2 arguments passed to script."
else
echo "Less than 2 arguments passed to script."
fi
# 这么写的话, 行数太多了, 没有"与列表"来的精简.
exit 0
%%%&&&ex64.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex65.sh@@@!!!************************************************************************************
#!/bin/bash
# delete.sh, 不是很聪明的文件删除方法.
# Usage: delete filename
E_BADARGS=65
if [ -z "$1" ]
then
echo "Usage: `basename $0` filename"
exit $E_BADARGS # 没有参数? 退出脚本.
else
file=$1 # 设置文件名.
fi
[ ! -f "$file" ] && echo "File /"$file/" not found. /
Cowardly refusing to delete a nonexistent file."
# 与列表, 在文件不存在时将会给出错误信息.
# 注意echo命令使用了一个续行符, 这样下一行的内容, 也会作为echo命令的参数.
[ ! -f "$file" ] || (rm -f $file; echo "File /"$file/" deleted.")
# 或列表, 如果文件存在, 那就删除此文件.
# 注意, 上边的两个逻辑相反.
# 与列表在true的情况下才执行, 或列表在false的时候才执行.
exit 0
%%%&&&ex65.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex66.sh@@@!!!************************************************************************************
#!/bin/bash
area[11]=23
area[13]=37
area[51]=UFOs
# 数组成员不一定非得是相邻或连续的.
# 数组的部分成员可以不被初始化.
# 数组中允许空缺元素.
# 实际上, 保存着稀疏数据的数组("稀疏数组")
#+ 在电子表格处理软件中是非常有用的.
echo -n "area[11] = "
echo ${area[11]} # 需要{大括号}.
echo -n "area[13] = "
echo ${area[13]}
echo "Contents of area[51] are ${area[51]}."
# 没被初始化的数组成员打印为空值(null变量).
echo -n "area[43] = "
echo ${area[43]}
echo "(area[43] unassigned)"
echo
# 两个数组元素的和被赋值给另一个数组元素
area[5]=`expr ${area[11]} + ${area[13]}`
echo "area[5] = area[11] + area[13]"
echo -n "area[5] = "
echo ${area[5]}
area[6]=`expr ${area[11]} + ${area[51]}`
echo "area[6] = area[11] + area[51]"
echo -n "area[6] = "
echo ${area[6]}
# 这里会失败, 是因为不允许整数与字符串相加.
echo; echo; echo
# -----------------------------------------------------------------
# 另一个数组, "area2".
# 另一种给数组变量赋值的方法...
# array_name=( XXX YYY ZZZ ... )
area2=( zero one two three four )
echo -n "area2[0] = "
echo ${area2[0]}
# 阿哈, 从0开始计算数组下标(也就是数组的第一个元素为[0], 而不是[1]).
echo -n "area2[1] = "
echo ${area2[1]} # [1]是数组的第2个元素.
# -----------------------------------------------------------------
echo; echo; echo
# -----------------------------------------------
# 第3个数组, "area3".
# 第3种给数组元素赋值的方法...
# array_name=([xx]=XXX [yy]=YYY ...)
area3=([17]=seventeen [24]=twenty-four)
echo -n "area3[17] = "
echo ${area3[17]}
echo -n "area3[24] = "
echo ${area3[24]}
# -----------------------------------------------
exit 0
%%%&&&ex66.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex67.sh@@@!!!************************************************************************************
#!/bin/bash
declare -a colors
# 脚本中所有的后续命令都会把
#+ 变量"colors"看作数组.
echo "Enter your favorite colors (separated from each other by a space)."
read -a colors # 至少需要键入3种颜色, 以便于后边的演示.
# 'read'命令的特殊选项,
#+ 允许给数组元素赋值.
echo
element_count=${#colors[@]}
# 提取数组元素个数的特殊语法.
# 用element_count=${#colors[*]}也一样.
#
# "@"变量允许在引用中存在单词分割(word splitting)
#+ (依靠空白字符来分隔变量).
#
# 这就好像"$@"和"$*"
#+ 在位置参数中的所表现出来的行为一样.
index=0
while [ "$index" -lt "$element_count" ]
do # 列出数组中的所有元素.
echo ${colors[$index]}
let "index = $index + 1"
# 或:
# index+=1
# 如果你运行的Bash版本是3.1以后的话, 才支持这种语法.
done
# 每个数组元素被列为单独的一行.
# 如果没有这种要求的话, 可以使用echo -n "${colors[$index]} "
#
# 也可以使用"for"循环来做:
# for i in "${colors[@]}"
# do
# echo "$i"
# done
# (感谢, S.C.)
echo
# 再次列出数组中的所有元素, 不过这次的做法更优雅.
echo ${colors[@]} # 用echo ${colors[*]}也行.
echo
# "unset"命令即可以删除数组数据, 也可以删除整个数组.
unset colors[1] # 删除数组的第2个元素.
# 作用等效于 colors[1]=
echo ${colors[@]} # 再次列出数组内容, 第2个元素没了.
unset colors # 删除整个数组.
# unset colors[*] 或
#+ unset colors[@] 都可以.
echo; echo -n "Colors gone."
echo ${colors[@]} # 再次列出数组内容, 内容为空.
exit 0
%%%&&&ex67.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex68.sh@@@!!!************************************************************************************
#!/bin/bash
# sieve.sh (ex68.sh)
# 埃拉托色尼素数筛子
# 找素数的经典算法.
# 在同等数值的范围内,
#+ 这个脚本运行的速度比C版本慢的多.
LOWER_LIMIT=1 # 从1开始.
UPPER_LIMIT=1000 # 到1000.
# (如果你时间很多的话 . . . 你可以将这个数值调的很高.)
PRIME=1
NON_PRIME=0
let SPLIT=UPPER_LIMIT/2
# 优化:
# 只需要测试中间到最大的值(为什么?).
# (译者注: 这个变量在脚本正文并没有被使用, 仅仅在107行之后的优化部分才使用.)
declare -a Primes
# Primes[]是个数组.
initialize ()
{
# 初始化数组.
i=$LOWER_LIMIT
until [ "$i" -gt "$UPPER_LIMIT" ]
do
Primes[i]=$PRIME
let "i += 1"
done
# 假定所有数组成员都是需要检查的(素数)
#+ 直到检查完成.
}
print_primes ()
{
# 打印出所有数组Primes[]中被标记为素数的元素.
i=$LOWER_LIMIT
until [ "$i" -gt "$UPPER_LIMIT" ]
do
if [ "${Primes[i]}" -eq "$PRIME" ]
then
printf "%8d" $i
# 每个数字打印前先打印8个空格, 在偶数列才打印.
fi
let "i += 1"
done
}
sift () # 查出非素数.
{
let i=$LOWER_LIMIT+1
# 我们都知道1是素数, 所以我们从2开始.
# (译者注: 从2开始并不是由于1是素数, 而是因为要去掉以后每个数的倍数, 感谢网友KevinChen.)
until [ "$i" -gt "$UPPER_LIMIT" ]
do
if [ "${Primes[i]}" -eq "$PRIME" ]
# 不要处理已经过滤过的数字(被标识为非素数).
then
t=$i
while [ "$t" -le "$UPPER_LIMIT" ]
do
let "t += $i "
Primes[t]=$NON_PRIME
# 标识为非素数.
done
fi
let "i += 1"
done
}
# ==============================================
# main ()
# 继续调用函数.
initialize
sift
print_primes
# 这里就是被称为结构化编程的东西.
# ==============================================
echo
exit 0
# -------------------------------------------------------- #
# 因为前面的'exit'语句, 所以后边的代码不会运行.
# 下边的代码, 是由Stephane Chazelas所编写的埃拉托色尼素数筛子的改进版本,
#+ 这个版本可以运行的快一些.
# 必须在命令行上指定参数(这个参数就是: 寻找素数的限制范围).
UPPER_LIMIT=$1 # 来自于命令行.
let SPLIT=UPPER_LIMIT/2 # 从中间值到最大值.
Primes=( '' $(seq $UPPER_LIMIT) )
i=1
until (( ( i += 1 ) > SPLIT )) # 仅需要从中间值检查.
do
if [[ -n $Primes[i] ]]
then
t=$i
until (( ( t += i ) > UPPER_LIMIT ))
do
Primes[t]=
done
fi
done
echo ${Primes[*]}
exit 0
%%%&&&ex68.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex69.sh@@@!!!************************************************************************************
#!/bin/bash
# 用非交互的方式来使用'vi'编辑一个文件.
# 模仿'sed'.
E_BADARGS=65
if [ -z "$1" ]
then
echo "Usage: `basename $0` filename"
exit $E_BADARGS
fi
TARGETFILE=$1
# 在文件中插入两行, 然后保存.
#--------Begin here document-----------#
vi $TARGETFILE <<x23LimitStringx23
i
This is line 1 of the example file.
This is line 2 of the example file.
^[
ZZ
x23LimitStringx23
#----------End here document-----------#
# 注意上边^[是一个转义符, 键入Ctrl+v <Esc>就行,
#+ 事实上它是<Esc>键;.
# Bram Moolenaar指出这种方法不能使用在'vim'上, (译者注: Bram Moolenaar是vim作者)
#+ 因为可能会存在终端相互影响的问题.
exit 0
%%%&&&ex69.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex6.sh@@@!!!************************************************************************************
#!/bin/bash
# 检查一些系统环境变量.
# 这是一种可以做一些预防性保护措施的好习惯.
# 比如, 如果$USER(用户在控制台上中的名字)没有被设置的话,
#+ 那么系统就会不认你.
: ${HOSTNAME?} ${USER?} ${HOME?} ${MAIL?}
echo
echo "Name of the machine is $HOSTNAME."
echo "You are $USER."
echo "Your home directory is $HOME."
echo "Your mail INBOX is located in $MAIL."
echo
echo "If you are reading this message,"
echo "critical environmental variables have been set."
echo
echo
# ------------------------------------------------------
# ${variablename?}结构
#+ 也能够检查脚本中变量的设置情况.
ThisVariable=Value-of-ThisVariable
# 注意, 顺便提一下,
#+ 这个字符串变量可能会被设置一些非法字符.
: ${ThisVariable?}
echo "Value of ThisVariable is $ThisVariable".
echo
echo
: ${ZZXy23AB?"ZZXy23AB has not been set."}
# 如果变量ZZXy23AB没有被设置的话,
#+ 那么这个脚本会打印一个错误信息, 然后结束.
# 你可以自己指定错误消息.
# : ${variablename?"ERROR MESSAGE"}
# 等价于: dummy_variable=${ZZXy23AB?}
# dummy_variable=${ZZXy23AB?"ZXy23AB has not been set."}
#
# echo ${ZZXy23AB?} >/dev/null
# 使用命令"set -u"来比较这些检查变量是否被设置的方法.
#
echo "You will not see this message, because script already terminated."
HERE=0
exit $HERE # 不会在这里退出.
# 事实上, 这个脚本将会以返回值1作为退出状态(echo $?).
%%%&&&ex6.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex70.sh@@@!!!************************************************************************************
#!/bin/bash
wall <<zzz23EndOfMessagezzz23
E-mail your noontime orders for pizza to the system administrator.
(Add an extra dollar for anchovy or mushroom topping.)
# 附加的消息文本放在这里.
# 注意: 'wall'命令会把注释行也打印出来.
zzz23EndOfMessagezzz23
# 当然, 更有效率的做法是:
# wall <message-file
# 然而, 将消息模版嵌入到脚本中
#+ 只是一种"小吃店"(译者注: 方便但是不卫生)的做法, 而且这种做法是一次性的.
exit 0
%%%&&&ex70.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex71a.sh@@@!!!************************************************************************************
#!/bin/bash
# 与之前的例子相同, 但是...
# - 选项对于here docutment来说,
#+ <<-可以抑制文档体前边的tab,
#+ 而*不*是空格.
cat <<-ENDOFMESSAGE
This is line 1 of the message.
This is line 2 of the message.
This is line 3 of the message.
This is line 4 of the message.
This is the last line of the message.
ENDOFMESSAGE
# 脚本在输出的时候左边将被刷掉.
# 就是说每行前边的tab将不会显示.
# 上边5行"消息"的前边都是tab, 而不是空格.
# 空格是不受<<-影响的.
# 注意, 这个选项对于*嵌在*中间的tab没作用.
exit 0
%%%&&&ex71a.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex71b.sh@@@!!!************************************************************************************
#!/bin/bash
# 一个使用'cat'命令的here document, 使用了参数替换.
# 不传命令行参数给它, ./scriptname
# 传一个命令行参数给它, ./scriptname Mortimer
# 传一个包含2个单词(用引号括起来)的命令行参数给它,
# ./scriptname "Mortimer Jones"
CMDLINEPARAM=1 # 所期望的最少的命令行参数个数.
if [ $# -ge $CMDLINEPARAM ]
then
NAME=$1 # 如果命令行参数超过1个,
#+ 那么就只取第一个参数.
else
NAME="John Doe" # 默认情况下, 如果没有命令行参数的话.
fi
RESPONDENT="the author of this fine script"
cat <<Endofmessage
Hello, there, $NAME.
Greetings to you, $NAME, from $RESPONDENT.
# This comment shows up in the output (why?).
Endofmessage
# 注意上边的空行也打印输出,
# 而上边那行"注释"当然也会打印到输出.
# (译者注: 这就是为什么不翻译那行注释的原因, 尽量保持代码的原样)
exit 0
%%%&&&ex71b.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex71c.sh@@@!!!************************************************************************************
#!/bin/bash
# 一个使用'cat'的here document, 但是禁用了参数替换.
NAME="John Doe"
RESPONDENT="the author of this fine script"
cat <<'Endofmessage'
Hello, there, $NAME.
Greetings to you, $NAME, from $RESPONDENT.
Endofmessage
# 如果"limit string"被引用或转义的话, 那么就禁用了参数替换.
# 下边的两种方式具有相同的效果.
# cat <<"Endofmessage"
# cat <</Endofmessage
exit 0
%%%&&&ex71c.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex71.sh@@@!!!************************************************************************************
#!/bin/bash
# 'echo'对于打印单行消息来说是非常好用的,
#+ 但是在打印消息块时可能就有点问题了.
# 'cat' here document可以解决这个限制.
cat <<End-of-message
-------------------------------------
This is line 1 of the message.
This is line 2 of the message.
This is line 3 of the message.
This is line 4 of the message.
This is the last line of the message.
-------------------------------------
End-of-message
# 用下边这行代替上边的第7行,
#+ cat > $Newfile <<End-of-message
#+ ^^^^^^^^^^
#+ 那么就会把输出写到文件$Newfile中, 而不是stdout.
exit 0
#--------------------------------------------
# 下边的代码不会运行, 因为上边有"exit 0".
# S.C. 指出下边代码也能够达到相同目的.
echo "-------------------------------------
This is line 1 of the message.
This is line 2 of the message.
This is line 3 of the message.
This is line 4 of the message.
This is the last line of the message.
-------------------------------------"
# 然而, 文本中可能不允许包含双引号, 除非它们被转义.
%%%&&&ex71.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex72.sh@@@!!!************************************************************************************
#!/bin/bash
# upload.sh
# 上传这一对文件(Filename.lsm, Filename.tar.gz)
#+ 到Sunsite/UNC (ibiblio.org)的incoming目录.
# Filename.tar.gz是自身的tar包.
# Filename.lsm是描述文件.
# Sunsite需要"lsm"文件, 否则就拒绝上传.
E_ARGERROR=65
if [ -z "$1" ]
then
echo "Usage: `basename $0` Filename-to-upload"
exit $E_ARGERROR
fi
Filename=`basename $1` # 从文件名中去掉目录字符串.
Server="ibiblio.org"
Directory="/incoming/Linux"
# 在这里也不一定非得将上边的参数写死在这个脚本中,
#+ 可以使用命令行参数的方法来替换.
Password="your.e-mail.address" # 可以修改成相匹配的密码.
ftp -n $Server <<End-Of-Session
# -n选项禁用自动登录.
user anonymous "$Password"
binary
bell # 在每个文件传输后, 响铃.
cd $Directory
put "$Filename.lsm"
put "$Filename.tar.gz"
bye
End-Of-Session
exit 0
%%%&&&ex72.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex73.sh@@@!!!************************************************************************************
#!/bin/bash
# 创建一个交换文件.
ROOT_UID=0 # Root用户的$UID为0.
E_WRONG_USER=65 # 不是root?
FILE=/swap
BLOCKSIZE=1024
MINBLOCKS=40
SUCCESS=0
# 这个脚本必须以root身份来运行.
if [ "$UID" -ne "$ROOT_UID" ]
then
echo; echo "You must be root to run this script."; echo
exit $E_WRONG_USER
fi
blocks=${1:-$MINBLOCKS} # 如果没在命令行上指定,
#+ 默认设置为40块.
# 上边这句等价于下面这个命令块.
# --------------------------------------------------
# if [ -n "$1" ]
# then
# blocks=$1
# else
# blocks=$MINBLOCKS
# fi
# --------------------------------------------------
if [ "$blocks" -lt $MINBLOCKS ]
then
blocks=$MINBLOCKS # 至少要有40块.
fi
echo "Creating swap file of size $blocks blocks (KB)."
dd if=/dev/zero of=$FILE bs=$BLOCKSIZE count=$blocks # 用零填充文件.
mkswap $FILE $blocks # 将其指定为交换文件(译者注: 或称为交换分区).
swapon $FILE # 激活交换文件.
echo "Swap file created and activated."
exit $SUCCESS
%%%&&&ex73.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex74.sh@@@!!!************************************************************************************
#!/bin/bash
# ex74.sh
# 这是一个错误脚本.
# 哪里出了错?
a=37
if [$a -gt 27 ]
then
echo $a
fi
exit 0
%%%&&&ex74.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex75.sh@@@!!!************************************************************************************
#!/bin/bash
# 这个脚本的目的是删除当前目录下的某些文件,
#+ 这些文件特指那些文件名包含空格的文件.
# 但是不能如我们所愿的那样工作.
# 为什么?
badname=`ls | grep ' '`
# 试试这个:
# echo "$badname"
rm "$badname"
exit 0
%%%&&&ex75.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex76.sh@@@!!!************************************************************************************
#!/bin/bash
# 使用trap来捕捉变量值.
trap 'echo Variable Listing --- a = $a b = $b' EXIT
# EXIT是脚本中exit命令所产生信号的名字.
#
# "trap"所指定的命令并不会马上执行,
#+ 只有接收到合适的信号, 这些命令才会执行.
echo "This prints before the /"trap/" --"
echo "even though the script sees the /"trap/" first."
echo
a=39
b=36
exit 0
# 注意, 即使注释掉上面的这行'exit'命令, 也不会产生什么不同的结果,
#+ 这是因为所有命令都执行完毕后, 不管怎么样, 脚本都会退出的.
%%%&&&ex76.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex77.sh@@@!!!************************************************************************************
#!/bin/bash
# 字符串扩展.
# Bash版本2中引入的特性.
# $'xxx'格式的字符串
#+ 具备解释里面标准转义字符的能力.
echo $'Ringing bell 3 times /a /a /a'
# 可能在某些终端中, 只会响一次铃.
echo $'Three form feeds /f /f /f'
echo $'10 newlines /n/n/n/n/n/n/n/n/n/n'
echo $'/102/141/163/150' # Bash
# 8进制的等价字符.
exit 0
%%%&&&ex77.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex78.sh@@@!!!************************************************************************************
#!/bin/bash
# 间接变量引用.
# 这种方法比较像C++中的引用特性.
a=letter_of_alphabet
letter_of_alphabet=z
echo "a = $a" # 直接引用.
echo "Now a = ${!a}" # 间接引用.
# ${!variable}表示法比老式的"eval var1=/$$var2"表示法高级的多.
echo
t=table_cell_3
table_cell_3=24
echo "t = ${!t}" # t = 24
table_cell_3=387
echo "Value of t changed to ${!t}" # 387
# 在引用数组成员或者引用表的时候, 这种方法非常有用,
#+ 还可以用来模拟多维数组.
# 如果有能够索引的选项(类似于指针的算术运算)
#+ 就更好了. 可惜.
exit 0
%%%&&&ex78.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex79.sh@@@!!!************************************************************************************
#!/bin/bash
# 纸牌:
# 处理4人打牌.
UNPICKED=0
PICKED=1
DUPE_CARD=99
LOWER_LIMIT=0
UPPER_LIMIT=51
CARDS_IN_SUIT=13
CARDS=52
declare -a Deck
declare -a Suits
declare -a Cards
# 使用一个3维数组来代替这3个一维数组来描述数据,
#+ 可以更容易实现, 而且可以增加可读性.
# 或许在Bash未来的版本上会支持多维数组.
initialize_Deck ()
{
i=$LOWER_LIMIT
until [ "$i" -gt $UPPER_LIMIT ]
do
Deck[i]=$UNPICKED # 将整副"牌"的每一张都设置为无人持牌的状态.
let "i += 1"
done
echo
}
initialize_Suits ()
{
Suits[0]=C #梅花
Suits[1]=D #方块
Suits[2]=H #红心
Suits[3]=S #黑桃
}
initialize_Cards ()
{
Cards=(2 3 4 5 6 7 8 9 10 J Q K A)
# 另一种初始化数组的方法.
}
pick_a_card ()
{
card_number=$RANDOM
let "card_number %= $CARDS"
if [ "${Deck[card_number]}" -eq $UNPICKED ]
then
Deck[card_number]=$PICKED
return $card_number
else
return $DUPE_CARD
fi
}
parse_card ()
{
number=$1
let "suit_number = number / CARDS_IN_SUIT"
suit=${Suits[suit_number]}
echo -n "$suit-"
let "card_no = number % CARDS_IN_SUIT"
Card=${Cards[card_no]}
printf %-4s $Card
# 使用整洁的列形式来打印每张牌.
}
seed_random () # 种子随机数产生器.
{ # 如果不这么做, 会发生什么?
seed=`eval date +%s`
let "seed %= 32766"
RANDOM=$seed
# 还有其他的方法
#+ 能够产生种子随机数么?
}
deal_cards ()
{
echo
cards_picked=0
while [ "$cards_picked" -le $UPPER_LIMIT ]
do
pick_a_card
t=$?
if [ "$t" -ne $DUPE_CARD ]
then
parse_card $t
u=$cards_picked+1
# 将数组索引改为从1(译者注: 数组都是从0开始索引的)开始(临时的). 为什么?
let "u %= $CARDS_IN_SUIT"
if [ "$u" -eq 0 ] # 内嵌的if/then条件测试.
then
echo
echo
fi
# 分手.
let "cards_picked += 1"
fi
done
echo
return 0
}
# 结构化编程:
# 将函数中的整个程序逻辑模块化.
#================
seed_random
initialize_Deck
initialize_Suits
initialize_Cards
deal_cards
#================
exit 0
# 练习1:
# 完整的注释这个脚本.
# 练习2:
# 添加一个例程(函数)按照花色打印出每手牌.
# 如果你喜欢, 可以添加任何你想要添加的代码.
# 练习3:
# 简化并理顺脚本逻辑.
%%%&&&ex79.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex7.sh@@@!!!************************************************************************************
#!/bin/bash
var1=abcd-1234-defg
echo "var1 = $var1"
t=${var1#*-*}
echo "var1 (with everything, up to and including first - stripped out) = $t"
# t=${var1#*-} 也一样,
#+ 因为#匹配最短的字符串,
#+ 同时*匹配任意前缀, 包括空字符串.
# (感谢, Stephane Chazelas, 指出这点.)
t=${var1##*-*}
echo "If var1 contains a /"-/", returns empty string... var1 = $t"
t=${var1%*-*}
echo "var1 (with everything from the last - on stripped out) = $t"
echo
# -------------------------------------------
path_name=/home/bozo/ideas/thoughts.for.today
# -------------------------------------------
echo "path_name = $path_name"
t=${path_name##/*/}
echo "path_name, stripped of prefixes = $t"
# 在这个特例中, 与 t=`basename $path_name` 效果相同.
# t=${path_name%/}; t=${t##*/} 是更一般的解决方法.
#+ 但有时还是会失败.
# 如果$path_name以一个换行符结尾的话, 那么 `basename $path_name` 就不能正常工作了,
#+ 但是上边的表达式可以.
# (感谢, S.C.)
t=${path_name%/*.*}
# 与 t=`dirname $path_name` 效果相同.
echo "path_name, stripped of suffixes = $t"
# 在某些情况下将失效, 比如 "../", "/foo", # "foo/", "/".
# 删除后缀, 尤其是在basename没有后缀的情况下,
#+ 但是dirname可以, 不过这同时也使问题复杂化了.
# (感谢, S.C.)
echo
t=${path_name:11}
echo "$path_name, with first 11 chars stripped off = $t"
t=${path_name:11:5}
echo "$path_name, with first 11 chars stripped off, length 5 = $t"
echo
t=${path_name/bozo/clown}
echo "$path_name with /"bozo/" replaced by /"clown/" = $t"
t=${path_name/today/}
echo "$path_name with /"today/" deleted = $t"
t=${path_name//o/O}
echo "$path_name with all o's capitalized = $t"
t=${path_name//o/}
echo "$path_name with all o's deleted = $t"
exit 0
%%%&&&ex7.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex8.sh@@@!!!************************************************************************************
#!/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.
%%%&&&ex8.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ex9.sh@@@!!!************************************************************************************
#!/bin/bash
# 变量赋值和替换
a=375
hello=$a
#-------------------------------------------------------------------------
# 强烈注意, 在赋值的的时候, 等号前后一定不要有空格.
# 如果出现空格会怎么样?
# "VARIABLE =value"
# ^
#% 脚本将尝试运行一个"VARIABLE"的命令, 带着一个"=value"参数.
# "VARIABLE= value"
# ^
#% 脚本将尝试运行一个"value"的命令,
#+ 并且带着一个被赋值成""的环境变量"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" 将给出不同的结果.
# ===============================================================
# 引用一个变量将保留其中的空白, 当然, 如果是变量替换就不会保留了.
# ===============================================================
echo
echo '$hello' # $hello
# ^ ^
# 全引用的作用将会导致"$"被解释为单独的字符,
#+ 而不是变量前缀.
# 注意这两种引用所产生的不同的效果.
hello= # 设置为空值.
echo "/$hello (null value) = $hello"
# 注意设置一个变量为null, 与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"
# ^ ^
# 如果在变量中存在空白, If there is whitespace embedded within a variable,
#+ 那么就必须加上引用.
# other_numbers=1 2 3 # 给出一个错误消息.
echo "numbers = $numbers"
echo "other_numbers = $other_numbers" # other_numbers = 1 2 3
# 不过也可以采用将空白转义的方法.
mixed_bag=2/ ---/ Whatever
# ^ ^ 在转义符后边的空格(/).
echo "$mixed_bag" # 2 --- Whatever
echo; echo
echo "uninitialized_variable = $uninitialized_variable"
# Uninitialized变量为null(就是没有值).
uninitialized_variable= # 声明, 但是没有初始化这个变量,
#+ 其实和前边设置为空值的作用是一样的.
echo "uninitialized_variable = $uninitialized_variable"
# 还是一个空值.
uninitialized_variable=23 # 赋值.
unset uninitialized_variable # Unset这个变量.
echo "uninitialized_variable = $uninitialized_variable"
# 还是一个空值.
echo
exit 0
%%%&&&ex9.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@exercising-dd.sh@@@!!!************************************************************************************
#!/bin/bash
# exercising-dd.sh
# 由Stephane Chazelas编写.
# 本文作者做了少量修改.
input_file=$0 # 脚本自身.
output_file=log.txt
n=3
p=5
dd if=$input_file of=$output_file bs=1 skip=$((n-1)) count=$((p-n+1)) 2> /dev/null
# 从脚本中把位置n到p的字符提取出来.
# -------------------------------------------------------
echo -n "hello world" | dd cbs=1 conv=unblock 2> /dev/null
# 垂直地echo "hello world".
exit 0
%%%&&&exercising-dd.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@factr.sh@@@!!!************************************************************************************
#!/bin/bash
# factr.sh: 分解约数
MIN=2 # 如果比这个数小就不行了.
E_NOARGS=65
E_TOOSMALL=66
if [ -z $1 ]
then
echo "Usage: $0 number"
exit $E_NOARGS
fi
if [ "$1" -lt "$MIN" ]
then
echo "Number to factor must be $MIN or greater."
exit $E_TOOSMALL
fi
# 练习: 添加类型检查(防止非整型的参数).
echo "Factors of $1:"
# ---------------------------------------------------------------------------------
echo "$1[p]s2[lip/dli%0=1dvsr]s12sid2%0=13sidvsr[dli%0=1lrli2+dsi!>.]ds.xd1<2" | dc
# ---------------------------------------------------------------------------------
# 上边这行代码是Michel Charpentier编写的<charpov@cs.unh.edu>.
# 在此使用经过授权(感谢).
exit 0
%%%&&&factr.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@fc4upd.sh@@@!!!************************************************************************************
#!/bin/bash
# fc4upd.sh
# 脚本作者: Frank Wang.
# 本书作者作了少量修改.
# 授权在本书中使用.
# 使用rsync命令从镜像站点上下载Fedora 4的更新.
# 为了节省空间, 如果有多个版本存在的话,
#+ 只下载最新的包.
URL=rsync://distro.ibiblio.org/fedora-linux-core/updates/
# URL=rsync://ftp.kddilabs.jp/fedora/core/updates/
# URL=rsync://rsync.planetmirror.com/fedora-linux-core/updates/
DEST=${1:-/var/www/html/fedora/updates/}
LOG=/tmp/repo-update-$(/bin/date +%Y-%m-%d).txt
PID_FILE=/var/run/${0##*/}.pid
E_RETURN=65 # 某些意想不到的错误.
# 一般rsync选项
# -r: 递归下载
# -t: 保存时间
# -v: verbose
OPTS="-rtv --delete-excluded --delete-after --partial"
# rsync include模式
# 开头的"/"会导致绝对路径名匹配.
INCLUDE=(
"/4/i386/kde-i18n-Chinese*"
# ^ ^
# 双引号是必须的, 用来防止globbing.
)
# rsync exclude模式
# 使用"#"临时注释掉一些不需要的包.
EXCLUDE=(
/1
/2
/3
/testing
/4/SRPMS
/4/ppc
/4/x86_64
/4/i386/debug
"/4/i386/kde-i18n-*"
"/4/i386/openoffice.org-langpack-*"
"/4/i386/*i586.rpm"
"/4/i386/GFS-*"
"/4/i386/cman-*"
"/4/i386/dlm-*"
"/4/i386/gnbd-*"
"/4/i386/kernel-smp*"
# "/4/i386/kernel-xen*"
# "/4/i386/xen-*"
)
init () {
# 让管道命令返回可能的rsync错误, 比如, 网络延时(stalled network).
set -o pipefail
TMP=${TMPDIR:-/tmp}/${0##*/}.$$ # 保存精炼的下载列表.
trap "{
rm -f $TMP 2>/dev/null
}" EXIT # 删除存在的临时文件.
}
check_pid () {
# 检查进程是否存在.
if [ -s "$PID_FILE" ]; then
echo "PID file exists. Checking ..."
PID=$(/bin/egrep -o "^[[:digit:]]+" $PID_FILE)
if /bin/ps --pid $PID &>/dev/null; then
echo "Process $PID found. ${0##*/} seems to be running!"
/usr/bin/logger -t ${0##*/} /
"Process $PID found. ${0##*/} seems to be running!"
exit $E_RETURN
fi
echo "Process $PID not found. Start new process . . ."
fi
}
# 根据上边的模式,
#+ 设置整个文件的更新范围, 从root或$URL开始.
set_range () {
include=
exclude=
for p in "${INCLUDE[@]}"; do
include="$include --include /"$p/""
done
for p in "${EXCLUDE[@]}"; do
exclude="$exclude --exclude /"$p/""
done
}
# 获得并提炼rsync更新列表.
get_list () {
echo $$ > $PID_FILE || {
echo "Can't write to pid file $PID_FILE"
exit $E_RETURN
}
echo -n "Retrieving and refining update list . . ."
# 获得列表 -- 作为单个命令来运行rsync的话需要'eval'.
# $3和$4是文件创建的日期和时间.
# $5是完整的包名字.
previous=
pre_file=
pre_date=0
eval /bin/nice /usr/bin/rsync /
-r $include $exclude $URL | /
egrep '^dr.x|^-r' | /
awk '{print $3, $4, $5}' | /
sort -k3 | /
{ while read line; do
# 获得这段运行的秒数, 过滤掉不用的包.
cur_date=$(date -d "$(echo $line | awk '{print $1, $2}')" +%s)
# echo $cur_date
# 取得文件名.
cur_file=$(echo $line | awk '{print $3}')
# echo $cur_file
# 如果可能的话, 从文件名中取得rpm的包名字.
if [[ $cur_file == *rpm ]]; then
pkg_name=$(echo $cur_file | sed -r -e /
's/(^([^_-]+[_-])+)[[:digit:]]+/..*[_-].*$//1/')
else
pkg_name=
fi
# echo $pkg_name
if [ -z "$pkg_name" ]; then # 如果不是一个rpm文件,
echo $cur_file >> $TMP #+ 然后添加到下载列表里.
elif [ "$pkg_name" != "$previous" ]; then # 发现一个新包.
echo $pre_file >> $TMP # 输出最新的文件.
previous=$pkg_name # 保存当前状态.
pre_date=$cur_date
pre_file=$cur_file
elif [ "$cur_date" -gt "$pre_date" ]; then # 如果是相同的包, 但是这个包更新一些,
pre_date=$cur_date #+ 那么就更新最新的.
pre_file=$cur_file
fi
done
echo $pre_file >> $TMP # TMP现在包含所有
#+ 提炼过的列表.
# echo "subshell=$BASH_SUBSHELL"
} # 这里的大括号是为了让最后这句"echo $pre_file >> $TMP"
# 也能与整个循环一起放到同一个子shell ( 1 )中.
RET=$? # 取得管道命令的返回状态.
[ "$RET" -ne 0 ] && {
echo "List retrieving failed with code $RET"
exit $E_RETURN
}
echo "done"; echo
}
# 真正的rsync下载部分.
get_file () {
echo "Downloading..."
/bin/nice /usr/bin/rsync /
$OPTS /
--filter "merge,+/ $TMP" /
--exclude '*' /
$URL $DEST /
| /usr/bin/tee $LOG
RET=$?
# --filter merge,+/ 对于这个目的来说, 这句是至关重要的.
# + 修饰语意为着包含, / 意味着绝对路径.
# 然后$TMP中排过序的列表将会包含升序的路径名,
#+ 并从"简化的流程"(shortcutting the circuit)中阻止下边的 --exclude '*'.
echo "Done"
rm -f $PID_FILE 2>/dev/null
return $RET
}
# -------
# Main
init
check_pid
set_range
get_list
get_file
RET=$?
# -------
if [ "$RET" -eq 0 ]; then
/usr/bin/logger -t ${0##*/} "Fedora update mirrored successfully."
else
/usr/bin/logger -t ${0##*/} "Fedora update mirrored with failure code: $RET"
fi
exit $RET
%%%&&&fc4upd.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@fifo.sh@@@!!!************************************************************************************
#!/bin/bash
# ==> Script by James R. Van Zandt, and used here with his permission.
# ==> Comments added by author of this document.
HERE=`uname -n` # ==> hostname
THERE=bilbo
echo "starting remote backup to $THERE at `date +%r`"
# ==> `date +%r` returns time in 12-hour format, i.e. "08:08:34 PM".
# make sure /pipe really is a pipe and not a plain file
rm -rf /pipe
mkfifo /pipe # ==> Create a "named pipe", named "/pipe".
# ==> 'su xyz' runs commands as user "xyz".
# ==> 'ssh' invokes secure shell (remote login client).
su xyz -c "ssh $THERE /"cat >/home/xyz/backup/${HERE}-daily.tar.gz/" < /pipe"&
cd /
tar -czf - bin boot dev etc home info lib man root sbin share usr var >/pipe
# ==> Uses named pipe, /pipe, to communicate between processes:
# ==> 'tar/gzip' writes to /pipe and 'ssh' reads from /pipe.
# ==> The end result is this backs up the main directories, from / on down.
# ==> What are the advantages of a "named pipe" in this situation,
# ==>+ as opposed to an "anonymous pipe", with |?
# ==> Will an anonymous pipe even work here?
exit 0
%%%&&&fifo.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@file-comparison.sh@@@!!!************************************************************************************
#!/bin/bash
ARGS=2 # 脚本需要两个参数.
E_BADARGS=65
E_UNREADABLE=66
if [ $# -ne "$ARGS" ]
then
echo "Usage: `basename $0` file1 file2"
exit $E_BADARGS
fi
if [[ ! -r "$1" || ! -r "$2" ]]
then
echo "Both files to be compared must exist and be readable."
exit $E_UNREADABLE
fi
cmp $1 $2 &> /dev/null # /dev/null将会禁止"cmp"命令的输出.
# cmp -s $1 $2 与上边这句的结果相同("-s"选项是禁止输出(silent)标志)
# 感谢Anders Gustavsson指出这点.
#
# 使用'diff'命令也可以, 比如, diff $1 $2 &> /dev/null
if [ $? -eq 0 ] # 测试"cmp"命令的退出状态.
then
echo "File /"$1/" is identical to file /"$2/"."
else
echo "File /"$1/" differs from file /"$2/"."
fi
exit 0
%%%&&&file-comparison.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@file-info.sh@@@!!!************************************************************************************
#!/bin/bash
# fileinfo.sh
FILES="/usr/sbin/accept
/usr/sbin/pwck
/usr/sbin/chroot
/usr/bin/fakefile
/sbin/badblocks
/sbin/ypbind" # 这是你所关心的文件列表.
# 扔进去一个假文件, /usr/bin/fakefile.
echo
for file in $FILES
do
if [ ! -e "$file" ] # 检查文件是否存在.
then
echo "$file does not exist."; echo
continue # 继续下一个.
fi
ls -l $file | awk '{ print $9 " file size: " $5 }' # 打印两个域.
whatis `basename $file` # 文件信息.
# 注意whatis数据库需要提前建立好.
# 要想达到这个目的, 以root身份运行/usr/bin/makewhatis.
echo
done
exit 0
%%%&&&file-info.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@file-integrity.sh@@@!!!************************************************************************************
#!/bin/bash
# file-integrity.sh: 检查一个给定目录下的文件
# 是否被改动了.
E_DIR_NOMATCH=70
E_BAD_DBFILE=71
dbfile=File_record.md5
# 存储记录的文件名(数据库文件).
set_up_database ()
{
echo ""$directory"" > "$dbfile"
# 把目录名写到文件的第一行.
md5sum "$directory"/* >> "$dbfile"
# 在文件中附上md5 checksum和filename.
}
check_database ()
{
local n=0
local filename
local checksum
# ------------------------------------------- #
# 这个文件检查其实是不必要的,
#+ 但是能更安全一些.
if [ ! -r "$dbfile" ]
then
echo "Unable to read checksum database file!"
exit $E_BAD_DBFILE
fi
# ------------------------------------------- #
while read record[n]
do
directory_checked="${record[0]}"
if [ "$directory_checked" != "$directory" ]
then
echo "Directories do not match up!"
# 换个目录试一下.
exit $E_DIR_NOMATCH
fi
if [ "$n" -gt 0 ] # 不是目录名.
then
filename[n]=$( echo ${record[$n]} | awk '{ print $2 }' )
# md5sum向后写记录,
#+ 先写checksum, 然后写filename.
checksum[n]=$( md5sum "${filename[n]}" )
if [ "${record[n]}" = "${checksum[n]}" ]
then
echo "${filename[n]} unchanged."
elif [ "`basename ${filename[n]}`" != "$dbfile" ]
# 跳过checksum数据库文件,
#+ 因为在每次调用脚本它都会被修改.
# ---
# 这不幸的意味着当我们在$PWD中运行这个脚本时侯,
#+ 篡改这个checksum数
#+ 据库文件将不会被检测出来.
# 练习: 修正这个问题.
then
echo "${filename[n]} : CHECKSUM ERROR!"
# 从上次的检查之后, 文件已经被修改.
fi
fi
let "n+=1"
done <"$dbfile" # 从checksum数据库文件中读.
}
# =================================================== #
# main ()
if [ -z "$1" ]
then
directory="$PWD" # 如果没指定参数的话,
else #+ 那么就使用当前的工作目录.
directory="$1"
fi
clear # 清屏.
echo " Running file integrity check on $directory"
echo
# ------------------------------------------------------------------ #
if [ ! -r "$dbfile" ] # 是否需要建立数据库文件?
then
echo "Setting up database file, /""$directory"/"$dbfile"/"."; echo
set_up_database
fi
# ------------------------------------------------------------------ #
check_database # 调用主要处理函数.
echo
# 你可能想把这个脚本的输出重定向到文件中,
#+ 尤其在这个目录中有很多文件的时候.
exit 0
# 如果要对数量非常多的文件做完整性检查,
#+ 可以考虑一下"Tripwire"包,
#+ http://sourceforge.net/projects/tripwire/.
%%%&&&file-integrity.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@findstring.sh@@@!!!************************************************************************************
#!/bin/bash
# findstring.sh:
# 在一个指定目录的所有文件中查找一个特定的字符串.
directory=/usr/bin/
fstring="Free Software Foundation" # 查看哪个文件中包含FSF.
for file in $( find $directory -type f -name '*' | sort )
do
strings -f $file | grep "$fstring" | sed -e "s%$directory%%"
# 在"sed"表达式中,
#+ 我们必须替换掉正常的替换分隔符"/",
#+ 因为"/"碰巧是我们需要过滤的字符串之一.
# 如果不用"%"代替"/"作为分隔符,那么这个操作将失败,并给出一个错误消息.(试一试).
done
exit 0
# 练习 (很简单):
# ---------------
# 转换这个脚本, 用命令行参数
#+ 代替内部用的$directory和$fstring.
%%%&&&findstring.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@for-loopcmd.sh@@@!!!************************************************************************************
#!/bin/bash
# for-loopcmd.sh: 带[list]的for循环,
#+ [list]是由命令替换所产生的.
NUMBERS="9 7 3 8 37.53"
for number in `echo $NUMBERS` # for number in 9 7 3 8 37.53
do
echo -n "$number "
done
echo
exit 0
%%%&&&for-loopcmd.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@for-loopc.sh@@@!!!************************************************************************************
#!/bin/bash
# 两种循环到10的方法.
echo
# 标准语法.
for a in 1 2 3 4 5 6 7 8 9 10
do
echo -n "$a "
done
echo; echo
# +==========================================+
# 现在, 让我们用C风格语法来做相同的事情.
LIMIT=10
for ((a=1; a <= LIMIT ; a++)) # 双圆括号, 并且"LIMIT"变量前面没有"$".
do
echo -n "$a "
done # 这是一个借用'ksh93'的结构.
echo; echo
# +=========================================================================+
# 让我们使用C语言的"逗号操作符", 来同时增加两个变量的值.
for ((a=1, b=1; a <= LIMIT ; a++, b++)) # 逗号将同时进行两条操作.
do
echo -n "$a-$b "
done
echo; echo
exit 0
%%%&&&for-loopc.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@ftpget.sh@@@!!!************************************************************************************
#! /bin/sh
# $Id: ftpget,v 1.2 91/05/07 21:15:43 moraes Exp $
# Script to perform batch anonymous ftp. Essentially converts a list of
# of command line arguments into input to ftp.
# ==> This script is nothing but a shell wrapper around "ftp" . . .
# Simple, and quick - written as a companion to ftplist
# -h specifies the remote host (default prep.ai.mit.edu)
# -d specifies the remote directory to cd to - you can provide a sequence
# of -d options - they will be cd'ed to in turn. If the paths are relative,
# make sure you get the sequence right. Be careful with relative paths -
# there are far too many symlinks nowadays.
# (default is the ftp login directory)
# -v turns on the verbose option of ftp, and shows all responses from the
# ftp server.
# -f remotefile[:localfile] gets the remote file into localfile
# -m pattern does an mget with the specified pattern. Remember to quote
# shell characters.
# -c does a local cd to the specified directory
# For example,
# ftpget -h expo.lcs.mit.edu -d contrib -f xplaces.shar:xplaces.sh /
# -d ../pub/R3/fixes -c ~/fixes -m 'fix*'
# will get xplaces.shar from ~ftp/contrib on expo.lcs.mit.edu, and put it in
# xplaces.sh in the current working directory, and get all fixes from
# ~ftp/pub/R3/fixes and put them in the ~/fixes directory.
# Obviously, the sequence of the options is important, since the equivalent
# commands are executed by ftp in corresponding order
#
# Mark Moraes <moraes@csri.toronto.edu>, Feb 1, 1989
#
# ==> These comments added by author of this document.
# PATH=/local/bin:/usr/ucb:/usr/bin:/bin
# export PATH
# ==> Above 2 lines from original script probably superfluous.
E_BADARGS=65
TMPFILE=/tmp/ftp.$$
# ==> Creates temp file, using process id of script ($$)
# ==> to construct filename.
SITE=`domainname`.toronto.edu
# ==> 'domainname' similar to 'hostname'
# ==> May rewrite this to parameterize this for general use.
usage="Usage: $0 [-h remotehost] [-d remotedirectory]... [-f remfile:localfile]... /
[-c localdirectory] [-m filepattern] [-v]"
ftpflags="-i -n"
verbflag=
set -f # So we can use globbing in -m
set x `getopt vh:d:c:m:f: $*`
if [ $? != 0 ]; then
echo $usage
exit $E_BADARGS
fi
shift
trap 'rm -f ${TMPFILE} ; exit' 0 1 2 3 15
# ==> Delete tempfile in case of abnormal exit from script.
echo "user anonymous ${USER-gnu}@${SITE} > ${TMPFILE}"
# ==> Added quotes (recommended in complex echoes).
echo binary >> ${TMPFILE}
for i in $* # ==> Parse command line args.
do
case $i in
-v) verbflag=-v; echo hash >> ${TMPFILE}; shift;;
-h) remhost=$2; shift 2;;
-d) echo cd $2 >> ${TMPFILE};
if [ x${verbflag} != x ]; then
echo pwd >> ${TMPFILE};
fi;
shift 2;;
-c) echo lcd $2 >> ${TMPFILE}; shift 2;;
-m) echo mget "$2" >> ${TMPFILE}; shift 2;;
-f) f1=`expr "$2" : "/([^:]*/).*"`; f2=`expr "$2" : "[^:]*:/(.*/)"`;
echo get ${f1} ${f2} >> ${TMPFILE}; shift 2;;
--) shift; break;;
esac
# ==> 'lcd' and 'mget' are ftp commands. See "man ftp" . . .
done
if [ $# -ne 0 ]; then
echo $usage
exit $E_BADARGS
# ==> Changed from "exit 2" to conform with style standard.
fi
if [ x${verbflag} != x ]; then
ftpflags="${ftpflags} -v"
fi
if [ x${remhost} = x ]; then
remhost=prep.ai.mit.edu
# ==> Change to match appropriate ftp site.
fi
echo quit >> ${TMPFILE}
# ==> All commands saved in tempfile.
ftp ${ftpflags} ${remhost} < ${TMPFILE}
# ==> Now, tempfile batch processed by ftp.
rm -f ${TMPFILE}
# ==> Finally, tempfile deleted (you may wish to copy it to a logfile).
# ==> Exercises:
# ==> ---------
# ==> 1) Add error checking.
# ==> 2) Add bells & whistles.
%%%&&&ftpget.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@func-cmdlinearg.sh@@@!!!************************************************************************************
#!/bin/bash
# func-cmdlinearg.sh
# 调用这个脚本, 并且带一个命令行参数.
#+ 类似于 $0 arg1.
func ()
{
echo "$1"
}
echo "First call to function: no arg passed."
echo "See if command-line arg is seen."
func
# 不行! 命令行参数不可见.
echo "============================================================"
echo
echo "Second call to function: command-line arg passed explicitly."
func $1
# 现在可见了!
exit 0
%%%&&&func-cmdlinearg.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@gcd.sh@@@!!!************************************************************************************
#!/bin/bash
# gcd.sh: 最大公约数
# 使用Euclid的算法
# 两个整数的"最大公约数" (gcd),
#+ 就是两个整数所能够同时整除的最大的数.
# Euclid算法采用连续除法.
# 在每一次循环中,
#+ 被除数 <--- 除数
#+ 除数 <--- 余数
#+ 直到 余数 = 0.
#+ 在最后一次循环中, gcd = 被除数.
#
# 关于Euclid算法的更精彩的讨论, 可以到
#+ Jim Loy的站点, 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 #+ 在这里, 哪个值给的大都没关系.
# 为什么没关系?
remainder=1 # 如果在循环中使用了未初始化的变量,
#+ 那么在第一次循环中,
#+ 它将会产生一个错误消息.
until [ "$remainder" -eq 0 ]
do
let "remainder = $dividend % $divisor"
dividend=$divisor # 现在使用两个最小的数来重复.
divisor=$remainder
done # Euclid的算法
} # Last $dividend is the gcd.
gcd $1 $2
echo; echo "GCD of $1 and $2 = $dividend"; echo
# Exercise :
# --------
# 检查传递进来的命令行参数来确保它们都是整数.
#+ 如果不是整数, 那就给出一个适当的错误消息并退出脚本.
exit 0
%%%&&&gcd.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@generate-script.sh@@@!!!************************************************************************************
#!/bin/bash
# generate-script.sh
# 这个脚本的诞生基于Albert Reiner的一个主意.
OUTFILE=generated.sh # 所产生文件的名字.
# -----------------------------------------------------------
# 'Here document包含了需要产生的脚本的代码.
(
cat <<'EOF'
#!/bin/bash
echo "This is a generated shell script."
# Note that since we are inside a subshell,
#+ we can't access variables in the "outside" script.
echo "Generated file will be named: $OUTFILE"
# Above line will not work as normally expected
#+ because parameter expansion has been disabled.
# Instead, the result is literal output.
a=7
b=3
let "c = $a * $b"
echo "c = $c"
exit 0
EOF
) > $OUTFILE
# -----------------------------------------------------------
# 将'limit string'引用起来将会阻止上边
#+ here document消息体中的变量扩展.
# 这会使得输出文件中的内容保持here document消息体中的原文.
if [ -f "$OUTFILE" ]
then
chmod 755 $OUTFILE
# 让所产生的文件具有可执行权限.
else
echo "Problem in creating file: /"$OUTFILE/""
fi
# 这个方法也可以用来产生
#+ C程序代码, Perl程序代码, Python程序代码, makefile,
#+ 和其他的一些类似的代码.
# (译者注: 中间一段没译的注释将会被here document打印出来)
exit 0
%%%&&&generate-script.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
***@@@getopt-simple.sh@@@!!!************************************************************************************
#!/bin/bash
# getopt-simple.sh
# 作者: Chris Morgan
# 已经经过授权, 可以使用在本书中.
getopt_simple()
{
echo "getopt_simple()"
echo "Parameters are '$*'"
until [ -z "$1" ]
do
echo "Processing parameter of: '$1'"
if [ ${1:0:1} = '/' ]
then
tmp=${1:1} # 去掉开头的'/' . . .
parameter=${tmp%%=*} # 提取参数名.
value=${tmp##*=} # 提取参数值.
echo "Parameter: '$parameter', value: '$value'"
eval $parameter=$value
fi
shift
done
}
# 把所有选项传给函数getopt_simple().
getopt_simple $*
echo "test is '$test'"
echo "test2 is '$test2'"
exit 0
---
sh getopt_example.sh /test=value1 /test2=value2
Parameters are '/test=value1 /test2=value2'
Processing parameter of: '/test=value1'
Parameter: 'test', value: 'value1'
Processing parameter of: '/test2=value2'
Parameter: 'test2', value: 'value2'
test is 'value1'
test2 is 'value2'
%%%&&&getopt-simple.sh&&&%%%>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>