分享

对话 UNIX: !$#@*%

 jijo 2009-01-01



更好地理解 UNIX? 用户输入的这些 “奇怪的” 字符。学习如何在 UNIX 中使用管道、重定向、操作符等特性。

现在,您已经在 IBM? AIX? 上工作了一段时间了。您已经学习了几个基本命令,能够在目录结构中移动、创建和修改文件、查看正在运行的进程以及管理用户和系统。这很不错,但是您希望了解 UNIX? 管理员输入的命令是什么意思。这些命令中包含许多奇怪的符号。在本文中,了解 |>>><<<[[]] 等符号在 UNIX 和 Linux? 中的意思,以及如何使用 &&||<<=!= 操作符。

管道

如果您熟悉 UNIX,那么管道(或 pipe)会是每天都要接触到的东西。管道最初是由 Malcolm McIlroy 开发的,可以使用管道把一个命令的标准输出(stdout)定向到下一个命令的标准输入(stdin),这样就形成了连续执行的命令链。可以在一个命令行上使用多个管道。在许多时候,一个命令的 stdout 用作下一个命令的 stdin,第二个命令的 stdout 又被重定向到另一个命令的 stdin,依此类推。

例如,在排除故障或执行日常检查时,大多数 UNIX 管理员首先做的事情之一是查看系统上当前正在运行的进程。清单 1 演示这样的检查。


清单 1. 日常进程检查示例
                                    # ps –ef
                                    UID     PID    PPID   C    STIME    TTY  TIME CMD
                                    root       1       0   0   Jul 27      -  0:05 /etc/init
                                    root   53442  151674   0   Jul 27      -  0:00 /usr/sbin/syslogd
                                    root   57426       1   0   Jul 27      -  0:00 /usr/lib/errdemon
                                    root   61510       1   0   Jul 27      - 23:55 /usr/sbin/syncd 60
                                    root   65634       1   0   Jul 27      -  0:00 /usr/ccs/bin/shlap64
                                    root   82002  110652   0   Jul 27      -  0:24 /usr/lpp/X11/bin/X -x abx
                                    -x dbe -x GLX -D /usr/lib/X11//rgb -T -force :0 -auth /var/dt/A:0-SfIdMa
                                    root   86102       1   0   Jul 27      -  0:00 /usr/lib/methods/ssa_daemon -l ssa0
                                    root  106538  151674   0   Jul 27      -  0:01 sendmail: accepting connections
                                    root  110652       1   0   Jul 27      -  0:00 /usr/dt/bin/dtlogin -daemon
                                    root  114754  118854   0   Jul 27      - 20:22 dtgreet
                                    root  118854  110652   0   Jul 27      -  0:00 dtlogin <:0>        -daemon
                                    root  131088       1   0   Jul 27      -  0:07 /usr/atria/etc/lockmgr
                                    -a /var/adm/atria/almd -q 1024 -u 256 -f 256
                                    root  147584       1   0   Jul 27      -  0:01 /usr/sbin/cron
                                    root  155816  151674   0   Jul 27      -  0:04 /usr/sbin/portmap
                                    root  163968  151674   0   Jul 27      -  0:00 /usr/sbin/qdaemon
                                    root  168018  151674   0   Jul 27      -  0:00 /usr/sbin/inetd
                                    root  172116  151674   0   Jul 27      -  0:03 /usr/sbin/xntpd
                                    root  180314  151674   0   Jul 27      -  0:19 /usr/sbin/snmpmibd
                                    root  184414  151674   0   Jul 27      -  0:21 /usr/sbin/aixmibd
                                    root  188512  151674   0   Jul 27      -  0:20 /usr/sbin/hostmibd
                                    root  192608  151674   0   Jul 27      -  7:46 /usr/sbin/muxatmd
                                    root  196718  151674   0 11:00:27      -  0:00 /usr/sbin/rpc.mountd
                                    root  200818  151674   0   Jul 27      -  0:00 /usr/sbin/biod 6
                                    root  213108  151674   0   Jul 27      -  0:00 /usr/sbin/nfsd 3891
                                    root  221304  245894   0   Jul 27      -  0:05 /bin/nsrexecd
                                    daemon  225402  151674   0 11:00:27      -  0:00 /usr/sbin/rpc.statd
                                    root  229498  151674   0 11:00:27      -  0:00 /usr/sbin/rpc.lockd
                                    root  241794  151674   0   Jul 27      -  0:51 /usr/lib/netsvc/yp/ypbind
                                    root  245894       1   0   Jul 27      -  0:00 /bin/nsrexecd
                                    root  253960       1   0   Jul 27      -  0:00 ./mflm_manager
                                    root  274568  151674   0   Jul 27      -  0:00 /usr/sbin/sshd -D
                                    root  282766       1   0   Jul 27   lft0  0:00 /usr/sbin /dev/console
                                    root  290958       1   0   Jul 27      -  0:00 /usr/lpp/diagnostics/bin/diagd
                                    root  315646  151674   0   Jul 27      -  0:00 /usr/sbin/lpd
                                    root  319664       1   0   Jul 27      -  0:00 /usr/atria/etc/albd_server
                                    root  340144  168018   0 12:34:56      -  0:00 rpc.ttdbserver 100083 1
                                    root  376846  168018   0   Jul 30      -  0:00 rlogind
                                    cormany  409708  569522   0 19:29:27  pts/1  0:00 -ksh
                                    root  569522  168018   0 19:29:26      -  0:00 rlogind
                                    cormany  733188  409708   3 19:30:34  pts/1  0:00 ps -ef
                                    root  749668  168018   0   Jul 30      -  0:00 rlogind
                                    

