BBS水木清华站∶精华区

发信人: freedom@csie.nctu.edu.tw (Tan Koan-Sin), 信区: unix 
标  题: UNIX FAQ 中文版 Part 3 
发信站: Computer Sci. & Information Eng., NCTU,  (Tue Jul 11 03:10:27 1995) 
转信站: phoenix!news.csie.nctu!freedom 
Origin: freedom@dragon.csie.nctu.edu.tw 
 
 
这七篇文章包含一些在 comp.unix.questions 和 comp.unix.shell 常见到的问 
题。请不再问这些问题,因为这些问题已经被回答过太多次了。但也请不要因为 
有人问这些问题而发火,因为他们可能尚未读过这些文章。 
 
This collection of documents is Copyright (c) 1994, Ted Timar, except 
Part 6, which is Copyright (c) 1994, Pierre Lewis and Ted Timar. 
 
All rights reserved. Permission to distribute the collection is 
hereby granted providing that distribution is electronic, no money is 
involved, reasonable attempts are made to use the latest version and 
all credits and this copyright notice are maintained. 
 
Other requests for distribution will be considered. 
 
All reasonable requests will be granted. 
 
中文翻译 by {chenjl,freedom,jjyang}@csie.nctu.edu.tw 
若您对中文翻译有任何意见请发 e-mail 给 cfaq@csie.nctu.edu.tw 
 
我们希望这些文件中的资讯能对你有所帮助,但是并不保证是正确的。若发生损 
害请自行负责。 
 
您可以在 rtfm.mit.edu 的 pub/usenet/news.answers 找到包括此文件在内的 
许多 FAQ。 在此目录下的 FAQ 的名字可在文章的顶端的 "Archive-Name:" 
那一行找到。 
 
[译注: 在台湾请用 NCTUCCA.edu.tw:/USENET/FAQ,在交大的话 
       ftp.csie.nctu.edu.tw:/pub/FAQ 是从 CCCA mirror 来的] 
 
  此一 FAQ 是以"unix-faq/faq/part[1-7]" 为名。 
 
这些文章大约分成: 
 
      1.*)一般性的问题 
      2.*)初学者可能会问的基本问题 
      3.*) 中级的问题 
      4.*) 自以为已经知道所有答案的人可能会问的高级问题 
      5.*) 关於各种 shell 的问题 
      6.*) 各式各样的 Unix 
      7.*) An comparison of configuration management systems (RCS, SCCS). 
 
This article includes answers to: 
 
      3.1)  要如何得知一个档案建立的时间? 
      3.2)  在执行 rsh 的时候要怎样才能不必等远方指令执行结束就回到 shell? 
      3.3)  要怎样才能截断一个档案? 
      3.4)  为什麽执行 find 时所使用的 {} 符号无法达到我预期的结果? 
      3.5)  我要如何改变一个 symbolic link 的 permission 呢? 
      3.6)  我要如何 "undelete" 一个档案? 
      3.7)  一个process 要怎样侦测出自己是否在背景状态执行? 
      3.8)  为什麽在 Bourne shell 当中,对回圈的输出入转向无法达到预期的效果? 
      3.9)  我要怎麽在一个 shell script 中或在背景执行 'ftp'、'telnet'、'tip' 
            等 interactive 的程式呢? 
      3.10) 在 shell script 或 C 程式当中,要怎样才能找到某个程式的 process ID 
            呢? 
      3.11) 我要怎样经由 rsh 执行远方指令时,检查远方指令的结束状态? 
      3.12) 能不能把 shell 变数传进 awk 程式当中呢? 
      3.13) 要怎样才能避免在记忆体中留下zombie processes? 
      3.14) 当我要从 pipe 读一行输入时,要如何才能让这行资料像是直接从键盘输 
            入而非从一个大 block buffer 来的? 
      3.15) 我要如何在档案名字中加入日期? 
      3.16) 为什麽有一些 script 是用 #! ... 做为档案的开端? 
 
若要找问题 3.5 的答案, 用 regular expression 往前找 "^3.5)" 即可。 
 
因为这些都是正当合理的问题, 所以在 comp.unix.questions 或是 
comp.unix.shell 中。每隔一阵子, 就会有这些问题与答案出现, 紧接著就会 
有人对同样问题一再出现发牢骚。关於 UNIX 代表啥呢? 请参考每月 post 在 
news.announce.newusers 中名为 "Answers to Frequently Asked Questions" 
的文章。 
 
因为 Unix 有太多不同的种类了, 所以很难保证此文件所提供的答案必然会有 
用。在尝试本文件提供的作法前, 请先读读你所使用系统的手册。若你对答案 
有任何建议或更正, 请送 email 给 tmtaimar@isgtec.com. 
 
 
 ======================================================================== < 
 
Subject: How do I find the creation time of a file? 
Date: Thu Mar 18 17:16:55 EST 1993 
 
3.1)    我要如何得知一个档案建立的时间? 
 
        很遗憾,因为档案建立的时间并未储存在任何地方,所以答案是无法得知。 
        关於一个档案你只能查到最後修改的时间("ls -l"),最後读取的时间 
        ("ls -lu") 与 inode 改变的使间。有一些 man pages 将最後一个时间当 
        成是建立的时间,这种说法是错的。因为 mv、ln、chmod、chmod、chown、 
        chgrp 等动作都会改变这个时间。 
 
        若需更详尽的说明可参考 "stat(2)" 的 man page. 
 
------------------------------ 
 
Subject: How do I use "rsh" without having the rsh hang around ... ? 
Date: Thu Mar 18 17:16:55 EST 1993 
 
