12.6. 通讯命令

下边命令中的某几个命令你会在 "追踪垃圾邮件" 练习中找到其用法, 用来进行网络数据的转换和分析.

信息与统计

host

通过名字或 IP 地址来搜索一个互联网主机的信息, 使用 DNS.

 bash$ host surfacemail.com
 surfacemail.com. has address 202.92.42.236
 	      

ipcalc

显示一个主机 IP 信息. 使用 -h 选项, ipcalc 将会做一个 DNS 的反向查询, 通过 IP 地址找到主机(服务器)名.

 bash$ ipcalc -h 202.92.42.236
 HOSTNAME=surfacemail.com
 	      

nslookup

通过 IP 地址在一个主机上做一个互联网的 "名字服务查询". 事实上这与 ipcalc -hdig -x 等价. 这个命令既可以交互运行也可以非交互运行, 换句话说, 就是在脚本中运行.

nslookup 命令据说已经慢慢被"忽视"了, 但是它还是有它的用处.

 bash$ nslookup -sil 66.97.104.180
 nslookup kuhleersparnis.ch
 Server:         135.116.137.2
 Address:        135.116.137.2#53

 Non-authoritative answer:
 Name:   kuhleersparnis.ch
 	      

dig

域信息查询. 与 nslookup 很相似, dig 在一个主机上做一个互联网的 "名字服务查询". 这个命令既可以交互运行也可以非交互运行, 换句话说, 就是在脚本中运行.

下边是一些 dig 命令有趣的选项, +time=N 选项用来设置查询超时为 N 秒, +nofail 选项用来持续查询服务器直到收到一个响应, -x 选项会做反向地址查询.

比较下边这3个命令的输出, dig -x , ipcalc -hnslookup.

 bash$ dig -x 81.9.6.2
 ;; Got answer:
 ;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 11649
 ;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 0

 ;; QUESTION SECTION:
 ;2.6.9.81.in-addr.arpa.         IN      PTR

 ;; AUTHORITY SECTION:
 6.9.81.in-addr.arpa.    3600    IN      SOA     ns.eltel.net. noc.eltel.net.
 2002031705 900 600 86400 3600

 ;; Query time: 537 msec
 ;; SERVER: 135.116.137.2#53(135.116.137.2)
 ;; WHEN: Wed Jun 26 08:35:24 2002
 ;; MSG SIZE  rcvd: 91
 	      