系统上当前正在运行的进程的列表可能像 清单 1 这么简单;但是,大多数生产系统运行的进程更多,这会使 ps 的输出更长。为了把这个列表缩短到自己需要的范围,可以使用管道把 ps –ef 的标准输出重定向到 grep,从而搜索自己真正希望看到的结果。清单 2清单 1 产生的进程列表重定向到 grep,搜索字符串 “rpc” 和 “ksh”。


清单 2. 把进程列表重定向到 grep
                                    # ps –ef | grep –E "rpc|ksh"
                                    root  196718  151674   0 11:00:27      -  0:00 /usr/sbin/rpc.mountd
                                    daemon  225402  151674   0 11:00:27      -  0:00 /usr/sbin/rpc.statd
                                    root  229498  151674   0 11:00:27      -  0:00 /usr/sbin/rpc.lockd
                                    root  340144  168018   0 12:34:56      -  0:00 rpc.ttdbserver 100083 1
                                    cormany  409708  569522   0 19:29:27  pts/1  0:00 -ksh
                                    cormany  733202  409708   0 19:52:20  pts/1  0:00 grep -E rpc|ksh
                                    

当多次把 stdout 重定向到 stdin 时,管道的使用方法可以很复杂。在下面的示例中,扩展了前面的 psgrep 示例,把它的 stdout 重定向到另一个 grep,其作用是排除包含 “grep” 或 “ttdbserver” 的字符串。当最后的 grep 操作完成时,再次使用管道把 stdout 重定向到一个 awk 语句,其作用是输出进程标识符(PID)大于 200,000 的所有进程:

# ps –ef | grep –E "rpc|ksh" | grep -vE "grep|rpc.ttdbserver" |
                                    awk -v _MAX_PID=200000 '{if ($2 > _MAX_PID) {print "PID for
                                    process",$8,"is greater than", _MAX_PID}}'
                                    PID for process /usr/sbin/rpc.statd is greater than 200000
                                    PID for process /usr/sbin/rpc.lockd is greater than 200000
                                    PID for process -ksh is greater than 200000
                                    

图 1 通过图形说明命令的 stdout 重定向到后续命令的 stdin 的次序。


图 1. 管道示例
管道示例




回页首


用 >、>>、< 和 << 执行数据重定向

通过命令行界面(CLI)执行命令的另一个重要方面是,能够把各种输出写到一个设备,或者把来自另一个设备的输入读取到命令中。要想写一个命令的输出,需要在执行的命令后面加上大于号(> 或 >>)和所需的目标文件名或设备。如果目标文件不存在,而且您对目标目录有写权限,那么 > 和 >> 会创建这个文件并根据您的 umask 设置权限,然后把命令的输出写到刚创建的文件中。但是,如果这个文件存在,> 会尝试打开文件并覆盖整个内容。如果希望在这个文件中追加内容,那么只需使用 >>。可以认为它的作用是把左边命令的输出数据流移动到右边的目标文件中(即 <cmd> -> <output> -> <file>)。

下面的示例执行 “管道” 一节中的 ps –ef 示例,并把输出重定向到文件 ps_out

# ps –ef | grep –E "rpc|ksh" > ps_out
                                    

下面的代码执行前面扩展的管道示例并把输出重定向到同一个文件(ps_out),但是追加到当前数据后面:

# ps –ef | grep –E "rpc|ksh" | grep -vE "grep|rpc.ttdbserver" |
                                    awk -v _MAX_PID=200000 '{if ($2 > _MAX_PID) {print "PID for
                                    process",$8,"is greater than", _MAX_PID}}' >> ps_out
                                    

清单 3 给出前两个重定向的输出。