3.2)    在执行 rsh 的时候要怎样才能不必等远方指令执行结束就回到 shell? 
        (关於我们所讨论的 rsh,请参阅问题2.7) 
 
        以下这些凭直觉想到的答案都达不到这个效果: 
                rsh machine command & 
        或 
                rsh machine 'command &' 
 
        例如, 执行 rsh machine 'sleep 60 &' 这个命令时,我们可以观察到:rsh 并 
        不会立刻结束,而是等到远方的 sleep 命令完成以後才结束,即使我们在远 
        方使用背景方式执行此命令。所以要怎样才能让 rsh 在 sleep命令启动後立 
        刻结束呢? 
 
        答案如下- 
 
        如果您在远端使用csh: 
                rsh machine -n 'command >&/dev/null </dev/null &' 
 
        如果您在远端使用 sh: 
                rsh machine -n 'command >/dev/null 2>&1 </dev/null &' 
 
        为什麽呢?因为 "-n" 会把 rsh 的stdin接到 /dev/null,因此您可以在本地 
        机器以背景方式执行整个 rsh 命令。不管是使用 -n 选项或者在指令结尾使 
        用 "/dev/null",其效果都是一样的。此外,在远端机器使用的输出入转向(写 
        在单引号内的部份)会让 rsh 认定此次连线可迳行结束(因为已无其他输 
        入资料)。 
 
        附注: 任何档案都可以用於远端机器的输出入转向,而不仅限於 /dev/null。 
 
        在许多状况下,这个复杂的命令当中有很多部份都是非必要的。 
 
------------------------------ 
 
Subject: How do I truncate a file? 
Date: Mon, 27 Mar 1995 18:09:10 -0500 
 
3.3)    要怎样才能截断一个档案? 
 
        BSD 的函数ftruncate() 可以设定档案的长度。但是并不是每一种版本的动作 
        都一样。其他 UNIX 的变种似乎也都支援其他版本的截断功能。 
 
        支援 ftruncate函数的系统多半可归类为以下三种: 
 
        BSD 4.2 - Ultrix, SGI, LynxOS 
                -无法使用截断功能来增加档案长度 
                -执行截断动作不会移动档案指标 
 
        BSD 4.3 - SunOS, Solaris, OSF/1, HP/UX, Amiga 
                -可以用截断功能来增加档案长度 
                -执行截断动作不会移动档案指标 
 
        Cray - UniCOS 7, UniCOS 8 
                -无法使用截断功能来增加档案长度 
                -执行截断动作会移动档案指标 
 
        其他系统则可能在以下四个地方与众不同: 
 
        F_CHSIZE - 只在SCO 上 
                -有些系统定义了F_CHSIZE 但并没有真的支援此功能 
                -动作类似BSD 4.3 
 
        F_FREESP - 只在 Interative Unix 上 
                -有些系统(如Interactive Unix)定义了F_FREESP 但并没有真的支援此 
                 功能 
                -动作类似BSD 4.3 
 
        chsize() - QNX and SCO 
                -有些系统(如Interactive Unix)有chsize() 函数但并没有真的支援 
                 此功能 
                -动作类似BSD 4.3 
 
        「空空如也」-目前找不到这种系统 
                -也许会有系统完全不支援 truncate功能 
 
        FAQ 维护者的注解:以下是我在几年前从网路抓到的程式,原作者已不可考, 
                         不过S.Spencer Sun <spencer@ncd.com> 也贡献了一份 
                         F_FREESP的功能。 
 
              functions for each non-native ftruncate follow 
 
              /* ftruncate emulations that work on some System V's. 
                 This file is in the public domain. */ 
 
              #include 
              #include 
 
              #ifdef F_CHSIZE 
              int 
              ftruncate (fd, length) 
                   int fd; 
                   off_t length; 
              { 
                return fcntl (fd, F_CHSIZE, length); 
              } 
              #else 
              #ifdef F_FREESP 
              /* The following function was written by 
                 kucharsk@Solbourne.com (William Kucharski) */ 
 
              #include 
              #include 
              #include 
 
              int 
              ftruncate (fd, length) 
                   int fd; 
                   off_t length; 
              { 
                struct flock fl; 
                struct stat filebuf; 
 
                if (fstat (fd, &filebuf) < 0) 
                  return -1; 
 
                if (filebuf.st_size < length) 
                  { 
                    /* Extend file length. */ 
                    if (lseek (fd, (length - 1), SEEK_SET) < 0) 
                      return -1; 
 
                    /* Write a "0" byte. */ 
                    if (write (fd, "", 1) != 1) 
                      return -1; 
                  } 
                else 
                  { 
                    /* Truncate length. */ 
                    fl.l_whence = 0; 
                    fl.l_len = 0; 
                    fl.l_start = length; 
                    fl.l_type = F_WRLCK;      /* Write lock on file space. */ 
 
        /* This relies on the UNDOCUMENTED F_FREESP argument to 
           fcntl, which truncates the file so that it ends at the 
           position indicated by fl.l_start. 
           Will minor miracles never cease? */ 
                  if (fcntl (fd, F_FREESP, &fl) < 0) 
                      return -1; 
                  } 
 
                return 0; 
              } 
              #else 
              int 
              ftruncate (fd, length) 
                   int fd; 
                   off_t length; 
              { 
                return chsize (fd, length); 
              } 
              #endif 
              #endif 
 
------------------------------ 
 
Subject: Why doesn't find's "{}" symbol do what I want? 
Date: Thu Mar 18 17:16:55 EST 1993 
 
