標  題: UNIX FAQ 中文版 Part 3


這七篇文章包含一些在 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 2>&1  也貢獻了一份
                         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 
功能,那麼s
hell
        只要把 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/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 
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 支援 '#!' 當然不錯,可是我對此不抱任何希望。
-------------------------------------