清单 3. 重定向的输出
                                    # cat ps_out
                                    root  196718  151674   0 11:00:27      -  0:00 /usr/sbin/rpc.mountd
                                    daemon  225402  151674   0 11:00:27      -  0:00 /usr/sbin/rpc.statd
                                    root  229498  151674   0 11:00:27      -  0:00 /usr/sbin/rpc.lockd
                                    root  340144  168018   0 12:34:56      -  0:00 rpc.ttdbserver 100083 1
                                    cormany  409708  569522   0 19:29:27  pts/1  0:00 -ksh
                                    cormany  733202  409708   0 19:52:20  pts/1  0:00 grep -E rpc|ksh
                                    PID for process /usr/sbin/rpc.statd is greater than 200000
                                    PID for process /usr/sbin/rpc.lockd is greater than 200000
                                    PID for process -ksh is greater than 200000
                                    

当只使用 > 重定向输出时,只重定向命令的 stdout。但是,除了 stdout,还有 stderr 输出:前者表示为 1,后者表示为 2。在 UNIX 中输出重定向没有区别。只需在 > 前面加上所需的输出类型(例如,1>2>),告诉 shell 要把输出路由到哪里。

清单 4 尝试列出 fileA.tar.bz2 和 fileC.tar.bz2。但是,如第一个命令(ls)所示,fileC.tar.bz2 不存在。好在可以把 stdout 和 stderr 分别重定向到 ls.out 和 ls.err,这样就能够看到错误消息。


清单 4. 列出文件 fileA.tar.bz2 和 fileC.tar.bz2
                                    # ls
                                    fileA.tar.bz2   fileAA.tar.bz2  fileB.tar.bz2   fileBB.tar.bz2
                                    # ls fileA.tar.bz2 fileC.tar.bz2 1> ls.out 2> ls.err
                                    # cat ls.out
                                    fileA.tar.bz2
                                    # cat ls.err
                                    ls: 0653-341 The file fileC.tar.bz2 does not exist.
                                    

在 AIX 中,对 stdout 和 stderr 使用 >>> 时应用相同的规则。例如,以后的测试可以使用相同的输出文件,见 清单 5


清单 5. 使用输出文件进行以后的测试
                                    # ls fileB.tar.bz2 fileD.tar.bz2 1>> ls.out 2>> ls.err
                                    # cat ls.out
                                    fileA.tar.bz2
                                    fileB.tar.bz2
                                    # cat ls.err
                                    ls: 0653-341 The file fileC.tar.bz2 does not exist.
                                    ls: 0653-341 The file fileD.tar.bz2 does not exist.
                                    

有时候,可能需要把 stdout 和 stderr 写到同一个文件或设备。这有两种方法。第一种方法是把 1>2> 重定向到同一个文件:

# ls fileA.tar.bz2 fileC.tar.bz2 1> ls.out 2> ls.out
                                    # cat ls.out
                                    fileA.tar.bz2
                                    ls: 0653-341 The file fileC.tar.bz2 does not exist.
                                    

第二个方法更简单更快速,有经验的 UNIX 用户更喜欢采用这种方法:

# ls fileA.tar.bz2 fileC.tar.bz2 > ls.out 2>&1
                                    # cat ls.out
                                    fileA.tar.bz2
                                    ls: 0653-341 The file fileC.tar.bz2 does not exist.
                                    

我们分解这个语句。首先,执行 ls fileA.tar.bz2 fileC.tar.bz2。然后使用 > ls.out 把 stdout 重定向到 ls.out,使用 2>&1 把 stderr 重定向到前面重定向的 stdout(ls.out)。

请记住,可以把输出重定向到文件和其他设备。可以把数据重定向到打印机、软盘、终端类型(TTY)以及各种其他设备。例如,如果希望把一个消息发送给所有会话(或 TTY)上的某个用户,那么只需循环处理 who 并把一个消息重定向到 TTY(如果您有足够的权限的话),见 清单 6


清单 6. 把消息重定向到一个 TTY
                                    # for _TTY in 'who | grep "cormany" | awk '{print $2}''
                                    > do
                                    >   _TTY="/dev/${_TTY}"
                                    >   echo "Sending message to cormany on ${_TTY}"
                                    >   echo "Test Message to cormany@${_TTY}" > ${_TTY}
                                    > done
                                    Sending message to cormany on /dev/pts/13
                                    Test Message to cormany@/dev/pts/13
                                    Sending message to cormany on /dev/pts/14
                                    





回页首


stdin 而不是 stdout