3.4)    为什麽执行 find 时所使用的 {} 符号无法达到我预期的结果? 
 
        Find 指令有一个 -exec 的选项会针对每一个找到的档案执行一个特殊 
        的指令。Find 会把出现{}的地方置换成目前找到的档案名称。因此, 
        也许有一天您会使用 find 指令对每一个档案执行某个指令,或者对 
        一个目录执行某个指令。 
 
                find /path -type d -exec command {}/\* \; 
 
        希望 find 能依序执行以下指令: 
 
                command directory1/* 
                command directory2/* 
                ... 
 
        不幸的是,find 只会展开自成一体的 {} token;如果 {} 跟其他字元相连 
        的话(如:{}/*),那麽find将不会以您所想的方式展开 {}, 而是转换为以 
        下命令 
 
                command {}/* 
                command {}/* 
                ... 
 
        也许您可以把它当成 bug, 也可以把它看成是故意设计的特异功能。但我们 
        可不愿被目前这个特异功能干扰。所以要怎样避免这个问题呢?其中一种做 
        法是写一个小小的 shell script,名称就叫做 ./doit 好了,其内容如下: 
 
                command "$1"/* 
 
        那麽您就可以把原来的命令行改写为 
 
                find /path -type d -exec ./doit {} \; 
 
        如果您想省掉 ./doit 这个 shell script, 可以这麽写: 
 
                find /path -type d -exec sh -c 'command $0/*' {} \; 
 
        (这种写法可行的原因是 "sh -c 'command' A B C ..."指令当中,$0会展开为 
        A, $1会展开为B, 依此类推) 
 
        或者您也可以略施小计使用 sed 来造出您想执行的指令行: 
 
                find /path -type d -print | sed 's:.*:command &/*:' | sh 
 
        如果您想减少 command 的执行次数,您可以先检查看看系统中有没有 
        xargs 这个指令, xargs会从标准输入一次读取一行,并且把这些读入的资料 
        合并至一个命令行内。您可以写成以下命令行: 
 
                find /path -print | xargs command 
 
        这样会使以下指令执行一次或多次: 
 
                command file1 file2 file3 file4 dir1/file1 dir1/file2 
 
        很不幸地,这并不是完美无缺或者万无一失的解法,输入 xargs 的文字行 
        必须以换行字元结尾,所以当档案名称当中有奇怪的字元(如换行字元)时, 
        xargs就会因此而混淆。 
 
------------------------------ 
 
Subject: How do I set the permissions on a symbolic link? 
Date: Thu Mar 18 17:16:55 EST 1993 
 
3.5)  我要如何改变一个 symbolic link 的 permission 呢? 
 
        这个问题没有意义,因为 symbolic link的 permission 根本不代表什麽。 
        那个 link 所指过去的档案的 permission 才有意义。 
 
------------------------------ 
 
Subject: How do I "undelete" a file? 
Date: Thu Mar 18 17:16:55 EST 1993 
 
3.6)  我要如何 "undelete" 一个档案? 
 
        某年某月的某一天,要删除 "*.foo" 却一不小心打成了 "rm * .foo", 
        结果发现竟把 "*" 都删除了。真的是欲哭无泪啊!可是你也只好把这当成 
        是成长的代价了。 
 
        当然一个称职的系统管理员应当会定期做备份。先问一问你的系统管理员看 
        你不小心删除的档案是不是有备份起来。如果没有的话,嗯,继续往下看吧! 
 
        不管你是不是真的要删除一个档案,当你下了 "rm" 以後,档案就不见了。 
        在你 "rm" 一个档案,系统就不再记得你的档案是用了硬碟中的哪些 block 
        了。更糟糕的是,当系统要用到更多的硬碟空间时,就优先取用这些刚放出 
        来的 block。不过天底下没有不可能的事。理论上说,若你在下了 "rm" 後, 
        马上把系统 shutdown,资料是就得回来的。不过,你得找一个对系统非常 
        熟悉且肯花费数小时至数天的时间来帮你做这件事专家才行。 
 
        当你不小心 "rm" 了一个档案後,第一个反应或许是为什麽不用一个 alias 
        或在 sh 中的 function 将 "rm"  取代掉,当你下 "rm" 只把档案搬到一个 
        垃圾桶之类的地方呢?那如果不小心杀错档案就可以挽救,只是要定期清一 
        清垃圾桶就好了。有两个理由。第一,大多数的人不认为这是一个好的做法。 
        这麽做的话你会太依赖你的 "rm",有一天到了一个正常的系统中把正常的 
        "rm" 当成你的 "rm" 来用,那可能会死得很惨。第二,你会发现你花费了 
        许多不必要的时间在处理垃圾桶里的东西。所以对一个初学者而言呢,用 
        "rm" 的 -i 选项应该就够了。 
 
        如果你有大无畏的精神的话,那好吧,就给你个简单的答案。写一个名为 
        "can" 的指令,功用是将档案移到垃圾桶里。在 csh(1) 中,将以下的东西 
        放进 ".login" 里: 
 
        alias can       'mv \!* ~/.trashcan'       # junk file(s) to trashcan 
        alias mtcan     'rm -f ~/.trashcan/*'      # irretrievably empty trash 
        if ( ! -d ~/.trashcan ) mkdir ~/.trashcan  # ensure trashcan exists 
 
        如果你想要每次 logout 时都把垃圾桶清乾净,那就把 
 
        rm -f ~/.trashcan/* 
 
        进 ".logout" 里。若你用的是 sh 或是 ksh,那自己试试著写写看吧! 
 
        MIT 的雅典娜计画(Project Athena)作出了一套有 
        delete/undelete/expunge/purge 的软体。这套软体可以完全取代 "rm" 而又提 
        供 undelete 的功能。这个软体曾 post 在 comp.sources.misc(volume 17, 
        issue 023-025)。 
 
------------------------------ 
 
Subject: How can a process detect if it's running in the background? 
Date: Thu Mar 18 17:16:55 EST 1993 
 
3.7)    一个process 要怎样侦测出自己是否在背景状态执行? 
 
        首先,您是否想知道您自己是在背景状态下执行,或者在交谈状态下执行?如果 
        您只是想藉此决定是否该在终端机上印出提示符号之类的讯息,那麽更合适的做 
        法应该是检查您的标准输入是否为终端机: 
 
            sh: if [ -t 0 ]; then ... fi 
            C: if(isatty(0)) { ... } 
 
        一般来说,您无法得知自己是否在背景状态下执行。问题的根本在於不同的shell 
 
        与不同的 UNIX 版本对於「前景」与「背景」的定义可能有所不同。而且在最 
        常见的系统上,前景与背景都有较好的定义,程式甚至可以在背景与前景之间任 
        意切换! 
 
        在没有 job control 的UNIX系统上,若要把 process 放入背景状态通常是把 
        SIGINT 与 SIGQUIT 忽略掉,并且把标准输入转为"/dev/null",这是由shell处 
        理的。 
 
        在具有 job control 功能的 UNIX 系统,若shell支援 job control功能,那麽 
        shell只要把 process group ID 设成跟 terminal 所属的 PGID 不同即可把 
        process 切换至背景状态;如果要把 process 切回前景状态,只要把此 process 
        的 PGID 设成跟目前 terminal 所属的 PGID 即可。如果 shell 不支援 job 
        control 功能,则不管UNIX 系统是否支援 job control 的功能,shell 对 
        process 的处理动作都是一样的(也就是忽略SIGINT 与 SIGQUIT,并且把标准 
        输入转为"/dev/null")。 
 
------------------------------ 
 
Subject: Why doesn't redirecting a loop work as intended?  (Bourne shell) 
Date: Thu Mar 18 17:16:55 EST 1993 
 
3.8)    为什麽在 Bourne shell 当中,对回圈的输出入转向无法达到预期的效果? 
 
        举个例子来说好了: 
 
                foo=bar 
                while read line 
                do 
                # do something with $line 
                    foo=bletch 
                done < /etc/passwd 
 
                echo "foo is now: $foo" 
 
        尽管 "foo=bletch" 已经设定了 foo 的值,然而在多种系统的 Bourne shell 
        上执行此 script 的时候仍会印出 "foo is now: bar"。为什麽呢?因为一些 
        历史因素,在 Bourne shell 当中,一个控制结构(如一个回圈,或者一个 
        "if" 叙述)的重导向会造出一个新的 subshell,所以啦,在此 subshell 内 
        所设定的变数当然不会影响目前 shell 的变数。 
 
        POSIX 1003.2 Shell and Tools Interface 的标准委员会已防止上述的问题, 
        也就是上述的例子在遵循P1003.2 标准的Bourne shells当中会印出 
        "foo is now: bletch"。 
 
        在一些较古老的 (以及遵循 P1003.2 标准的) Bourne shell 当中,您可以使 
        用以下技巧来避免重转向的问题: 
 
                foo=bar 
                # make file descriptor 9 a duplicate of file descriptor 0 
                # stdin); 
                # then connect stdin to /etc/passwd; the original stdin is now 
                # `remembered' in file descriptor 9; see dup(2) and sh(1) 
                exec 9<&0 < /etc/passwd 
 
                while read line 
                do 
                # do something with $line 
                    foo=bletch 
                done 
 
                # make stdin a duplicate of file descriptor 9, i.e. reconnect 
                # it to the original stdin; then close file descriptor 9 
                exec 0<&9 9<&- 
                echo "foo is now: $foo" 
 
        这样子不管在哪种 Bourne shell 应该都会印出 "foo is now: bletch"。 
        接下来,看看以下这个例子: 
 
                foo=bar 
 
                echo bletch | read foo 
 
                echo "foo is now: $foo" 
 
        这个例子在许多 Bourne shell 内都会印出 "foo is now: bar",有些则会 
        印出 "foo is now: bletch"。为什麽呢?一般说来,一个 pipeline 里面 
        的每一个部份都是在一个 subshell 中执行。但是有些系统的里 pipeline 
        的最後一个如果是如 "read" 这类的内建指令,并不会另外造出一个 
        subshell。 
 
        POSIX 1003.2 对这两种作法并没有硬性规定要用哪一种。所以一个 portable 
        的 shell script 不应该依赖这两种作法其中的一种。 
 
------------------------------ 
 
Subject: How do I run ... interactive programs from a shell script ... ? 
Date: Thu Mar 18 17:16:55 EST 1993 
 
3.9)  我要怎麽在一个 shell script 中或在背景执行 'ftp'、'telnet'、'tip' 等 
      interactive 的程式呢? 
 
        这些程式需要一个 terminal interface。这是 shell 所无法提供的。所以这些 
        无法在 shell script 里自动执行这些程式。 
 
        有一个就做 'expect' 的程式,可以用来做这件事,因为它提供了 
        programmable terminal interface。底下的例子是用 'expect' 来帮你 
login: 
 
                # username is passed as 1st arg, password as 2nd 
                set password [index $argv 2] 
                spawn passwd [index $argv 1] 
                expect "*password:" 
                send "$password\r" 
                expect "*password:" 
                send "$password\r" 
                expect eof 
 
        expect 为 telnet, rlogin,debugger 和一些没有内建 command language 的 
        程式提供了一个近乎自动化的方法。Expect 里面的有一用以在玩 rogue 
        (一个 Unix 中的古老游戏)时取得较佳初始情况,然後将控制权还回给使用者 
        的例子。用这个 script 你就能得到『成功的一半』。 
 
        再者,有一些已经写好的程式可以帮你这类与 pseudo-tty 有关的东西,所 
        以你只要在 script 中执行这些程式就可以帮你处理这些东西。 
 
        有两个方法可以取得 'expect': 
        1.送一封 email 给 library@cme.nist.gov 内容就写 "send 
          pub/expect/expect.shar.Z" 
        2. ftp://ftp.cme.nist.gov/pub/expect/expect.shar.Z 
 
        另一个做法是用一个就 pty 4.0 曾贴在 comp.sources.unix volume25的东 
        西。这个程式会提供一个 pseudo-tty session 给需要 tty 的程式用。若使用 
        named pipe 配合 pty 4.0 来做上例,则看起来可能如下: 
 
               #!/bin/sh 
                /etc/mknod out.$$ p; exec 2>&1 
                ( exec 4<out.$$; rm -f out.$$ 
                <&4 waitfor 'password:' 
                    echo "$2" 
                <&4 waitfor 'password:' 
                    echo "$2" 
                <&4 cat >/dev/null 
                ) | ( pty passwd "$1" >out.$$ ) 
 
        上面的 'waitfor' 是简单的 C 程式,功用为等到 input 有与所等待的字串 
        相同时再往下做。 
 
        下面是一个更简单的做法,不过缺点是与 'passwd' 程式的互动可能无法同 
        步。 
 
                #!/bin/sh 
                ( sleep 5; echo "$2"; sleep 5; echo "$2") | pty passwd "$1" 
 
------------------------------ 
 
Subject: How do I find the process ID of a program with a particular name ... 

Date: Thu Mar 18 17:16:55 EST 1993 
 
3.10)   在 shell script 或 C 程式当中,要怎样才能找到某个程式的 process ID 
        呢? 
 
        在 shell script 当中: 
 
        没有现成的程式可以用来查询程式名称与 process ID 之间的对应。此外, 
        如果有对应的话,通常也都不太可信,因为可能会有多个 process 执行同一 
        个名称的程式,而且 process 在启动之後仍可修改自己的名称。然而,如果 
        您真的想要得知执行某个特定程式的所有 process, 可以利用以下命令行达 
        成: 
                ps ux | awk '/name/ && !/awk/ {print $2}' 
 
        您可以把 "name" 换成您想寻找的程式名称。 
 
        这个命令行的基本观念是分析 ps 程式的输出,然後用 awk或grep等公用 
        程式来搜寻具有特定名称的文字行,然後把这些文字行当中的 PID 栏位印 
        出来。值得注意的是此例的命令行用了 "!/awk/" 以避免 awk 的 process 被 
        列出来。 
 
        您可能要根据您所用的 Unix 种类来调整 ps 所用的参数。 
 
        在 C 语言程式里面: 
 
        在 C 的程式库里面一样没有(具有可携性)的函数可以找出程式名称与 
        process IDs。 
 
        然而有些厂商提供函数让您能读取 Kernel 的记忆体,例如 Sun 提供了 
        kvm_ 开头的函数,Data General 则提供了 dg_ 开头的函数。如果您的系 
        统管理员未限定 Kernel 记忆体的读取权力的话(一般只有 super user 或 
        kmem 群组里的人员才能读取 Kernel 记忆体),一般使用者也可以利用这 
        些特殊函数来达到目的。然而,这些函数通常没有正式的文件说明,就算有 
        的话也都写得艰深难懂,甚至会随著系统版本的更新而改变。 
 
        有些厂商会提供 /proc 档案系统,此档案系统存在的方式为一个内含多个档 
        案的目录。每个档名都是一个数字,对应於 process ID,您可以开启这个档 
        案并且读取关於这个 process 的资讯。再次提醒一下,有时候您会因为存取 
        权限的限制而无法使用这些功能,而且使用这些功能的方式也随著系统而 
        变。 
 
        如果您的厂商并没有提供特殊的程式库或者 /proc 来处理这些事,但是您又 
        想要在 C 里面完成这些功能,那麽您可能要自己在Kernel 记忆体当中费心 
        搜寻。如果您想看看这些功能在某些系统上是怎麽做到的,可以参考 ofiles 
        的原始程式,您可以从 comp.source.sources.unix 的历年归档文章当中取 
        得。(有一个称为 kstuff 的套装程式曾经在 1991 年五月发表於 
        alt.sources,它可以帮您在 kernel 当中搜寻有用的资讯,您可以到 
        wuarchive.wustl.edu 利用匿名 ftp 取回 
        usenet/alt.sources/articles/{329{6,7,8,9},330{0,1}}.Z。) 
 
------------------------------ 
 
Subject: How do I check the exit status of a remote command executed via 
"rsh"? 
Date: Thu Mar 18 17:16:55 EST 1993 
 
3.11)   我要怎样经由 rsh 执行远方指令时,检查远方指令的结束状态? 
 
        以下指令行是行不通的: 
 
                rsh some-machine some-crummy-command || echo "Command failed" 
 
        如果 rsh 程式本身能成功地执行,那麽 rsh 程式的结束状态就是 0,但这 
        也许不是您真正想要的结果。 
        如果您想检查远方程式的执行状态,您可以试试Maarten Litmaath 於 1994 
        年十月在 alt.sources发表的 "ersh" script,ersh 是一个呼叫 rsh 的 shell 
        script,它会安排远方的机器回应远方指令的结束状态,并传回此结束状态。 
 
------------------------------ 
 
Subject: Is it possible to pass shell variable settings into an awk program? 
Date: Thu Mar 18 17:16:55 EST 1993 
 
3.12)   能不能把 shell 变数传进 awk 程式当中呢? 
 
        这个问题有两个可行的方法,第一个方法只是把程式当中需要用到此变数的 
        地方直接展开,例如要得知您目前使用哪些 tty,可以使用: 
 
        who | awk '/^'"$USER"'/ { print $2 }'                           (1) 
 
        awk 程式的程式通常会用单引号括起来,因为 awk 程式里面经常会用到 $ 
        字元,如果使用双引号的话,shell 本身会解释这个字元。所以啦,在这种 
        特殊情形下,我们想要 shell 解释 $USER 当中的 $ 字元时,就必需先用 
        单引号把前半段的句子暂时括起来,然後用双引号把 $USER 括起来,再用 
        单引号把随後的句子括起来。请注意,双引号在某些状况下可以略去不写, 
        也就是说,可以写成: 
 
        who | awk '/^'$USER'/ { print $2 }'                             (2) 
 
        然而,如果 shell 变数的内容含有特殊字元或空白字元时,就不适用了。 
 
        第二种把变数的设定传进 awk 的方式是利用 awk 当中一个无文件说明的 
        功能,它允许您从命令列透过「假造的档案名称」来设定变数,例如: 
 
        who | awk '$1 == user { print $2 }' user="$USER" -              (3) 
 
        由於命令行中的变数设定是在 awk 真正处理到的时候才会生效,因此您可 
        以利用这种技巧让 awk 在遇到不同档名的时候做不同的动作。例如: 
 
        awk '{ program that depends on s }' s=1 file1 s=0 file2         (4) 
 
        请注意有些 awk 的版本会在 BEGIN 区块执行之前,就让真实档案名称之 
        前所叙述的变数设定生效,但有些不会,所以您不可以依赖其中一种。 
 
        再进一步提醒,当您指定变数的设定时,如果没有指定真实的档案名称, 
        awk 将不会自动从标准输入读取,所以您要在命令之後加上一个 - 参数, 
        就跟 (3) 的指令行内容一样。 
 
        第三种做法是使用较新版的awk (nawk),您可以在 nawk 当中直接取用环 
        境变数。例如: 
 
        nawk 'END { print "Your path variable is " ENVIRON["PATH"] }' 
/dev/null 
 
------------------------------ 
 
Subject: How do I get rid of zombie processes that persevere? 
From: jik@rtfm.MIT.Edu (Jonathan I. Kamens) 
From: casper@fwi.uva.nl (Casper Dik) 
Date: Thu, 09 Sep 93 16:39:58 +0200 
 
3.13)   要怎样才能避免在记忆体中留下zombie processes? 
 
        很不幸地,对於死掉的子 process 应有的行为特性并没有办法做一般化,因 
        为这些特定/特定的机制会随著 Unix 的种类不同而有所差异。 
 
        首先,在各种 Unix 上面您都必需使用 wait() 来处理子 process。也就是 
        说,我还没看过有一种 Unix 会自动把结束的子 process 干掉,即使您不告 
        诉它该怎麽做。 
 
        其次,在某些从 SysV 衍生的系统当中,如果您执行了 signal(SIGCHLD, 
        SIG_IGN)",(嗯,事实上应该是SIGCLD 而非SIGCHLD,但大多数新出 
        炉的 SysV 系统都会在表头档当中加上 #define SIGCHLD SIGCLD),那 
        麽子 processes 都会自动被清除得乾乾净净,您什麽事都不用做。看看这个 
        方式是否可行的最佳做法就是自己在机器上试试看。如果您想试著写出具可 
        携性的程式码,那麽依赖这种特殊处理方式可能不是好主意。不幸的是,在 
        POSIX 并不允许您这样做;把 SIGCHLD 的行为特性设成 SIG_IGN 在 
        POSIX 当中并没有定义,所以如果您要让您的程式合乎 POSIX 的要求 
        时,您就不可以这样做。 
 
        那麽怎样才算是 POSIX 的做法呢?如同前面所述,您必需设定一个 signal 
        的处理函数,然後让它去 wait。在 POSIX 当中 signal 处理函数是经由 
        sigaction 设定,由於您只对终止的子 process 感兴趣,而不是那些 stopped 
        的子 process,所以可以在 sa_flags 当中加上 SA_NOCLDSTOP。如果要 
        wait 子 process 而本身不因此被挡 (block),可以使用 waitpid()。第一 
        个参数必需是 -1 (代表 wait 任何 pid),第三个参数必需是 WNOHANG,这是 
        最具可携性的做法,也是可能会成为未来最具可携性的写法。 
 
        如果您的系统不支援 POSIX,那就有很多做法了。最简单的方式就是先试 
        试signal(SIGCHLD, SIG_IGN) 是否可行,可以的话那就好了。如果 
        SIG_IGN 无法用来强制自动收拾残骸,那麽您就要自己写一个 signal 处理 
        函数来收拾残骸了。要写出一个适用於每一种 Unix 的 singal 处理函数来 
        做这件事是不容易的事,因为有下列不一致的地方: 
 
        在一些 Unix 中,一个或一个以上的子 process 死时,会呼叫 SIGCHLD 的 
        signal 处理函数。也就是说,如果你的 signal 处理函数只有一个 wait() 
        时,并不会把所有的子 process 都收拾乾净。不过还好,我相信这类的 
        Unix 都会有 wait3() 或 waitpid(),这两者都有可在 option 参数中使用 
        WNOHNAG 可用来检查是否有子 process 尚待收拾。所以在一个有 
        wait3()/waitpid() 的系统中,你可以一再重复使用 wait3()/waitpid() 
        以确定所有的子 process 都已收拾乾净。最好是用 waitpid() 因为 
        它在 POSIX 标准中。 
 
        在一些 SysV-derived 的系统中,再 SIGCHLD 的 signal 处理函数结束後, 
        若还有子 process 等待清除,还是会产生 SIGCHLD signal。 因此,在大部 
        份的 SysV 系统中,在 signal 处理函数里可以假设要处理的 signal 只有一 
        个, 反正若还有等待处理者,signal 会一再的产生,系统也会一再的呼叫 
        signal 处理函数。 
 
        在一些比较旧的系统中,无法防止 signal 处理函数在被呼叫後 signal 处理 
        函数被自动设为 SIG_DFL。在这类的系统中,要在你的signal 处理函数中 
        的最後加入 "signal(SIGCHLD,catcher_func)"("catcher_func" 是处理函数的 
        名字)。 
 
        还好新一点的系统中在 signal 处理函数被呼叫後并不会从设为 
        SIG_DFL。所以在没有 wait3()/waitpid() 而有 SIGCLD 的系统中,要处理 
        此问题,当在处理函数呼叫了一次 wait() 以後就得用 signal() 从新设定 
        signal 处理函数。为了向前相容之故, System V 的 signal() 维与以前相同 
        的作法。所以,应该要用 sigaction() 或 sigset() 来安装 signal 处理函数。 
 
        总结来说,在有 waitpid()(POSIX) 或wait3() 的系统了,你应该要用它们而 
        你的 signal 处理函数里也要 loop,在没有 waitpid() 与 wait3() 的系统 
        中,则每次呼叫 signal 处理函数都要有 wait()。 
 
        最後,有一个 portable但是效率较差的做法。那就是 parent process 在fork() 
 
        後要 wait()  它的child process 的结束。而此子 process 马上又 fork(),这 
        时你就有一 child process与一 grandchild process。将此 child process马上 
        结束,则 parent process 也会跟著结束。让 grandchild 来做原先要 child 做 
         的事情。此时 grandchild 的因为其 parent (child) 已死,parent 就变成了 
        init,init 就会帮你处理wait() 相关事宜。这个方法多用了一个 fork() 所以 
        比较没有效率,但这绝对是个 portable 的方法。 
 
------------------------------ 
 
Subject: How do I get lines from a pipe ... instead of only in larger blocks? 
From: jik@rtfm.MIT.Edu (Jonathan I. Kamens) 
Date: Sun, 16 Feb 92 20:59:28 -0500 
 
3.14) 当我要从 pipe 读一行输入时,要如何才能让这行资料像是直接从键盘输 
      入而非从一个大 block buffer 来的? 
 
        stdio 这个 library 会自己判断它是否是在 tty 下执行,并藉以决定采用何种 
        buffering 方式。如果它认为是在 tty 模式下,那麽它就会以一次一行来做 
        buffering;反之,则用一个较大的 buffer 而非一行一行的做。 
 
        如果你能拿到你所想要取消 buffering 的软体的原始程式,那麽你就可以用 
        setbuf() 或 setvbuf() 来改变 buffering 的方式。 
 
        如果你无法拿到原始程式,那麽你就只能试著去说服这个正在 pty 下执行 
        的程式,让它以为它是在 tty 下执行。例如,用先前在问题 3.9 中所提过 
        的 pty 程式。 
 
------------------------------ 
 
Subject: How do I get the date into a filename? 
From: melodie neal <melodie@comtech.ct.oz.au> 
Date: Fri, 7 Oct 1994 09:27:33 -0400 
 
3.15) 我要如何在档案名字中加入日期? 
 
        这其实并不难,但是看起来有一点神秘。我们就从 date 这个命令开始说 
        起:date 能接收一个格式字串,并根据此字串来决定它的输出是什麽。这 
        个格式字串必需用单引号括起来,以避免 shell 自己去解释这个字串。 
        试试以下这个命令: 
 
                date '+%d%m%y' 
 
        你应该会得到类似 130994 的结果。如果你还想把它加上标点符号,你只要 
        直接把这些字元加入格式字串中就行了(不要用斜线 '/'): 
 
                date '+%d.%m.%y' 
 
        在这个格式字串中还有很多 token 可供使用:建议读 date 的 man page 就 
        可以找到有关的说明。 
 
        现在,就可以把上述 date 命令的结果放到档名里去了。譬如,要造一个名 
        为 report.130994(反正就是代表今天日期的意思)的档案: 
 
                FILENAME=report.`date '+%d%m%y'` 
 
        注意一点,这里一共用了两组引号:里面的引号是为了避免格式字串被 shell 
        用去做其他解释;外面那组引号则是用来告诉 shell 被包起来的部份要去执 
        行,并把执行的输出代换到这整行命令中(command substitution)。 
 
------------------------------ 
 
Subject: Why do some scripts start with #! ... ? 
From: chip@@chinacat.unicom.com (Chip Rosenthal) 
Date: Tue, 14 Jul 1992 21:31:54 GMT 
 
3.16) 为什麽有一些 script 是用 #! ... 做为档案的开端 
 
        我想有些人可能会把两种以 '#' 这个字元开始的机制搞混了。这两种机制 
        用来解决不同情况下的同一问题。 
 
        背景知识:当 UNIX 的 kernel 开始要执行一只程式(使用 exec() 中的任一 
        个),会先偷看档案开头的 16 个 bit。这 16 个 bit 称为 'magic number'。 
        Magic number 有几个重要的功能。首先,kernel 再执行一个档案之前会先 
        看看它的 magic number,如果 kernel 不认得那个 magic number,就不会 
        去执行之并且会 return 回 ENOEXEC。 
 
        再者,随著时代进步, magic number 不只可以用来辨别是否为执行档,更 
        可以用来辨别该如何执行此档。举例来说,假如你在 SCO XENIX/386 上 
        compile 了一个程式,然後将这个程式拿到 SysV/386 UNIX 上去执行, 
        SysV/386 UNIX 的 kernel 会认得它的 magic number,说「啊哈!这是一个 
        x.out 格式的可执行档」,然後会设定自己去使用 XENIX 相容的 system 
        call。 
 
        既然 kernel 只能执行 binary executable image。你或许会问,那 script 
        又是怎麽执行的呢?当我在 shell prompt 下打 'my.script' 并不会得到 
        ENOEXEC啊!这是因为 script 的执行是由 shell 做的而非由 kernel 来做 
        的。在 shell 中执行程式的部份可能是长得这个样子: 
 
                /* try to run the program */ 
                execl(program, basename(program), (char *)0); 
 
                /* the exec failed -- maybe it is a shell script? */ 
                if (errno == ENOEXEC) 
                    execl ("/bin/sh", "sh", "-c", program, (char *)0); 
 
                /* oh no mr bill!! */ 
                perror(program); 
                return -1; 
 
                    (This example is highly simplified.  There is a lot 
                    more involved, but this illustrates the point I'm 
                    trying to make.) 
 
        在上例中,若第一个 execl() 成功的话,那在 execl() 底下的的部份就不必 
        看了,因为 execl() 执行後就不再回头了。 
 
        假如第一个 execl() 失败的话,那就表示这不是一个 binary executable, 
        shell 会试著把它当成 shell script 来执行。 
 
        在柏克莱的人们对於如何扩充 kernel 执行程式的能力想到的了一个非常棒 
        的法子。他们让 kernel 认得 '#!' 这个 magic number。(两个 8-bit 的字元 
        构成一个 16 bits 的 magic numbre。)如果一个档案是以 '#!' 开始的,则 
        kernel 会把第一行其它的内容当成要用来执行此档案内容的命令。有了这个 
        处理後,我们就有了如下的作法: 
 
                #! /bin/sh 
 
                #! /bin/csh 
 
                #! /bin/awk -F: 
 
        这种处理只有存在於 Berkeley 系统,USG 系统的是直到 SVR4 才把这种 
        作法加入 kernel 中。所以若你用 System V 在 R4 之前的版本,除非厂商 
        有做修改,否则就只能执行 binary executable image。 
 
        讲到这里让我们先把时光倒流,回到 USG based UNIX 还没有 csh 的年 
        代。因为,有愈来愈多的的使用者说:「作为一个 interactive user 
        interface 而言 sh 真的是一个失败之作,我要用 csh 啦!」所以呢,有许 
        多的厂商就把 csh 与其 magic number 加入他们的产品中。 
 
        这种做法造成了一个问题。这麽说吧,你把 login shell 换成了 /bin/csh。 
        更进一步的假设你是个坚持要写 csh script 的白痴笨蛋。你当然会希望只要 
        打 'my.script' 就能执行一个 csh script。而且希望不必经过 /bin/sh 而是 
        经由如下的作法执行: 
 
                execl ("/bin/csh", "csh", "-c", "my.script", (char *)0); 
 
        但是如过这麽做的话会有大麻烦的。想想原先系统里还有不少 sh 的 shell 
        sript,一旦用 csh 来执行这些 sript,那是必死无疑。所以得有一个可以有 
        时候用 csh,有时候用 sh 来执行 script 的方法。 
 
        当时想到的作法是让 csh 去检查要执行的 script 的第一个字元。假若这个 
        字元是 '#' 那 csh 会用 /bin/csh 去执行这个 script,否则就用 /bin/sh 
        去执行这个 script。上述的作法可能长得像这个样子: 
 
                /* try to run the program */ 
                execl(program, basename(program), (char *)0); 
 
                /* the exec failed -- maybe it is a shell script? */ 
                if (errno == ENOEXEC && (fp = fopen(program, "r")) != NULL) { 
                    i = getc(fp); 
                    (void) fclose(fp); 
                    if (i == '#') 
                        execl ("/bin/csh", "csh", "-c", program, (char *)0); 
                    else 
                        execl ("/bin/sh", "sh", "-c", program, (char *)0); 
                } 
 
                /* oh no mr bill!! */ 
                perror(program); 
                return -1; 
 
        有两点要注意的是。第一,这是由 'csh' 动手脚,没有动到 kernel 也没有 
        改到别的 shell。所以如果要 execl()一个 script,不管它是不是以 '#' 开 
        始,你都会得到的 return value 都会是ENOEXEC。假如你从 csh 以外的 
        shell (如 /bin/sh ) 的 script,执行这个 script 的还是 sh 而非 csh。 
 
        第二,这个做法只判断第一个字元是否为 '#',所以只要不是 '#' 其他不管 
        是啥东西都送给 sh 去处理。底下是几个例子: 
 
                : 
 
                : /bin/sh 
 
                                <--- a blank line 
 
                : /usr/games/rogue 
 
                echo "Gee...I wonder what shell I am running under???" 
 
        同里只要是以 '#' 为开端的 script,如下: 
 
                # 
 
                # /bin/csh 
 
                #! /bin/csh 
 
                #! /bin/sh 
 
                # Gee...I wonder what shell I am running under??? 
 
        如果是在 csh 里执行之,则会用 /bin/csh 来执行。 
 
        (注解:如果你用的 ksh,那把上述的 'sh' 换成 'ksh' 即可。因为, 
        理论上来说 Korn shell 应该是与 Bourne shell 相容的。如果你用的 
        是 zsh, bash 等其它的 shell,那自己看著办吧。) 
 
        如果你的 kernel 有支援 '#!' 那 '#' 显然就是多馀的做法。而且,还会造成 
        混淆,譬如当你用 '#! /bin/sh' 应该要怎麽办呢? 
 
        还好,'#!' 日渐盛行。System V Release 4 已经将其加入。而有一些 System 
        V Release 3.2 的厂商,也正把 V.4 中这类比较显而易见的特性加入产中, 
        并且尝试著说服你说这些特性就够了,你并不需要其他如一个真正 streams 
        或是动态调节 kernel 参数这类的东西。 
 
        XENIX并不支援 '#!'。不过 XENIX 的 /bin/csh 有支援 '#' 的作法。 如 
        果 XENIX 支援 '#!' 当然不错,可是我对此不抱任何希望。 
------------------------------------- 

BBS水木清华站∶精华区