Example 12-36. 查找滥用的连接来报告垃圾邮件发送者

   1 #!/bin/bash
   2 # spam-lookup.sh: 查找滥用的连接来报告垃圾邮件发送者.
   3 # 感谢 Michael Zick.
   4 
   5 # 检查命令行参数.
   6 ARGCOUNT=1
   7 E_WRONGARGS=65
   8 if [ $# -ne "$ARGCOUNT" ]
   9 then
  10   echo "Usage: `basename $0` domain-name"
  11   exit $E_WRONGARGS
  12 fi
  13 
  14 
  15 dig +short $1.contacts.abuse.net -c in -t txt
  16 # 也试试:
  17 #     dig +nssearch $1
  18 #     尽量找到 "可信赖的名字服务器" 并且显示 SOA 记录.
  19 
  20 # 下边这句也可以:
  21 #     whois -h whois.abuse.net $1
  22 #           ^^ ^^^^^^^^^^^^^^^  指定主机.
  23 #     使用这个命令也可以查找多个垃圾邮件发送者, 比如:"
  24 #     whois -h whois.abuse.net $spamdomain1 $spamdomain2 . . .
  25 
  26 
  27 #  练习:
  28 #  -----
  29 #  扩展这个脚本的功能,
  30 #+ 让它可以自动发送 e-mail 来通知
  31 #+ 需要对此负责的 ISP 的联系地址.
  32 #  暗示: 使用 "mail" 命令.
  33 
  34 exit $?
  35 
  36 # spam-lookup.sh chinatietong.com
  37 #                一个已知的垃圾邮件域.(译者: 中国铁通. . .)
  38 
  39 # "crnet_mgr@chinatietong.com"
  40 # "crnet_tec@chinatietong.com"
  41 # "postmaster@chinatietong.com"
  42 
  43 
  44 #  如果想找到这个脚本的一个更详尽的版本,
  45 #+ 请访问 SpamViz 的主页, http://www.spamviz.net/index.html.


Example 12-37. 分析一个垃圾邮件域<rojy bug>

   1 #! /bin/bash
   2 # is-spammer.sh: 鉴别一个垃圾邮件域
   3 
   4 # $Id: is-spammer, v 1.4 2004/09/01 19:37:52 mszick Exp $
   5 # 上边这行是 RCS ID 信息.
   6 #
   7 #  这是附件中捐献脚本 is_spammer.bash
   8 #+ 的一个简单版本.
   9 
  10 # is-spammer <domain.name>
  11 
  12 # 使用外部程序: 'dig'
  13 # 测试版本: 9.2.4rc5
  14 
  15 # 使用函数.
  16 # 使用 IFS 来分析分配在数组中的字符串.
  17 # 检查 e-mail 黑名单.
  18 
  19 # 使用来自文本体中的 domain.name:
  20 # http://www.good_stuff.spammer.biz/just_ignore_everything_else
  21 #                       ^^^^^^^^^^^
  22 # 或者使用来自任意 e-mail 地址的 domain.name:
  23 # Really_Good_Offer@spammer.biz
  24 #
  25 # 并将其作为这个脚本的唯一参数.
  26 #(另: 你的 Inet 连接应该保证连接)
  27 #
  28 # 这样, 在上边两个实例中调用这个脚本:
  29 #       is-spammer.sh spammer.biz
  30 
  31 
  32 # Whitespace == :Space:Tab:Line Feed:Carriage Return:
  33 WSP_IFS=$'\x20'$'\x09'$'\x0A'$'\x0D'
  34 
  35 # No Whitespace == Line Feed:Carriage Return
  36 No_WSP=$'\x0A'$'\x0D'
  37 
  38 # 域分隔符为点分10进制 ip 地址
  39 ADR_IFS=${No_WSP}'.'
  40 
  41 # 取得 dns 文本资源记录.
  42 # get_txt <error_code> <list_query>
  43 get_txt() {
  44 
  45     # 分析在"."中分配的 $1.
  46     local -a dns
  47     IFS=$ADR_IFS
  48     dns=( $1 )
  49     IFS=$WSP_IFS
  50     if [ "${dns[0]}" == '127' ]
  51     then
  52         # 查看此处是否有原因.
  53         echo $(dig +short $2 -t txt)
  54     fi
  55 }
  56 
  57 # 取得 dns 地址资源记录.
  58 # chk_adr <rev_dns> <list_server>
  59 chk_adr() {
  60     local reply
  61     local server
  62     local reason
  63 
  64     server=${1}${2}
  65     reply=$( dig +short ${server} )
  66 
  67     # 假设应答可能是一个错误码 . . .
  68     if [ ${#reply} -gt 6 ]
  69     then
  70         reason=$(get_txt ${reply} ${server} )
  71         reason=${reason:-${reply}}
  72     fi
  73     echo ${reason:-' not blacklisted.'}
  74 }
  75 
  76 # 需要从名字中取得 IP 地址.
  77 echo 'Get address of: '$1
  78 ip_adr=$(dig +short $1)
  79 dns_reply=${ip_adr:-' no answer '}
  80 echo ' Found address: '${dns_reply}
  81 
  82 # 一个可用的应答至少是4个数字加上3个点.
  83 if [ ${#ip_adr} -gt 6 ]
  84 then
  85     echo
  86     declare query
  87 
  88     # 分析点中的分配.
  89     declare -a dns
  90     IFS=$ADR_IFS
  91     dns=( ${ip_adr} )
  92     IFS=$WSP_IFS
  93 
  94     # Reorder octets into dns query order.
  95     rev_dns="${dns[3]}"'.'"${dns[2]}"'.'"${dns[1]}"'.'"${dns[0]}"'.'
  96 
  97 # 参见: http://www.spamhaus.org (Conservative, well maintained)
  98     echo -n 'spamhaus.org says: '
  99     echo $(chk_adr ${rev_dns} 'sbl-xbl.spamhaus.org')
 100 
 101 # 参见: http://ordb.org (Open mail relays)
 102     echo -n '   ordb.org  says: '
 103     echo $(chk_adr ${rev_dns} 'relays.ordb.org')
 104 
 105 # 参见: http://www.spamcop.net/ (你可以在这里报告 spammer)
 106     echo -n ' spamcop.net says: '
 107     echo $(chk_adr ${rev_dns} 'bl.spamcop.net')
 108 
 109 # # # 其他的黑名单操作 # # #
 110 
 111 # 参见: http://cbl.abuseat.org.
 112     echo -n ' abuseat.org says: '
 113     echo $(chk_adr ${rev_dns} 'cbl.abuseat.org')
 114 
 115 # 参见: http://dsbl.org/usage (Various mail relays)
 116     echo
 117     echo 'Distributed Server Listings'
 118     echo -n '       list.dsbl.org says: '
 119     echo $(chk_adr ${rev_dns} 'list.dsbl.org')
 120 
 121     echo -n '   multihop.dsbl.org says: '
 122     echo $(chk_adr ${rev_dns} 'multihop.dsbl.org')
 123 
 124     echo -n 'unconfirmed.dsbl.org says: '
 125     echo $(chk_adr ${rev_dns} 'unconfirmed.dsbl.org')
 126 
 127 else
 128     echo
 129     echo 'Could not use that address.'
 130 fi
 131 
 132 exit 0
 133 
 134 # 练习:
 135 # -----
 136 
 137 # 1) 检查脚本的参数,
 138 #    并且如果必要的话使用合适的错误消息退出.
 139 
 140 # 2) 检查调用这个脚本的时候是否在线,
 141 #    并且如果必要的话使用合适的错误消息退出.
 142 
 143 # 3) Substitute generic variables for "hard-coded" BHL domains.
 144 
 145 # 4) 通过对 'dig' 命令使用 "+time=" 选项
 146      来给这个脚本设置一个暂停.

想获得比上边这个脚本更详细的版本, 参见 Example A-27.

traceroute

跟踪包发送到远端主机过程中的路由信息. 这个命令在 LAN, WAN, 或者在 Internet 上都可以正常工作. 远端主机可以通过 IP 地址来指定. 这个命令的输出也可以通过管道中的 grepsed 命令来过滤.

 bash$ traceroute 81.9.6.2
 traceroute to 81.9.6.2 (81.9.6.2), 30 hops max, 38 byte packets
 1  tc43.xjbnnbrb.com (136.30.178.8)  191.303 ms  179.400 ms  179.767 ms
 2  or0.xjbnnbrb.com (136.30.178.1)  179.536 ms  179.534 ms  169.685 ms
 3  192.168.11.101 (192.168.11.101)  189.471 ms  189.556 ms *
 ...
 	      

ping

广播一个 "ICMP ECHO_REQUEST" 包到其他主机上, 既可以是本地网络也可以使远端网络. 这是一个测试网络连接的诊断工具, 应该小心使用.

一个成功的 ping 返回的 退出码0. 可以用在脚本的测试语句中.

 bash$ ping localhost
 PING localhost.localdomain (127.0.0.1) from 127.0.0.1 : 56(84) bytes of data.
 64 bytes from localhost.localdomain (127.0.0.1): icmp_seq=0 ttl=255 time=709 usec
 64 bytes from localhost.localdomain (127.0.0.1): icmp_seq=1 ttl=255 time=286 usec

 --- localhost.localdomain ping statistics ---
 2 packets transmitted, 2 packets received, 0% packet loss
 round-trip min/avg/max/mdev = 0.286/0.497/0.709/0.212 ms
 	      

whois

执行DNS (域名系统) 查询lookup. -h 选项允许指定需要查询的特定的 whois 服务器. 参见 Example 4-6Example 12-36.

finger

取得网络上的用户信息. 另外这个命令可以显示一个用户的~/.plan, ~/.project, 和 ~/.forward 文件, 如果存在的话.

 bash$ finger
 Login  Name           Tty      Idle  Login Time   Office     Office Phone
 bozo   Bozo Bozeman   tty1        8  Jun 25 16:59
 bozo   Bozo Bozeman   ttyp0          Jun 25 16:59
 bozo   Bozo Bozeman   ttyp1          Jun 25 17:07
 
 
 
 bash$ finger bozo
 Login: bozo                             Name: Bozo Bozeman
 Directory: /home/bozo                   Shell: /bin/bash
 Office: 2355 Clown St., 543-1234
 On since Fri Aug 31 20:13 (MST) on tty1    1 hour 38 minutes idle
 On since Fri Aug 31 20:13 (MST) on pts/0   12 seconds idle
 On since Fri Aug 31 20:13 (MST) on pts/1
 On since Fri Aug 31 20:31 (MST) on pts/2   1 hour 16 minutes idle
 No mail.
 No Plan.
 	      

处于安全上的考虑, 许多网络都禁用了 finger 以及和它相关的幽灵进程. [1]

chfn

修改 finger 命令所显示出来的用户信息.

vrfy

验证一个互联网的 e-mail 地址.

远端主机接入

sx, rx

sxrx 命令使用 xmodem 协议, 设置服务来向远端主机传输文件和接收文件. 这些都是通讯安装包的一般部分, 比如 minicom.

sz, rz

szrz 命令使用 zmodem 协议, 设置服务来向远端主机传输文件和接收文件. zmodem 协议在某些方面比 xmodem强, 比如使用更快的的传输波特率, 并且可以对中断的文件进行续传.与 sx 一样 rx, 这些都是通讯安装包的一般部分.

ftp

向远端服务器上传或下载的工具和协议. 一个ftp会话可以写到脚本中自动运行. (见 Example 17-6, Example A-4, 和 Example A-13).

uucp, uux, cu

uucp: UNIX 到 UNIX 拷贝. 这是一个通讯安装包, 目的是为了在 UNIX 服务器之间传输文件. 使用 shell 脚本来处理 uucp 命令序列是一种有效的方法.

因为互联网和电子邮件的出现, uucp 现在看起来已经很落伍了, 但是这个命令在互联网连接不可用或者不适合使用的地方, 这个命令还是可以完美的运行. uucp 的优点就是它的容错性, 即使有一个服务将拷贝操作中断了, 那么当连接恢复的时候, 这个命令还是可以在中断的地方续传.

---

uux: UNIX 到 UNIX 执行. 在远端系统上执行一个命令.这个命令是 uucp 包的一部分.

---

cu: Call Up 一个远端系统并且作为一个简单终端进行连接. 这是一个 telnet 的缩减版本. 这个命令是 uucp 包的一部分.

telnet

连接远端主机的工具和协议.

telnet 协议本身包含安全漏洞, 因此我们应该适当的避免使用.

wget

wget 工具使用非交互的形式从 web 或 ftp 站点上取得或下载文件. 在脚本中使用正好.

   1 wget -p http://www.xyz23.com/file01.html
   2 #  The -p or --page-requisite 选项将会使得 wget 取得显示指定页时
   3 #+ 所需要的所有文件.(译者: 比如内嵌图片和样式表等).
   4 
   5 wget -r ftp://ftp.xyz24.net/~bozo/project_files/ -O $SAVEFILE
   6 #  -r 选项将会递归的从指定站点
   7 #+ 上下载所有连接.


Example 12-38. 获得一份股票报价

   1 #!/bin/bash
   2 # quote-fetch.sh: 下载一份股票报价.
   3 
   4 
   5 E_NOPARAMS=66
   6 
   7 if [ -z "$1" ]  # 必须指定需要获取的股票(代号).
   8   then echo "Usage: `basename $0` stock-symbol"
   9   exit $E_NOPARAMS
  10 fi
  11 
  12 stock_symbol=$1
  13 
  14 file_suffix=.html
  15 # 获得一个 HTML 文件, 所以要正确命名它.
  16 URL='http://finance.yahoo.com/q?s='
  17 # Yahoo 金融板块, 后缀是股票查询.
  18 
  19 # -----------------------------------------------------------
  20 wget -O ${stock_symbol}${file_suffix} "${URL}${stock_symbol}"
  21 # -----------------------------------------------------------
  22 
  23 
  24 # 在 http://search.yahoo.com 上查询相关材料:
  25 # -----------------------------------------------------------
  26 # URL="http://search.yahoo.com/search?fr=ush-news&p=${query}"
  27 # wget -O "$savefilename" "${URL}"
  28 # -----------------------------------------------------------
  29 # 保存相关 URL 的列表.
  30 
  31 exit $?
  32 
  33 # 练习:
  34 # -----
  35 #
  36 # 1) 添加一个测试来验证用户正在线.
  37 #    (暗示: 对 "ppp" 或 "connect" 来分析 'ps -ax' 的输出.
  38 #
  39 # 2) 修改这个脚本, 让这个脚本具有获得本地天气预报的能力,
  40 #+   将用户的 zip code 作为参数.

参见 Example A-29Example A-30.

lynx

lynx 是一个网页浏览器, 也是一个文件浏览器. 它可以(通过使用 -dump 选项)在脚本中使用. 它的作用是可以从 Web 或 ftp 站点上非交互的获得文件.
   1 lynx -dump http://www.xyz23.com/file01.html >$SAVEFILE

使用 -traversal 选项, lynx 将从参数中指定的 HTTP URL 开始, 遍历指定服务器上的所有链接. 如果与 -crawl 选项一起用的话, 将会把每个输出的页面文本都放到一个 log 文件中.

rlogin

远端登陆, 在远端的主机上开启一个会话. 这个命令存在安全隐患, 所以要使用 ssh 来代替.

rsh

远端 shell, 在远端的主机上执行命令. 这个命令存在安全隐患, 所以要使用 ssh 来代替.

rcp

远端拷贝, 在网络上的不同主机间拷贝文件.

rsync

远端同步, 在网络上的不同主机间(同步)更新文件.

 bash$ rsync -a ~/sourcedir/*txt /node1/subdirectory/
 	      


Example 12-39. 更新 Fedora 4 <rojy bug>

   1 #!/bin/bash
   2 # fc4upd.sh
   3 
   4 # 脚本作者: Frank Wang.
   5 # 本书作者作了少量修改.
   6 # 授权在本书中使用.
   7 
   8 
   9 #  使用 rsync 命令从镜像站点上下载 Fedora 4 的更新.
  10 #  为了节省空间, 如果有多个版本存在的话,
  11 #+ 只下载最新的包.
  12 
  13 URL=rsync://distro.ibiblio.org/fedora-linux-core/updates/
  14 # URL=rsync://ftp.kddilabs.jp/fedora/core/updates/
  15 # URL=rsync://rsync.planetmirror.com/fedora-linux-core/updates/
  16 
  17 DEST=${1:-/var/www/html/fedora/updates/}
  18 LOG=/tmp/repo-update-$(/bin/date +%Y-%m-%d).txt
  19 PID_FILE=/var/run/${0##*/}.pid
  20 
  21 E_RETURN=65        # 某些意想不到的错误.
  22 
  23 
  24 # 一搬 rsync 选项
  25 # -r: 递归下载
  26 # -t: 保存时间
  27 # -v: verbose
  28 
  29 OPTS="-rtv --delete-excluded --delete-after --partial"
  30 
  31 # rsync include 模式
  32 # Leading slash causes absolute path name match.
  33 INCLUDE=(
  34     "/4/i386/kde-i18n-Chinese*" 
  35 #   ^                         ^
  36 # 双引号是必须的, 用来防止file globbing.
  37 ) 
  38 
  39 
  40 # rsync exclude 模式
  41 # 使用 "#" 临时注释掉一些不需要的包.
  42 EXCLUDE=(
  43     /1
  44     /2
  45     /3
  46     /testing
  47     /4/SRPMS
  48     /4/ppc
  49     /4/x86_64
  50     /4/i386/debug
  51    "/4/i386/kde-i18n-*"
  52    "/4/i386/openoffice.org-langpack-*"
  53    "/4/i386/*i586.rpm"
  54    "/4/i386/GFS-*"
  55    "/4/i386/cman-*"
  56    "/4/i386/dlm-*"
  57    "/4/i386/gnbd-*"
  58    "/4/i386/kernel-smp*"
  59 #  "/4/i386/kernel-xen*" 
  60 #  "/4/i386/xen-*" 
  61 )
  62 
  63 
  64 init () {
  65     # 让管道命令返回可能的 rsync 错误, 比如, 网络延时(stalled network).
  66     set -o pipefail
  67 
  68     TMP=${TMPDIR:-/tmp}/${0##*/}.$$     # 保存精炼的下载列表.
  69     trap "{
  70         rm -f $TMP 2>/dev/null
  71     }" EXIT                             # 删除存在的临时文件.
  72 }
  73 
  74 
  75 check_pid () {
  76 # 检查进程是否存在.
  77     if [ -s "$PID_FILE" ]; then
  78         echo "PID file exists. Checking ..."
  79         PID=$(/bin/egrep -o "^[[:digit:]]+" $PID_FILE)
  80         if /bin/ps --pid $PID &>/dev/null; then
  81             echo "Process $PID found. ${0##*/} seems to be running!"
  82            /usr/bin/logger -t ${0##*/} \
  83                  "Process $PID found. ${0##*/} seems to be running!"
  84             exit $E_RETURN
  85         fi
  86         echo "Process $PID not found. Start new process . . ."
  87     fi
  88 }
  89 
  90 
  91 #  根据上边的模式,
  92 #+ 设置整个文件的更新范围, 从 root 或 $URL 开始.
  93 set_range () {
  94     include=
  95     exclude=
  96     for p in "${INCLUDE[@]}"; do
  97         include="$include --include \"$p\""
  98     done
  99 
 100     for p in "${EXCLUDE[@]}"; do
 101         exclude="$exclude --exclude \"$p\""
 102     done
 103 }
 104 
 105 
 106 # 获得并提炼 rsync 更新列表.
 107 get_list () {
 108     echo $$ > $PID_FILE || {
 109         echo "Can't write to pid file $PID_FILE"
 110         exit $E_RETURN
 111     }
 112 
 113     echo -n "Retrieving and refining update list . . ."
 114 
 115     # 获得列表 -- 为了作为单个命令来运行 rsync 需要 'eval'.
 116     # $3 和 $4 是文件创建的日期和时间.
 117     # $5 是完整的包名字.
 118     previous=
 119     pre_file=
 120     pre_date=0
 121     eval /bin/nice /usr/bin/rsync \
 122         -r $include $exclude $URL | \
 123         egrep '^dr.x|^-r' | \
 124         awk '{print $3, $4, $5}' | \
 125         sort -k3 | \
 126         { while read line; do
 127             # 获得这段运行的秒数, 过滤掉不用的包.
 128             cur_date=$(date -d "$(echo $line | awk '{print $1, $2}')" +%s)
 129             #  echo $cur_date
 130 
 131             # 取得文件名.
 132             cur_file=$(echo $line | awk '{print $3}')
 133             #  echo $cur_file
 134 
 135             # 如果可能的话, 从文件名中取得 rpm 的包名字.
 136             if [[ $cur_file == *rpm ]]; then
 137                 pkg_name=$(echo $cur_file | sed -r -e \
 138                     's/(^([^_-]+[_-])+)[[:digit:]]+\..*[_-].*$/\1/')
 139             else
 140                 pkg_name=
 141             fi
 142             # echo $pkg_name
 143 
 144             if [ -z "$pkg_name" ]; then   #  如果不是一个 rpm 文件,
 145                 echo $cur_file >> $TMP    #+ 然后添加到下载列表里.
 146             elif [ "$pkg_name" != "$previous" ]; then   # 发现一个新包.
 147                 echo $pre_file >> $TMP                  # 输出最新的文件.
 148                 previous=$pkg_name                      # 保存当前状态.
 149                 pre_date=$cur_date
 150                 pre_file=$cur_file
 151             elif [ "$cur_date" -gt "$pre_date" ]; then  #  如果是相同的包, 但是更新一些,
 152                 pre_date=$cur_date                      #+ 那么就更新最新的.
 153                 pre_file=$cur_file
 154             fi
 155             done
 156             echo $pre_file >> $TMP                      #  TMP 现在包含所有
 157                                                         #+ 提炼过的列表.
 158             # echo "subshell=$BASH_SUBSHELL"
 159 
 160     }       # 这里的打括号是为了让最后这句"echo $pre_file >> $TMP"
 161             # 也能与整个循环一起放到同一个子 shell ( 1 )中.
 162 
 163     RET=$?  # 取得管道命令的返回码.
 164 
 165     [ "$RET" -ne 0 ] && {
 166         echo "List retrieving failed with code $RET"
 167         exit $E_RETURN
 168     }
 169 
 170     echo "done"; echo
 171 }
 172 
 173 # 真正的 rsync 的下载部分.
 174 get_file () {
 175 
 176     echo "Downloading..."
 177     /bin/nice /usr/bin/rsync \
 178         $OPTS \
 179         --filter "merge,+/ $TMP" \
 180         --exclude '*'  \
 181         $URL $DEST     \
 182         | /usr/bin/tee $LOG
 183 
 184     RET=$?
 185 
 186         #  --filter merge,+/ is crucial for the intention. 
 187         #  + modifier means include and / means absolute path.
 188         #  Then sorted list in $TMP will contain ascending dir name and 
 189         #+ prevent the following --exclude '*' from "shortcutting the circuit." 
 190 
 191     echo "Done"
 192 
 193     rm -f $PID_FILE 2>/dev/null
 194 
 195     return $RET
 196 }
 197 
 198 # -------
 199 # Main
 200 init
 201 check_pid
 202 set_range
 203 get_list
 204 get_file
 205 RET=$?
 206 # -------
 207 
 208 if [ "$RET" -eq 0 ]; then
 209     /usr/bin/logger -t ${0##*/} "Fedora update mirrored successfully."
 210 else
 211     /usr/bin/logger -t ${0##*/} "Fedora update mirrored with failure code: $RET"
 212 fi
 213 
 214 exit $RET

使用 rcp, rsync, 和其他一些有安全问题的类似工具, 并将这些工具用在 shell 脚本中是不明智的. 应该考虑使用 ssh, scp, 或者一个 expect 脚本来代替这些不安全的工具.

ssh

安全 shell, 登陆远端主机并在其上运行命令. 这个工具具有身份认证和加密的功能, 可以安全的替换 telnet, rlogin, rcp, 和 rsh 等工具. 参见 man页 来获取详细信息.


Example 12-40. 使用 ssh

   1 #!/bin/bash
   2 # remote.bash: 使用 ssh.
   3 
   4 # 这个例子是 Michael Zick 编写的.
   5 # 授权使用.
   6 
   7 
   8 #   假设:
   9 #   -----
  10 #   fd-2(文件描述符2) 并没有被抛弃 ( '2>/dev/null' ).
  11 #   ssh/sshd 假设 stderr ('2') 将会被显示给用户.
  12 #
  13 #   sshd 正运行在你的机器上.
  14 #   对于大多数 '标准' 的发行版, 是应该有的,
  15 #+  并且没有一些稀奇古怪的 ssh-keygen.
  16 
  17 # 在你的机器上从命令行中试一下 ssh:
  18 #
  19 # $ ssh $HOSTNAME
  20 # 不同特殊的准备, 你将被要求输入你的密码.
  21 #   输入密码
  22 #   完成后,  $ exit
  23 #
  24 # 好使了么? 如果好使了, 你可以做好准备来获取更多的乐趣了.
  25 
  26 # 在你的机器上用 'root'身份来试试 ssh:
  27 #
  28 #   $  ssh -l root $HOSTNAME
  29 #   当询问密码时, 输入 root 的密码, 别输入你的密码.
  30 #          Last login: Tue Aug 10 20:25:49 2004 from localhost.localdomain
  31 #   完成后键入 'exit'.
  32 
  33 #  上边的动作将会给你一个交互的shell.
  34 #  在 'single command' 模式下建立 sshd 是可能的, <rojy bug>
  35 #+ 不过这已经超出本例的范围了.
  36 #  唯一需要注意的事情是下面都可以工作在
  37 #+ 'single command' 模式.
  38 
  39 
  40 # 一个基本的写输出(本地)命令.
  41 
  42 ls -l
  43 
  44 # 现在在远端机器上使用同样的基本命令.
  45 # 使用一套不同的 'USERNAME' 和 'HOSTNAME' :
  46 USER=${USERNAME:-$(whoami)}
  47 HOST=${HOSTNAME:-$(hostname)}
  48 
  49 #  现在在远端主机上运行上边的命令行命令,
  50 #+ 当然, 所有的传输都被加密了.
  51 
  52 ssh -l ${USER} ${HOST} " ls -l "
  53 
  54 #  期望的结果就是在远端主机上列出你的
  55 #+ username 主目录的所有文件.
  56 #  如果想看点不一样的, 那就
  57 #+ 在别的地方运行这个脚本, 别再你的主目录上运行这个脚本.
  58 
  59 #  换句话说, Bash 命令已经作为一个引用行
  60 #+ 被传递到远端的shell 中了,这样就可以在远端的机器上运行它了.
  61 #  在这种情况下, sshd 代表你运行了 ' bash -c "ls -l" '.
  62 
  63 #  对于每个命令行如果想不输入密码的话,
  64 #+ 对于这种类似的议题, 可以参阅
  65 #+    man ssh
  66 #+    man ssh-keygen
  67 #+    man sshd_config.
  68 
  69 exit 0

在循环中, ssh 可能会引起意想不到的异常行为. 根据comp.unix 上的shell文档 Usenet post , ssh 继承了循环的标准输入.为了解决这个问题, 使用 ssh 的 -n 或者 -f 选项.

感谢 Jason Bechtel, 指出这点.

scp

安全拷贝, 在功能上与 rcp 很相似, 就是在2个不同的网络主机之间拷贝文件, 但是要通过鉴权的方式, 并且使用与 ssh 类似的安全层.

Local Network

write

这是一个端到端通讯的工具. 这个工具可以从你的终端上(console 或者 xterm)发送整行到另一个用户的终端上. mesg 命令当然也可以用来对于一个终端的写权限

因为 write 是需要交互的, 所以这个命令通常不使用在脚本中.

netconfig

用来配置网络适配器(使用 DHCP)的命令行工具. 这个命令对于红帽发行版来说是内置的.

Mail

mail

发送或读取 e-mail 消息.

如果把这个命令行的 mail 客户端当成一个脚本中的命令来使用的话, 效果非常好.


Example 12-41. 一个可以mail自己的脚本

   1 #!/bin/sh
   2 # self-mailer.sh: mail自己的脚本
   3 
   4 adr=${1:-`whoami`}     # 如果不指定的话, 默认是当前用户.
   5 #  键入 'self-mailer.sh wiseguy@superdupergenius.com'
   6 #+ 发送这个脚本到这个地址.
   7 #  如果只键入 'self-mailer.sh' (不给参数) 的话, 那么这脚本就会被发送给
   8 #+ 调用者, 比如 bozo@localhost.localdomain.
   9 #
  10 #  如果想了解 ${parameter:-default} 结构的更多细节,
  11 #+ 请参见第9章 变量重游中的
  12 #+ 第3节 参数替换.
  13 
  14 # ============================================================================
  15   cat $0 | mail -s "Script \"`basename $0`\" has mailed itself to you." "$adr"
  16 # ============================================================================
  17 
  18 # --------------------------------------------
  19 #  来自 self-mailing 脚本的一份祝福.
  20 #  一个喜欢恶搞的家伙运行了这个脚本,
  21 #+ 这导致了他自己收到了这份mail.
  22 #  显然的, 有些人确实没什么事好做,
  23 #+ 就只能浪费他们自己的时间玩了.
  24 # --------------------------------------------
  25 
  26 echo "At `date`, script \"`basename $0`\" mailed to "$adr"."
  27 
  28 exit 0

mailto

mail 命令很相似, mailto 命令可以使用命令行或在脚本中发送 e-mail 消息. 然而, mailto 命令也允许发送 MIME (多媒体) 消息.

vacation

这个工具可以自动回复 e-mail 给发送者, 表示邮件的接受者正在度假暂时无法收到邮件. 这个工具与 sendmail 一起运行于网络上, 并且这个工具不支持拨号的 POPmail 帐号.

注意事项

[1]

一个 幽灵进程 指的是并未附加在终端会话中的后台进程. 幽灵进程 在指定的时间执行指定的服务, 或者由特定的事件出发来执行指定的服务.

希腊文中的 "daemon" 意思是幽灵, 这个词充满了神秘感和神奇的力量, 在 UNIX 中幽灵进程总是在后台默默地执行着分配给它们的任务.