尽管使用 > 和 >> 对于大多数人是一个相当容易掌握的概念,但是有的人在使用小于号(< 和 <<)时常常有困难。在考虑 > 和 >> 时,认为它们把左边命令的输出数据流移动到右边的目标文件中,这样最容易理解。同样的方法也适用于 < 和 <<。在使用 < 时,本质上是用一个已经提供的 stdin 执行一个命令。也就是说,把已经提供的数据提供给左边的命令作为 stdin(即 <cmd> <- <data>)。

例如,假设希望把一个包含 ASCII 文本文件的电子邮件发送给另一个用户。可以使用管道把 cat 的 stdout 重定向到 mail 的 stdin(即 cat mail_file.out | mail –s "Here's your E-mail!" acormany@yahoo.com),也可以把文件的内容重定向到 mail 命令的 stdin:

# mail –s "Here's your E-mail!" acormany@yahoo.com < mail_file.out
                                    

使用 <<(也称为 here-document)可以节省格式化时间,并且使命令执行的处理更容易。通过使用 <<,文本字符串被重定向到执行的命令作为 stdin,但是可以继续输入信息,直到到达终止标识符。只需输入命令,输入 << 和终止标识符,然后输入需要的任何内容,最后在一个新行上输入终止标识符。通过使用 here-document,可以保留空格、换行等。

例如,UNIX 必须单独处理下面五个 echo 语句:

# echo "Line 1"
                                    Line 1
                                    # echo "Line 2"
                                    Line 2
                                    # echo "Line 3"
                                    Line 3
                                    # echo "Line 4"
                                    Line 4
                                    # echo "Line 5"
                                    Line 5
                                    

可以用以下代码替换多个 echo 语句,UNIX 只需处理一次执行:

# cat << EOF
                                    > Line 1
                                    > Line 2
                                    > Line 3
                                    > Line 4
                                    > Line 5
                                    > EOF
                                    Line 1
                                    Line 2
                                    Line 3
                                    Line 4
                                    Line 5
                                    

还可以使用制表符让 shell 脚本中的内容更整洁一点,这只需要在 << 和终止标识符之间放上一个连字符(-):

# cat <<- ATC
                                    >	Line 1
                                    >	Line 2
                                    >	Line 3
                                    >	Line 4
                                    >	Line 5
                                    > ATC
                                    Line 1
                                    Line 2
                                    Line 3
                                    Line 4
                                    Line 5
                                    

清单 7 给出的示例演示如何结合使用本文到目前为止讨论的东西。


清单 7. 组合 CLI
                                    # cat redirect_example
                                    #!/usr/bin/ksh
                                    cat <<- ATC | sed "s/^/Redirect Example => /g" >> atc.out
                                    This is an example of how to redirect
                                    stdout to a file as well as pipe stdout into stdin
                                    of another command (i.e. sed), all done inside
                                    a here-document.
                                    Cool eh?
                                    ATC
                                    

现在,看看关于重定向和管道的脚本。

# ./redirect_example
                                    # cat atc.out
                                    Redirect Example => This is an example of how to redirect
                                    Redirect Example => stdout to a file as well as pipe stdout into stdin
                                    Redirect Example => of another command (i.e. sed), all done inside
                                    Redirect Example => a here-document.
                                    Redirect Example =>
                                    Redirect Example => Cool eh?
                                    





回页首


子 shell

有时候,需要一起执行几个命令。例如,如果希望在另一个目录中执行某一操作,可以使用 清单 8 中的代码。


清单 8. 同时执行几个命令
                                    # pwd
                                    /home/cormany
                                    # cd testdir
                                    # tar –cf ls_output.tar ls.out?
                                    # pwd
                                    /home/cormany/testdir
                                    

这是有效的,但是要注意,在执行这些步骤之后,您就不再位于原来的目录中了。通过把这些命令放在它们自己的子 shell 中,它们会作为子 shell 的实例执行。清单 9 演示如何使用子 shell 执行相同的代码。


清单 9. 使用子 shell 同时执行几个命令
                                    # pwd
                                    /home/cormany
                                    # (cd testdir ; tar -cf ls_output.tar ls.out?)
                                    # pwd
                                    /home/cormany
                                    





回页首


test 命令、[ ] 和 [[ ]]

在编写 shell 脚本或用任何现代语言编写程序时,运算表达式或值的能力都很重要。UNIX 一直通过 test 命令提供这一功能。正如 test 的手册页指出的,test 命令运算表达式参数的值,如果表达式的值是 True,就返回零(True)退出值。关于 test 的定义和所有可用条件的更多信息,请参见 test 手册页。

要想使用 test 命令,只需给这个命令提供适当的标志和文件名。当 test 运算完表达式时,返回到命令提示,可以在这里检查返回码,见 清单 10


清单 10. 检查返回码
                                    # ls –l
                                    -rwxr-xr-x    1 cormany  atc             786 Feb 22 16:11 check_file
                                    -rw-r--r--    1 cormany  atc               0 Aug 04 20:57 emptyfile
                                    # test -f emptyfile
                                    # echo $?
                                    0
                                    # test -f badfilename
                                    # echo $?
                                    1
                                    

根据定义,如果表达式值是 True,那么 test 返回零退出值,否则返回非零退出值(即 1)。在 清单 10 中,找到了文件 emptyfile,所以 test 返回 0;但是没有找到文件 badfilename,所以返回 1

使用 test 的另一种方法是把要运算的表达式放在单层方括号([ ])中。使用 test 命令或把它替换为 [ ] 会返回相同的值:

# [ -f emptyfile ]
                                    # echo $?
                                    0
                                    # [ -f badfilename ]
                                    # echo $?
                                    1
                                    

使用单层方括号([ ])还是双层方括号([[ ]])是个人习惯问题,实际上取决于您如何学习命令和 shell 脚本编程。但是请记住,这两者之间有一些差异。尽管 [ ][[ ]] 在运算期间使用相同的测试操作符,但是它们使用不同的逻辑操作符。





回页首


操作符

在 ksh(AIX 中使用的默认 shell)中,以及 UNIX 和 Linux 使用的其他 shell 中,一定要知道如何使用测试、逻辑和替换操作符。

测试操作符

在编写 shell 脚本时,测试操作符对于检查错误和检查文件状态很重要。下面只是可以在 ksh 和其他标准 UNIX shell 中使用的一部分测试操作符:

  • -d <file><file> 是一个目录
  • -e <flle><file> 存在
  • -f <file><file> 是一个常规文件
  • -n <string><string> 不是 NULL
  • -r <file>用户对 <file> 有读权限
  • -s <file><file> 的大小大于 0
  • -w <file>用户对 <file> 有写权限
  • -x <file>用户对 <file> 有执行权限
  • -z <string><string> 是 null
  • -L <file><file> 是一个符号链接

请记住,在 UNIX 目录中,设备、符号链接和其他对象都是文件,所以上面的测试操作符适用于所有类型的文件。

每个人都有自己的 shell 脚本编程风格。无论在测试语句中使用 [[ ]] 还是 [ ],上面的测试操作符的作用是相同的。本文使用 [[ ]]清单 11 演示如何使用上面列出的几个测试操作符。


清单 11. 使用测试操作符
                                    #!/usr/bin/ksh
                                    while true
                                    do
                                    echo "\nEnter file to check:  \c"
                                    read _FNAME
                                    if [[ ! -e "${_FNAME}" ]]
                                    then
                                    echo "Unable to find file '${_FNAME}'"
                                    continue
                                    fi
                                    if [[ -f "${_FNAME}" ]]
                                    then
                                    echo "${_FNAME} is a file."
                                    elif [[ -d "${_FNAME}" ]]
                                    then
                                    echo "${_FNAME} is a directory."
                                    elif [[ -L "${_FNAME}" ]]
                                    then
                                    echo "${_FNAME} is a symbolic link."
                                    else
                                    echo "Unable to determine file type for '${_FNAME}'"
                                    fi
                                    [[ -r "${_FNAME}" ]] && echo "User ${USER} can read '${_FNAME}'"
                                    [[ -w "${_FNAME}" ]] && echo "User ${USER} can write to '${_FNAME}'"
                                    [[ -x "${_FNAME}" ]] && echo "User ${USER} can execute '${_FNAME}'"
                                    if [[ -s "${_FNAME}" ]]
                                    then
                                    echo "${_FNAME} is NOT empty."
                                    else
                                    echo "${_FNAME} is empty."
                                    fi
                                    done
                                    

执行清单 11 中的代码并检查几个文件名,会产生 清单 12 所示的输出。


清单 12. 执行测试操作符的输出
                                    # ls –l
                                    -rwxr-xr-x    1 cormany  atc             786 Feb 22 16:11 check_file
                                    -rw-r--r--    1 cormany  atc               0 Aug 04 20:57 emptyfile
                                    # ./check_file
                                    Enter file to check:  badfilename
                                    Unable to find file 'badfilename'
                                    Enter file to check:  check_file
                                    check_file is a file.
                                    User cormany can read 'check_file'
                                    User cormany can write to 'check_file'
                                    User cormany can execute 'check_file'
                                    check_file is NOT empty.
                                    Enter file to check:  emptyfile
                                    emptyfile is a file.
                                    User cormany can read 'emptyfile'
                                    User cormany can write to 'emptyfile'
                                    emptyfile is empty.
                                    

要想了解关于测试操作符的更多信息和完整列表,请执行 man test

逻辑操作符

UNIX 中的另一组重要操作符是逻辑操作符。与大多数现代编程语言一样,需要使用 ANDOR 语句对表达式或它们的值进行条件运算。

如果您读过我以前的文章(参见 参考资料),就会注意到我喜欢使用逻辑操作符,而不是编写多行代码。这会使脚本更干净,更容易管理。在编写脚本时,我会先编写 exit_msg() 函数:

exit_msg() {
                                    [[ $# -gt 1 ]] && echo "${0##*/} (${1}) – ${2}"
                                    exit ${1:-0}
                                    }
                                    

这样就不必编写 清单 13 那样臃肿的代码。


清单 13. 不使用 exit_msg() 函数和逻辑操作符的脚本
                                    #!/usr/bin/ksh
                                    if [[ -n ${_NUM1} ]]
                                    then
                                    unset _NUM1
                                    fi
                                    if [[ -n ${_NUM2} ]]
                                    then
                                    unset _NUM2
                                    fi
                                    while [[ -z ${_NUM1} ]] || [[ -z ${_NUM2} ]]
                                    do
                                    echo "Enter 2 sets of numbers:  \c"
                                    read _NUM1 _NUM2
                                    done
                                    echo "Enter file to log results to: \c"
                                    read _FNAME
                                    if [[ ! -e "${_FNAME}" ]]
                                    then
                                    echo "File '${_FNAME}' doesn't exist. A new log will be created."
                                    fi
                                    touch "${_FNAME}"
                                    if [[ ! -w "${_FNAME}" ]]
                                    then
                                    echo "Unable to write to file '${_FNAME}'"
                                    exit 1
                                    fi
                                    expr ${_NUM1} \/ 1 > /dev/null 2>&1
                                    if [[ $? -ne 0 ]]
                                    then
                                    echo "Number '${_NUM1}' is not numeric."
                                    exit 2
                                    fi
                                    expr ${_NUM2} \/ 1 > /dev/null 2>&1
                                    if [[ $? -ne 0 ]]
                                    then
                                    echo "Number '${_NUM2}' is not numeric."
                                    exit 2
                                    fi
                                    echo "${_NUM1},${_NUM2}" >> "${_FNAME}"
                                    

只需使用 exit_msg() 这样的简单函数和几个逻辑操作符,这个脚本就可以简化成更漂亮更容易理解的程序,见 清单 14


清单 14. 使用函数和逻辑操作符的脚本简化版本
                                    #!/usr/bin/ksh
                                    exit_msg() {
                                    [[ $# -gt 1 ]] && echo "${0##*/} (${1}) - ${2}"
                                    exit ${1:-0}
                                    }
                                    [[ -n ${_NUM1} ]] && unset _NUM1
                                    [[ -n ${_NUM2} ]] && unset _NUM2
                                    while [[ -z ${_NUM1} ]] || [[ -z ${_NUM2} ]]
                                    do
                                    echo "Enter 2 sets of numbers:  \c"
                                    read _NUM1 _NUM2
                                    done
                                    echo "Enter file to log results to: \c"
                                    read _FNAME
                                    [[ ! -e "${_FNAME}" ]] && echo
                                    "File '${_FNAME}' doesn't exist. A new log will be created."
                                    touch "${_FNAME}"
                                    [[ ! -w "${_FNAME}" ]] && exit_msg 1
                                    "Unable to write to file '${_FNAME}'"
                                    expr ${_NUM1} \/ 1 > /dev/null 2>&1
                                    [[ $? -ne 0 ]] && exit_msg 2 "Number '${_NUM1}' is not numeric."
                                    expr ${_NUM2} \/ 1 > /dev/null 2>&1
                                    [[ $? -ne 0 ]] && exit_msg 2 "Number '${_NUM2}' is not numeric."
                                    echo "${_NUM1},${_NUM2}" >> "${_FNAME}"
                                    

前面的示例主要使用 AND&&)和 OR||)逻辑操作符。除了这些之外,还可以使用 AND–a)和 OR–o)操作符。在使用 test 命令或单层方括号([ ])时,使用 –a–o 运算表达式。但是,如果使用双层方括号([[ ]]),就要使用 &&||

# [[ "Paul" != "Xander" && 2 -gt 0 ]]
                                    # echo $?
                                    0
                                    # [ "Paul" != "Xander" -a 2 -gt 0 ]
                                    # echo $?
                                    0
                                    

比较测试操作符

另一组测试操作符称为比较测试操作符。与前一组测试操作符一样,比较测试操作符是执行错误检查或根据另一个值测试值的简便方法。前一组测试操作符主要用来测试文件或检查一个变量是否已经定义了,而比较测试操作符主要用来测试字符串和数字值,例如检查日期、检查文件大小、检查两个字符串是否相同等等。

比较测试操作符包括:

  • <fileA> -nt <fileB>fileA 比 fileB 新
  • <fileA> -ot <fileB>fileA 比 fileB 旧
  • <fileA> -ef <fileB>fileA 和 fileB 指向同一个文件
  • <string> = <pattern>字符串与模式匹配
  • <string> != <pattern>字符串与模式不匹配
  • <stringA> < <stringB>按照词典次序 stringA 出现在 stringB 之前
  • <stringA> > <stringB>按照词典次序 stringA 出现在 stringB 之后
  • <exprA> -eq <exprB>expressionA 等于 expressionB
  • <exprA> -ne <exprB>expressionA 不等于 expressionB
  • <exprA> -lt <exprB>expressionA 小于 expressionB
  • <exprA> -gt <exprB>expressionA 大于 expressionB
  • <exprA> -le <exprB>expressionA 小于或等于 expressionB
  • <exprA> -ge <exprB>expressionA 大于或等于 expressionB

可以按照与其他操作符相同的格式使用比较测试操作符。可以使用 test[ ][[ ]]清单 15清单 16清单 17 分别演示如何使用文件、数字和字符串比较。


清单 15. 文件比较
                                    # ls -l *.file
                                    -rw-r--r--    1 cormany  atc              21 Feb 22 2006  Pauls.file
                                    -rw-r--r--    1 cormany  atc              22 Aug 04 20:57 Xanders.file
                                    # [[ "Pauls.file" -ot "Xanders.file" ]]
                                    # echo $?
                                    0
                                    


清单 16. 数字比较
                                    # _PSIZE=`ls -l Pauls.file | awk '{print $5}'`
                                    # _XSIZE=`ls -l Xanders.file | awk '{print $5}'`
                                    # [[ ${_PSIZE} -lt ${_XSIZE} ]]
                                    # echo $?
                                    0
                                    


清单 17. 字符串比较
                                    # [[ "cat" = "dog" ]]
                                    # echo $?
                                    1
                                    

替换操作符

在编写脚本时,很容易忘记定义一个变量或为它赋值。或者,您好几年没有改动一个脚本,现在需要修改它,但是忘了它的细节。有时候,希望告诉用户已经设置了一个值,或者设置了某些默认值。替换操作符是解决这些问题的好方法:

  • ${var-value}如果 <var> 存在,就返回 <var> 的值。如果 <var> 不存在,就返回 <value>
  • ${var=value}如果 <var> 存在,就返回 <var> 的值。如果 <var> 不存在,就把 <var> 设置为 <value> 并返回 <value>
  • ${var+value}如果 <var> 存在,就返回 <var> 的值。如果 <var> 不存在,就返回 NULL。
  • ${var?value}如果 <var> 存在,就返回 <var> 的值。如果 <var> 不存在,就退出命令或脚本并显示用 <value> 设置的错误消息。如果未设置 <value>,就显示默认的错误消息 “Parameter null or not set”。
  • ${var:-value}如果 <var> 存在且不是 NULL,就返回 <var> 的值。如果 <var> 不存在或者是 NULL,就返回 <value>
  • ${var:=value}如果 <var> 存在且不是 NULL,就返回 <var> 的值。如果 <var> 不存在或者是 NULL,就把 <var> 设置为 <value> 并返回 <value>
  • ${var:+value}如果 <var> 存在且不是 NULL,就返回 <var> 的值。如果 <var> 不存在或者是 NULL,就返回 NULL。
  • ${var:?value}如果 <var> 存在且不是 NULL,就返回 <var> 的值。如果 <var> 不存在或者是 NULL,就退出命令或脚本并显示用 <value> 设置的错误消息。如果未设置 <value>,就显示默认的错误消息 “Parameter null or not set”。

请注意前四个和后四个定义之间的细微差异。后一组定义在变量名和替换操作符之间包含一个冒号(:),这会额外检查变量是否是 NULL。在考虑用替换操作符给变量赋值时,要注意的另一个重要问题是,给变量赋值应用的规则与以一般方式从命令行或脚本定义变量的规则相同。不能用新值覆盖受保护的保留变量(例如,$1$2$3)。

清单 18 中的示例演示替换操作符的使用方法。注意,可以组合使用多个替换操作符,见脚本的最后一行。


清单 18. 使用替换操作符
                                    # cat subops_examples
                                    #!/usr/bin/ksh
                                    _ARG1="${1}"
                                    echo "Test 1A: The 1st argument is ${_ARG1-'ATC'}"
                                    echo "Test 1B: The 1st argument is ${_ARG1:-'ATC'}"
                                    _ARG2="${2}"
                                    echo "Test 2A: The 2nd argument is ${_ARG2-'AMDC'}"
                                    echo "Test 2B: The 2nd argument is ${_ARG2:-'AMDC'}"
                                    _ARG3="${3}"
                                    echo "Test 3A: The 3rd argument is ${_ARG3='PAC'}"
                                    echo "Test 3B: The 3rd argument is ${_ARG3:='PAC'}"
                                    _ARG4="${4}"
                                    echo "Test 4A: ${4:+'The 4th argument was supplied'}"
                                    echo "Test 5: If the 4th argument was provided, the value would be
                                    ${4:?'The 4th argument was not supplied.'}. Otherwise, we will not
                                    see this message and get an error instead."
                                    _ARG8="${8}"
                                    echo "${_ARG8:=${7:-${6:-${5:-No Arguments were supplied after the 4th}}}}"
                                    

清单 19 显示在不提供参数的情况下执行这个脚本的情况。


清单 19. 不带参数执行脚本
                                    # ./subops_examples
                                    Test 1A: The 1st argument is
                                    Test 1B: The 1st argument is ATC
                                    Test 2A: The 2nd argument is
                                    Test 2B: The 2nd argument is AMDC
                                    Test 3A: The 3rd argument is
                                    Test 3B: The 3rd argument is PAC
                                    Test 4A:
                                    ./subops_examples[18]: 4: The 4th argument was not supplied.
                                    

清单 20 显示在只提供三个参数的情况下执行脚本会发生什么。


清单 20. 用三个参数执行脚本
                                    # ./subops_examples arg1 arg2 arg3
                                    Test 1A: The 1st argument is arg1
                                    Test 1B: The 1st argument is arg1
                                    Test 2A: The 2nd argument is arg2
                                    Test 2B: The 2nd argument is arg2
                                    Test 3A: The 3rd argument is arg3
                                    Test 3B: The 3rd argument is arg3
                                    Test 4A:
                                    ./subops_examples[18]: 4: The 4th argument was not supplied.
                                    

清单 21 显示在只提供四个参数的情况下会发生什么。


清单 21. 用四个参数执行脚本
                                    # ./subops_examples arg1 arg2 arg3 arg4
                                    Test 1A: The 1st argument is arg1
                                    Test 1B: The 1st argument is arg1
                                    Test 2A: The 2nd argument is arg2
                                    Test 2B: The 2nd argument is arg2
                                    Test 3A: The 3rd argument is arg3
                                    Test 3B: The 3rd argument is arg3
                                    Test 4A: The 4th argument was supplied
                                    Test 5: If the 4th argument was provided, the value would be
                                    arg4. Otherwise, we will not see this message and get an
                                    error instead.
                                    No Arguments were supplied after the 4th
                                    

清单 22 显示提供所有五个参数时的情况。


清单 22. 用所有五个参数执行脚本
                                    # ./subops_examples arg1 arg2 arg3 arg4 arg5
                                    Test 1A: The 1st argument is arg1
                                    Test 1B: The 1st argument is arg1
                                    Test 2A: The 2nd argument is arg2
                                    Test 2B: The 2nd argument is arg2
                                    Test 3A: The 3rd argument is arg3
                                    Test 3B: The 3rd argument is arg3
                                    Test 4A: The 4th argument was supplied
                                    Test 5: If the 4th argument was provided, the value would be
                                    arg4. Otherwise, we will not see this message and get an
                                    error instead.
                                    arg5
                                    

清单 23 显示提供七个参数时的情况。注意,因为提供了七个参数,所以第五和第六个参数被忽略。


清单 23. 用七个参数执行脚本
                                    # ./subops_examples arg1 arg2 arg3 arg4 arg5 arg6 arg7
                                    Test 1A: The 1st argument is arg1
                                    Test 1B: The 1st argument is arg1
                                    Test 2A: The 2nd argument is arg2
                                    Test 2B: The 2nd argument is arg2
                                    Test 3A: The 3rd argument is arg3
                                    Test 3B: The 3rd argument is arg3
                                    Test 4A: The 4th argument was supplied
                                    Test 5: If the 4th argument was provided, the value would be
                                    arg4. Otherwise, we will not see this message and get an
                                    error instead.
                                    arg7
                                    





回页首


结束语

阅读本文之后,您应该更好地理解了 UNIX 用户输入的那些 “奇怪的” 字符。您了解了如何把数据重定向到 stdin 或 stdout,如何使用管道,以及如何在 UNIX 中使用操作符;这些都有助于您编写出更强大的脚本,更好地捕捉错误,使逻辑更简洁清晰。祝您好运!



参考资料

学习

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多