欢迎访客 ( 登陆 | 注册 )

论坛索引 | 最新主题 | 热门主题 | 搜索论坛 | 成员列表 | 在线帮助

 
推荐 : Unix FAQ !
« 上一篇主题 | 下一篇主题 » 跟踪主题 | 邮寄主题 | 打印主题
  闪亮晨星 离线
  1. 推荐 : Unix FAQ !
HP : 241 / 1208
MP : 1463 / 20807
EXP : 32%
Administrator


成员等级: 49
发表总数: 4389
金币总数: 608
所属组别: 管理员
注册日期: 2003/01/10


这篇文章以下问题的解答:

1.1) 谁帮助你将做出这个 faq 的?
1.2) 当有人提及'rn(1)' 或 'ctime(3)' 的时候,刮弧里的数目意谓
什么呢?
1.3) 一些奇怪的 unix 指令名字的由来?
1.4) "comp.unix.questions" 和 "info-unix" mailing list 之间的
gateway 是如何运作的?
1.5) 请建议一些有用的 Unix, C 相关书籍.
1.6) 以前在这 FAQ 中的发音表到那去了?


1.1) 谁帮助你将做出这个 faq 的?

这个文件是最初的几个 FAQ 之一, 编纂于 1989 年七月。整个编辑工作几
乎都是 Steve Hayman 做的。 我只是接管这份文件的维护。

我们非常感谢 Usenet 读者提出的问题,反应,更正和提议。

特别感谢 Maarten Litmaath, Guy Harris 和 Jonathan Kamens,他们都
贡献良多。

这个文件中的第 5 部份(shells)几乎完全是 Matthew Wicks
<wicks@dcdmjw.fnal.gov> 写的。

这个文件中的第 6 部份(各种 Unix)几乎完全是 Pierre (P.) Lewis
<lew@bnr.ca> 写的

我尽可能的把每个问题的作者与最后更新的日期放在问题的开端。很不幸
地,因为我最近才开始这样做, 许多的资讯已遗失。我也疏于保存为问题
补充最新资料者的名单。对那些有贡献而没有得到应有的荣誉者, 在此致
歉。

我将此文件转为 *roff 格式(ms 与 mm 两种 macro 都有)。Andrew
Cromarty把它转换成 Texinfo 格式。这些格式化的版本可由 anonymous
ftp 于 ftp.wg.omron.co.jp 的 pub/unix-faq/docs 取得.


1.2) 当有人提及'rn(1)' 或 'ctime(3)' 的时候,刮弧里的数目意谓什么呢?

看起来像是 function call, 不过, 不是。 这些数目字是指 Unix 手册中
文件所在的章节。当你打 "man 3 ctime" 时, 表示是要查阅在第 3 节中
ctime 的内容。

传统 Unix 手册的分节法:

1 User-level commands
2 System calls
3 Library functions
4 Devices and device drivers
5 File formats
6 Games
7 Various miscellaneous stuff - macro packages etc.
8 System maintenance and operation commands

一些 Unix 版本使用非数字的章节名字。举例来说, Xenix 的指令用"C"
而功能用"S"。 一些比较新的 Unix 版本得用 "man -s# title" 而非
"man # title"。

每一节都有一个简介, 以 # 代表节数, "man # intro" 就可以读第 # 节
的简介。

有时为了区别指令和相同名字的常式或系统呼叫数字是必需的。 举例来说
,你的系统可能有"time(1)", 有关 'time' 这个测量所费时间指令的说明
,也有 "time(3)", 关于 'time' 这个用来决定目前时间的副常式的说明。

你可以用 "man 1 time" 或 "man 3 time" 来选择要看哪一 "time" 的说
明。

或许你系统会有其它的章节或在细分的次章节(subsection),像 Ultrix 就
有 3m, 3n, 3x 与 3yp。


1.3) 一些奇怪的 unix 指令名字的由来

awk = "Aho Weinberger and Kernighan"

这个语言以作者 Al Aho, Peter Weinberger 和 Brian Kernighan 的
姓来命名。

grep = "Global Regular Expression Print"

grep 来自 ed 的列印所有符合某 pattern 指令

g/re/p

"re" 代表 regular expression

fgrep = "Fixed GREP".

fgrep □找固定的字串。"f" 不是代表 "fast" - 事实上, "fgrep
foobar *.c" 通常比 "egrep foobar *.c" 来得慢(有点意外吧, 不信
的话, 自己试试喽)。


尽管如此,Fgrep 仍然有可取之处,在档案中搜寻字串的时候,Fgrep
能处理的字串数目较 egrep 多。

egrep = "Extended GREP"

egrep 用比 grep 更 fancy 的 regular rexpression。许多人始终只
用 egrep,因为它用的 algorithm 比 grep 或 fgrep 用的高级,而
且通常是三个程式中最快的。

cat = "CATenate"

catenate 是一个艰深难懂的单字,意思是"把它连成一串", 这就是
"cat" 这个指令对一个或多个档案所做的处理。

请不要跟 C/A/T 混淆了,C/A/T 是指电脑辅助排版系统(Computer
Aided Typesetter)。

gecos = "General Electric Comprehensive Operating Supervisor"

不过,当通用电器 (GE) 的大型系统部门卖给 Honeywell 的时候,
Honeywell 就把 GECOS 的 E 拿掉了。

目前 Unix 的密码档里面仍保有 "pw_gecos" 这个栏位。这个名字是
从古早的年代沿用过来的。

Dennis Ritchie 曾经说过:

"有时候我们会把印表输出或整批工作丢到 GCOS 机器。密码档
里面的 gcos 栏位是用来隐藏 $IDENT 卡片上的资讯,这样做不
够优雅"。


nroff = "New ROFF"
troff = "Typesetter new ROFF"

这些字都是从"roff"衍生的, roff 是重写 Multics 上的 runoff 程式得来
的 (runoff 的意思就是"印出文件")。

tee = T

这是管线工人的术语,代表 T 型的管线分叉器。

bss = "Block Started by Symbol" (由符号启始的区块)

Dennis Ritchie 曾说过:

这个缩写也许有其他说法,但事实上我们采用这个缩写的本意是
"Block Started by Symbol"。它是 FAP 上的虚拟指令,FAP
(Fortran Assembly [-er?] Program) 是指 IBM 704-709-7090-7094
这种机型的组译器。这个指令可定义自己的标号,并且预留一定数目
的字组空间。还有另一个虚拟指令 BES,是 "Block Ended by
Symbol",跟 BSS 指令几乎一样,不同点在于标号是定义在预留字组
空间尾端的位址 + 1 的地方。在这些机器上,Fortran 的阵列是以反
方向储存,而且阵列的索引是从 1 算起。

这种用法是合理的,因为这跟 UNIX 上标准的程式载入器一样,程式
码当中并非真的放入这一整块预留空间,而是先用一个数目表示,在
载入时才真的把所需的预留空间定出来。

biff = "BIFF"

这个指令是用来设定当您有新邮件进来时,是否要通知您。这是柏克
莱大学校园内一只狗的名字。

我可以确定这个名称的起源,如果您有兴趣的话,Biff 是 Heidi
Stettner 养的宠物,想当年 Heidi (还有我,跟 Bill Joy) 都还是
UCB 的研究生时,早期的 BSD 版本还在发展中。Biff 受到流连于
Evans Halls 这些人的喜爱,也因为它会对前来的邮差吠叫而闻名;因
此就以 biff 当作指令的名称。
(这是卡内基美浓大学的 Eric Cooper 证实的)

rc (像是 ".cshrc" 或 "/etc/rc" 中的 rc 这两个字母) = "RunCom"

"rc" 是取自 "runcom", 来自麻省理工学院在 1965 年发展的 CTSS
系统。相关文献曾记载这一段话: '具有从档案中取出一系列命令来执
行的功能;这称为 "run commands" 又称为 "runcom",而这种档案又
称为一个 runcom (a runcom)。'

Brian Kernighan 与 Dennis Ritchie 告诉 Vicki Brown 说: "rc" 也
是Plan 9 作业系统 shell 的名字。



Perl = "Practical Extraction and Report Language"
Perl = "Pathologically Eclectic Rubbish Lister"

Perl是 Larry Wall 所发展的一种相当受欢迎的语言, Perl 在处文字,
process,与档案时非常便利,可以说是兼得 shell 与 C 之长。想知道
更多关于Perl 的讯息,请看 Usenet newsgroup comp.lang.perl。

Don Libes 的 "Life with Unix" 一书里有更多这类的珍闻轶事。


1.4) "comp.unix.questions" 和 "info-unix" mailing list 之间的
gateway 是如何运作的?

"info-unix" 与 "unix-wizards”分别是 comp.unix.questions 和
comp.unix.wizards 的 mailing-list 版。Mailing list 与 newsgroup
的内容应该是相同的

要加入或退出任一个 mailing list, 请送 email 给info-unix-quest@brl.mil
或 unix-wizards-request@brl.mil。切记要加入或退出 mailing list 时是送给
"*-request@brl.mil" 。也请您耐心等候因为不会马上有回应。

底下就是 mailing list 的维护者 Bob Reschly 所提供关于这些 mailing list
的详细细节。

==== postings to info-UNIX and UNIX-wizards lists ===

我个人不对送到 mailing list 的内容作管制,任何送到这个 mailing list 的
信件都会贴到相对应的 news group。BRL 只是单纯的转送。在此 mailing list
上的 Internet 使用者,要送文章到 newgroup 上时请送到 info-UNIX 或
UNIX-wizards, '-request' 是要的信是要送给 mailing list 的维护者看的。

在此 mailing list 上的 Internet 使用者会收到两类的讯息,一种是单独一篇
的讨论文章,另一种则是集结多篇讨论精华的文摘。从 Internet 或者
BITNET (透过 Bitnet ←→ Internet 转换程式)寄往 BRL 的讯息都会转
发一份给 mail list 当中的每一个使用者。


从 USENET 发出来的文章则以每天汇整一次的方式寄给 mailing list 当中
的所有成员。BITNET 的网路交通跟 Internet 的网路交通很像。主要的不
同点在于:对于 mailing list 内所有 BITNET 的收件人,我只要维护一个
电子邮件地址,让这个地址所在的收件程式维护所有收件人的资讯,并且
自动转寄所有文章给 mailing list 上的每一个订阅者即可。

在 USENET 上的订阅者只会读到各自独立的讯息,所有发自 Internet 的
讯息则转送至我们位于 USENET 上的机器,然后贴至合适的讨论区。很
不幸地,这些透过转换程式贴出去的文章,发件人会变成 news@brl-adm,
这是转换软体目前尚未解决的先天限制。

至于读者群方面,USENET 是一个拥有广大读者群的地方,我估计约有数
千部主机与数万名使用者参与 USETNET。BRL 所维护的主要 list 约有
250 个,大约有百分之十是本地的转送 list。我不太清楚 BITNET 方面的
转送数目,不过如果要让我猜的话,数目大约跟主要 list 一样。平均一个
list 在一个星期内要送出 150K 到 400K 的资料。


1.5) 请建议一些有用的 Unix, C 相关书籍。

Mitch Wright (mitch@cirrus.com) 维护一份 Unix 和 C 相关书籍一览
表,里面包含简介与短评。目前在他的表上有 167 本书ftp.rahul.net
(192.160.13.1) 的 "pub/mitch/YABL/yabl" 就是这份一览表。 要加入新
的内容或提供建议送 email 给 mitch@cirrus.com。

Samuel Ko (kko@sfu.ca) 维护一份 Unix 相关书籍表。这个列表只包含推
荐书,因此比较短。 这份表是分类的列表, 如果你正在寻找特定条件类
型的书, 这份表无疑是较为合适的。rtfm.mit.edu 的
"pub/usenet/news.answers/books/unix" 就是此表。要加入新的内容或提
供建议送 email 给 kko@sfu.ca。

如果你不能使用 anonymouse ftp, email 到 "ftpmail@decwrl.dec.com"
信的内容就写 "help",然后你就会收到一份教你如何以 email 取得
anonymous 的信件。


1.6) 以前在这 FAQ 中的发音表到那去了?

当 1989 本文件开使时, 它包含了一份 Carl Paukstis
<carlp@frigg.isc-br.com> 原作,由 Maarten Litmaath 所维护的一份包
罗万象的发音表。后来它功成身退了,因为发音与 "Unix questions" 这
个主题不是真的有关。

若你碰到一些不知该怎么读的字, 请参考 Eric S. Raymond
eric@snark.thyrsus.com 所维护的 Jargon。

/* 译注: ftp://ftp.csie.nctu.edu.tw/pub/GNU/jarg320.txt.gz
普通文字版 Jargon */

若你还是坚持要以前那份发音表, ftp.wg.omron.co.jp (133.210.4.4) 的
"pub/unix-faq/docs/Pronunciation-Guide" 就是啦。


user posted image 努力传递美好和希望--NBO
One ought, every day at least, to hear a little song, read a good poem, see a fine picture, and if it were possible, to speak a few reasonable words. –Goethe
发表于2004/04/17, 00:09
     Top
  ysgl 离线
2. Re:推荐 : Unix FAQ -每日一贴!
HP : 0 / 609
MP : 264 / 8827
EXP : 37%
名动江湖


成员等级: 25
发表总数: 793
金币总数: 615
所属组别: 高级成员
注册日期: 2003/12/18

呵呵,这个俺也有:)
在水木上找到的


正在学习FreeBSD。。。
发表于2004/04/17, 10:34
     Top
  闪亮晨星 离线
3. Re:推荐 : Unix FAQ -每日一贴!
HP : 241 / 1208
MP : 1463 / 20807
EXP : 32%
Administrator


成员等级: 49
发表总数: 4389
金币总数: 608
所属组别: 管理员
注册日期: 2003/01/10

继续。

Unix Faq 2 本篇文章回答以下问题:

2.1) 我要怎么删除以 '-' 字元开头为档名的档案?
2.2) 我要怎样才能把档名当中含有特殊字元的档案删除?
2.3) 我要如何列出整个目录树呢?
2.4) 要怎么设定 prompt 才会显示出目前所在的目录?
2.5) 当我在写 shell script 时,要如何从 terminal 读入字元?
2.6) 怎么样把 "*.foo" 改名为 "*.bar" 呢?怎样把档案名称改成小写呢?
2.7) 为什么我用 "rsh host command" 会有一些奇怪的讯息出现?
2.8) 我要怎要用程式或者是 shell script 中设定目前所用的 shell 的环境
2.9) 我要如何将 csh 的 stdout 与 stderr 导向到不同的胤侥兀?nbsp;
2.10) 我如何在 .cshrc 中判断是否在 login shell 中?
2.11) 在 shell 中要用怎样的 pattern 来表示除了 "." 与 ".." 外的所有档
2.12) 在 Bourne shell script 里要怎么找出最后一个参数?
2.13) 为什么有人说 $PATH 里不可以放 '.' 呢?
2.14) 在 shell script 中要怎么让终端机发出声音呢?
2.15) 为什么我不能用 "talk" 与我在某机器上的朋友交谈呢?
2.16) 为什么我月历是错的?

2.1) 我要怎么删除以 '-' 字元开头为档名的档案?

找一个方法让档案名称开头不要是 '-' 就可以了,最简单的方法就是使用

rm ./-filename

(当然,我们假设 "-filename" 位于目前的目录)。这个方法可以避免让其
他指令解释 "-"。

有许多指令,特别是呼叫 "getopt(3)" 的参数剖析常式的程式,会接受一
个 "--" 的参数,代表「这是最后一个选项」,此后出现的项目都不再是选
项,因此您的 rm 可能会接受这个 "rm -- -filename" 这种写法。有些不用
getopt() 的 rm 程式也会以同样的方式处理单一字元 "-",因此您也可以试
试 "rm - -filename"。


2.2) 我要怎样才能把档名当中含有特殊字元的档案境?nbsp;

如果这个「特殊字元」是 '/',请跳到这题的结尾;如果这个特殊的字元是
一个 ' 或者控制字元或者中文字,请继续往下读。

典型的解法是:

rm -i some*pattern*that*matches*only*the*file*you*want

这样子的话 rm 会在要删除符合你给的条件的档案前,要你确定,不
过若你的 shell 会将每个字元的第八个 bit 变成零,那以中文作档
名的档案可能就删除不掉了!



rm -ri .

这样子的?nbsp;rm 会删除目前目录下的所有档案,而在删除一个档案之
前会问你是否要删除此档。不过很不幸的,并非每一个版本的 rm 都
能这么用。再者,就算能用的话,这么做的话会把目前所在目录的所
有子目录都找进去,可能要用 "chmod a-x" 避免使子目录无法搜寻才
能避免可怕的后果。要做 "rm -r" 或含有万用字元的 "rm" 前请先深
呼吸,搞清楚自己是在做什么!



find . -type f ... -ok rm '{}' ;

"..." 是一堆用以辨识档案名称的述词,譬如在找出一有问的档案的
inode 为何后,用

find . -num 12345 -ok rm '{}' ;



find . -inum 12345 -ok mv '{}' new-file-name ;

删除或改名。 选项 "-ok" 是告诉 find 要执行指令前先要求你确认
。若你能确定所下的指令没有问题,或者怕所要处理档案有奇怪的字
元印出来会使萤幕乱七八糟,那用选项 "-exec" 就不会先要求你的确
认。

那当档案名称里含有 '/' 时要怎么办呢?

这类档案是很特别的情形,并且只会因为 kernel 的 bug 而发生(通
常是在写 NFS 的时候,没有把从远端机器来的档案名称中不合规定的
字元过滤掉)。我们第一件要做的事情就是,试著去了解为什么这个
问题会如此奇怪。

UNIX 的目录其实就只是单纯的档名和 inode number 的成对组合。
举例来说,目录包含了如下的资讯:

filename inode

file1 12345
file2.c 12349
file3 12347

理论上挥?nbsp;'/' 和 '' 两个字元不能用在档案名称中,
因为它们有以下的特殊用途:

'/' :用来分隔目录名称及档案名称。
'' :用来当档名的终结字元。

非常、极端、很不幸的,某些厂商做出来的 NFS 在回应远端机器的要
求时,会很白痴地造出含有斜线(/)的档名。例如,当某人在 Mac
或其他非 Unix 机器透过 NFS 造一个以日期为名称的档案到你的
Unix 中。那么,你的 Unix 目录看起来可能就会像这个样子:

filename inode

91/02/07 12357

我们前面所提过的 'find' 或 'rm' 都无法删除这个档案,因为这些或
其他的 Unix 程式都会强制把 '/' 当作前述的分隔字元解释。

其实,任何一般的程式都会试著做 unlink("91/02/07"),而这对
kernel 来说,它的意义是 "unlink 目录 91 下的子目录 02 中的档
案 07",但是,我们并没有这样的档案,我们有的是一个名叫
"91/02/07" 的档案在目前的目录中。这是个极细微但极重要的区别。

这时该怎么办呢?首先回到产生这种乱七八糟档名的 Mac,试试
看 NFS daemon 要不要让你改成不含 '/' 的档名。如果不行,那就得
找你的系统管理者帮忙了。请他试试以下几种方法之一:

1. 用 "ls -i" 找出档案的 inode number,umount 掉这个
file system 然后以 "clri" 将这个 inode 清除,然后
祈求“fsck" 的成功。这个作法会删除这乱七八糟档名的
档案。

2. 若还想保存这个档案的资料,试试以下的做法:

-在那乱七八糟档名的档案所在之目录的亲目录底下建一
个子目录,将旧的目录下能搬动的档案都搬到新的目录
里。
-以 "ls -id" 取得旧目录的 inode number
-unmount 掉这个 file system, 用 "clri" 清掉那个
目录的 inode
-"fsck" 那个 file system
-从新 mount 上那个 file system
-将新的目录改名为旧的目录名
-从 lost+found 下找回那个档案,改个好名字,放回原
来的目录。

3.若你有一个叫做 "fsdb" 的程式,那你可以试试看喽!


2.3) 我要如何列出整个目录树呢?

底下有几种做法自己挑一个吧:

ls -R (not all versions of "ls" have -R)
find . -print (should work everywhere)
du -a . (shows you both the name and size)

若你要找的是特定的档案,例如说是档名结尾为 ".c" 者,可用

find . -name '*.c' -print

"find" 是一个很强很好用的程式。值得一学。


2.4) 要怎么设定 prompt 才会显示出目前所在的目录?

这得视你的 shell 而定。有些 shell 很容易,有些 shell 很难,有些根
本办不到。

C Shell (csh):

将以下的东西加入你的 .cshrc 里。

alias setprompt 'set prompt="${cwd}% "'
setprompt # to set the initial prompt
alias cd 'chdir !* && setprompt'

假如你有用 pushd 与 popd, 把底下的东西也加进去。

alias pushd 'pushd !* && setprompt'
alias popd 'popd !* && setprompt'

若你的 C shell 没有 $cwd 这个变数,那就得用 `pwd` 代替之。

若你想要的只是 prompt 里有目前所在目录的最后一个成分
("mail%" 而?nbsp;"/usr/spool/mail%") 则用

alias setprompt 'set prompt="$cwd:t% "'

有些旧版的 csh 将 && 和 || 的意义弄反了。你可以试试看:

false && echo bug

若结果是印出 "bug",那就把 && 和 || 对调,或找一个没有这种
bug 的 csh 来用。

Bourn Shell (sh):

如果你有较新版的 Bourn Shell(SVR2 或更新的版本),那么你就可
以用一个 shell function 来造你自己的命令,譬如 "xcd":

xcd() { cd $* ; PS1="`pwd` $ ";}

如果你的 Bourn Shell 是比较旧的版本,也是可以做到,但是方法比
较复杂。这里提供一个方法。把以下的内容加入你的 .profile:

LOGIN_SHELL=$$ export LOGIN_SHELL
CMDFILE=/tmp/cd.$$ export CMDFILE
# 16 is SIGURG, pick a signal that's not likely to be
used
PROMPTSIG=16 export PROMPTSIG
trap '. $CMDFILE' $PROMPTSIG

然后把以下的部份写成一个可执行的 script(不需要缩排),名字就
叫做 "xcd",放在你的 PATH 中

: xcd directory - change directory and set prompt
: by signalling the login shell to read a command file

cat >${CMDFILE?"not set"} <<EOF
cd $1
PS1="`pwd`$"
EOF
kill -${PROMPTSIG?"not set"} ${LOGIN_SHELL?"not set"}

那么,现在就可已用 "xcd /some/dir" 来改变工作目录了。

Korn Shell (ksh):

把下面这行加入你的 .profile 中:

PS1='$PWD $ '

如果你只想显示最后一个部分,那么就用

PS1='${PWD##*/} $ '

T C shell (tcsh)

Tcsh 是常用的 csh 加强版,增加了一些内建变数(及许多其他的功
能):

%~ the current directory, using ~ for $HOME
%/ the full pathname of the current directory
%c or %. the trailing component of the current directory

所以你可以直接用

set prompt='%~'

BASH (FSF's "Bourne Again Shell")

$PS1 中的 w 表示工作目录的完整路径(以 ~ 表示 $HOME);W 则
是表示工作目录的最后一个部份。所以,只要把前面所提有关 sh 和
ksh 的方法做以下的修改

PS1='w $ '

PS1='W $ '


2.5) 当我在写 shell script 时,要如何从 terminal 读入字元?

在 sh 中,你可以用 read。通常是使用在回圈,如下例:

while read line
do
...
done

在 csh 中,则用 $<:

while ( 1 )
set line = "$<"
if ( "$line" == "" ) break
...
end

很可惜的,csh 并没有方法判断空白行和档案结尾(end-of-file)的不同。

如果你要用 sh 从 terminal 读一个字元,那么你可以试试

echo -n "Enter a character: "
stty cbreak # or stty raw
readchar=`dd if=/dev/tty bs=1 count=1 2>/dev/null`
stty -cbreak
echo "Thank you for typing a $readchar ."


2.6) 怎么样把 "*.foo" 改名为 "*.bar" 呢?怎样把档案名称改成小写呢?

为什么 "mv *.foo *.bar" 不对呢? 想想 shell 是怎样把万用字元展开的
吧。 在 mv 读取参数前 "*.foo" 与 "*.bar" 就已经展开了。"mv *.foo
*.bar" 在各种不同的 shell 会有不同的结果。 Csh 的话会印出 "No
match",因为找不到 "*.bar"。 Sh 则是会执行 "mv a.foo b.foo c.foo
*.bar",也就是说如果只有在有一名为 "*.bar" 的子目录存在时 mv 才会
认为执行成功,不过就算成功也不是你所预期的结果。

正确的做法是用你所用的 shell 提供的回圈来做。若你的系统中有
"basename" 这个指令:

C Shell:
foreach f ( *.foo )
set base=`basename $f .foo`
mv $f $base.bar
end

Bourne Shell:
for f in *.foo; do
base=`basename $f .foo`
mv $f $base.bar
done

有些 shell 会提供就自己的变数替代功能,那就可以不用 "basename",
而用更简单的回圈了:

C Shell:

foreach f ( *.foo )
mv $f $f:r.bar
end

Korn Shell:

for f in *.foo; do
mv $f ${f%foo}bar
done

如果没有 "basename" 可用或是胍鱿癜?nbsp;foo.* 改名为 bar.* 之类的
事,那么可以用其他的方法如 "sed" 把原来的档案名称做分隔的动作,但
是回圈的想法是一样的。你也可以利用 "sed" 把档名转换成 "mv" 的命令
,然后再把这些命令转给 "sh" 执行。如下:

ls -d *.foo | sed -e 's/.*/mv & &/' -e 's/foo$/bar/' | sh

在 1990 年 4 月时,Vladimir Lanin 把他自己写的一个叫 "mmv" 的程式
post 到 comp.sources.unix (Volumn 21, issues 87 and 88),这个程式
就能够把这件事处理得很好。 你可以这样使用:

mmv '*.foo' '=1.bar'

以上所提的 shell 中的回圈也可以用来做档案名称的大、小写转换。你可
以用改档名的方式把大写档名改为小写:

C Shell:
foreach f ( * )
mv $f `echo $f | tr '[A-Z]' '[a-z]'`
end

Bourne Shell:
for f in *; do
mv $f `echo $f | tr '[A-Z]' '[a-z]'`
done

Korn Shell:
typeset -l l
for f in *; do
l="$f"
mv $f $l
done

如果你还希望能处理含有特殊字元(空白或其他的奇怪字元)的档名,那
么你最好用:

Bourne Shell:

for f in *; do
g=`expr "xxx$f" : 'xxx(.*)' | tr '[A-Z]' '[a-z]'`
mv "$f" "$g"
done

'expr' 不管档名里有没有特殊字元都会印出档名。

有些版本的 "tr" 需要用 '[' 和 ']',有些则不必。不过,不管是不是一
定要用 '[' 与 ']' 的 "tr",加了总是没有害处。

若你的系统里有装 "perl",那你可以用 Larry Wall 写的这个多用途改档
名的程式。

#!/usr/bin/perl
#
# rename script examples from lwall:
# rename 's/.orig$//' *.orig
# rename 'y/A-Z/a-z/ unless /^Make/' *
# rename '$_ .= ".bad"' *.f
# rename 'print "$_: "; s/foo/bar/ if <stdin> =~ /^y/I' *

$op = shift;
for (@;ARGV) {
$was = $_;
eval $op;
die $@ if $@;
rename($was,$_) unless $was eq $_;
}


2.7) 为什么我用 "rsh host command" 会有一些奇怪的讯息出现?

(这里所指的 "rsh"[也可能是 "remsh" 或 "remote"] 是 remote shell,
而不是在有些系统中名为 "rsh" 的 restricted shell,这两者天差地远
了!)

若你在远端的帐号用的是 C shell,那远端的主机会帮你启动一个 C
shell 来完成你所下的那个 'command',这个 shell 会读取你在远端的
.cshrc 档。若你的 .cshrc 中有 "stty" 或 "biff" 这类不适合 non-
interactive shell 的指令。那就可能会有你所意想不到的结果,举例来

说,若你把

stty erase ^H
biff y

放在你的 .cshrc 档里面你可能会得到类似以下的奇怪讯息

% rsh some-machine date
stty: : Can't assign requested address
Where are you?
Tue Oct 1 09:24:45 EST 1991

若你使用 "at" 或 "cron",那可能也会得到类似的错误讯息。

不过没关系,解决的方法非常简单。若你的 ".cshrc" 里面有一堆只有在
interactive shell 中才有用的 operation,那就将那些 operation 都用
以下的做法包起来:

if ( $?prompt ) then
operations....
endif

因为在一个 non-interactive 中不应该也没有必要去设定 "prompt"。

还有一些只有在开启一个 login session时才有用的东西,最好搬到
".login" 中去。


2.8) 我要怎要用程式或者是 shell script 中设定目前所用的 shell 的环境
变数或改变所在的目录?

若没有做一些特殊安排是做不到的。因为,当我们造出一 child process
时,此 process 会继承其 parent 的变数与所在的目录。在这个 child
process 只能改到自己的变数与所在目录而无法影响到其 parent。

要达到此目的,parent process 要与 child process 有所沟通。当
child process 要改变变数值时得把要改变的变数及其内容写到一个讲好
的地方,让 parent process 去读取, 并改变 parent process 的变数。


另一个做法则是写一个 shell script,然后在 Bourne shell 或 Korn
shell 中用 ".",在 C shell 中用 source 去执行那个 shell script。
若此 sript 名为 "myscript" :


在 Bourne shell 或 Korn shell 中就用

. myscript

在 C shell 中则用

source myscript

若你想做的只是要改变所在目录或是设定一个环境变数,那使用 C shell
中的 alias 或是 Bourne/Korn shell 中的函数就可达成你的目的。可参
考"要怎么设定 prompt 才会显示出目前所在的目录"一节中的做法。

Thomas Michanek (xtm@telelogic.se) 提供一个更详细的解答(
ftp://ftp.wg.omron.co.jp/pub/unix-faq/doc...cript-vs-env)。


2.9) 我要如何将 csh 的 stdout 与 stderr 导向到不同的地方呢?

在 csh 中,用 ">" 将 stdout 导向,用 ">&" 则能将 stdout 与 stderr
一起导向。可是不能只单独把 stderr 转向。最好的方法是

( command >stdout_file ) >&stderr_file

以上的命令会开一个 subshell 执行 "command";而这个 subshell 的
stdout 则被转向到 stdout_file,同时这个 subshell 的 stdout 和
stderr 则都被转向到 stderr_file,但是因为 stdour 已经先被转向了,
所以 stderr 就会被转到 stderr_file 了。

如果你只是单纯的不想把 stdout 做转向,那么就用 sh 来帮你吧。

sh -c 'command 2>stderr_file'


2.10) 我如何在 .cshrc 中判断是否在 login shell 中?

当有人这么问的时候,通常要问的是

要如何判断是否是一个 interactive shell? 或是问要如何判断是否是
最上层的 shell ?

若你是要问 "是否在 login shell 中"(注:就是在做完 .cshrc 后,会
再去做 .login),那么你也许用 "ps" 和 "$$" 随便弄一弄,就能知道了
。因为通常 login shells 的名字在 "ps" 看起来都是由 '-' 做开头的。
如果你是真的对另外两个问题感兴趣,那么这里有个方法可以让你在
.cshrc 中判断。

if (! $?CSHLEVEL) then
#
# This is a "top-level" shell,
# perhaps a login shell, perhaps a shell started up by
# 'rsh machine some-command'
# This is where we should set PATH and anything else we
# want to apply to every one of our shells.
#
setenv CSHLEVEL 0
set home = ~username # just to be sure
source ~/.env # environment stuff we always want
else
#
# This shell is a child of one of our other shells so
# we don't need to set all the environment variables again.
#
set tmp = $CSHLEVEL
@ tmp++
setenv CSHLEVEL $tmp
endif

# Exit from .cshrc if not interactive, e.g. under rsh
if (! $?prompt) exit

# Here we could set the prompt or aliases that would be useful
# for interactive shells only.

source ~/.aliases


2.11) 在 shell 中要用怎样的 pattern 来表示除了 "." 与 ".." 外的所有档案?

这个问题看来容易。因为你可以用

* 表示所有不是以 "." 为开端的档案

.* 表示所有以 "." 为开端的档案,但是这样会把 "." 和 ".." 也包
含进来,但是通常你并不会想把这两个也含进来。

.[!.]* 这只有比较新的 shells 才能用;某些 shells 用 "^" 代替 "!";
而符合 POSIX 标准的 shells 一定苡?nbsp;"!",但是大部份也都能接
受 "^";所有具可移植性的应用程式都不应该在 "[" 之后紧接著没被
quota 起来的 "^")表示所有以 "." 为开头并且第二个字元不是 "."
的档案;但是这样却会漏掉 "..foo" 这类的档案。

.??* 表示所有以 "." 为开头且档名长度至少为 3 的档案,这样大概
就能避开 "." 和 ".." 了,但是却还是会漏掉 ".a" 这类的档。

所以想要正确地表示除了 "." 与 ".." 之外所有的档案,你必须要用到 3
个 patterns(如果你没有像 ".a" 这样的档案,那你可以去掉第一个
pattern):

.[!.]* .??* *

或者你也可以用一两个外部程式和 backquote substitution。这样就很完
美了:


`ls -a | sed -e '/^.$/d' -e '/^..$/d'`

(or `ls -A` in some Unix versions)

不过即使是这样做,碰上档名里面含有换行字元, IFS 字元,或是万用字
元仍然是没辄。


2.12) 在 Bourne shell script 里要怎么找出最后一个参数?


Martin Weitzel <@mikros.systemware.de:martin@mwtech.uucp>
Maarten Litmaath <maart@nat.vu.nl>
提供的答案:

若你能确定参数不会超过九个的话,可用:

eval last=${$#}

在符合 POSIX 标准的 shell 里,不管有多少个参数都可用上述的方法。

底下方法是一定有用的:

for last
do
:
done

更一般性的做法是:

for i
do
third_last=$second_last
second_last=$last
last=$i
done

若你想做的是将最后一个参数去除或是将一堆参数的顺序反过来或是取用
第 N 个参数。底下是一个不用造出 subprocess 只用 shell 组建功能的
做法:

t0= u0= rest='1 2 3 4 5 6 7 8 9' argv=

for h in '' $rest
do
for t in "$t0" $rest
do
for u in $u0 $rest
do
case $# in
0)
break 3
esac
eval argv$h$t$u=$1
argv="$argv "$argv$h$t$u"" # (1)
shift
done
u0=0
done
t0=0
done

# now restore the arguments
eval set x "$argv" # (2)
shift

这个例子可以用到 999 个参数,应该够用了吧?仔细看看(1)与(2)标示的
地方,想办法说服你自己不管参数里面有什么奇怪的字元这两行都不会出
差错。

要找第 N 个参数,用:

eval argN=$argv$N

要将参数的顺序反过来,标示为(1)的那一行必须改成

argv=""$argv$h$t$u" $argv"

自己练习最后一个参数去除的方法。

若允许呼叫外部指令这类造出 subprocess 的做法,代志就更好办了。
底下是找出 argvN:

N=1

for i
do
eval argv$N=$i
N=`expr $N + 1`
done

要将参数的顺序反过来还有一个不用造出 subprocess,有更简单的方法。
这个方法也可以用来去除最后一个参数, 不过要注意的是 argvN 不在是
原来的第 N 个参数:

argv=

for i
do
eval argv$#=$i
argv=""$argv$#" $argv"
shift
done

eval set x "$argv"
shift


2.13) 为什么有人说 $PATH 里不可以放 '.' 呢?

背景知识: 环境变数 PATH 是一串用冒号隔开的目录。当你下一个指令而
没有指定所在的目录,例如 "ls" 而非 "/bin/ls",则你的 shell 就会在
PATH 所指定的目录中去找寻指令。


你可以在 PATH 里面放入目前所在的目录 "." 。或者,在 PATH 中加入一
个空的目录,这两者是等效的

for csh users:

setenv PATH :/usr/ucb:/bin:/usr/bin
setenv PATH .:/usr/ucb:/bin:/usr/bin

for sh or ksh users

PATH=:/usr/ucb:/bin:/usr/bin export PATH
PATH=.:/usr/ucb:/bin:/usr/bin export PATH

把 "." 放在 PATH 中是很方便的,若要执行 "./a.out" 只要打 "a.out"
即可,但是这么做会有大麻烦。

当把 "." 放在 PATH 的最前面情况下。若你目前所在目录是如 "/tmp" 这
样大家都可以写的地方。如果有别的使用这放了一个名为 "ls" 的程式在
那里,而你打入 "ls" 那你可能就死得非常难看了。

把 "." 放到 PATH 的结尾是个比较好的做法:

setenv PATH /usr/ucb:/bin:/usr/bin:.

这么一来,当你在 "/tmp" 中打 "ls" 保?nbsp;shell 就会先找 "/usr/ucb",
"/bin" 与 "/usr/bin" 里的 "ls"。减少了一点危险。不过仍然不是百分之
百安全。假如你是个笨拙的打字者,有一天你把 "ls -l" 打成 "sl -l",
而又有一个聪明的使用者能猜到这种常见的打字错误,在 "/tmp" 底下放
了一只 "sl",你还是得死。所以啊,千万要小心。






user posted image 努力传递美好和希望--NBO
One ought, every day at least, to hear a little song, read a good poem, see a fine picture, and if it were possible, to speak a few reasonable words. –Goethe
发表于2004/04/22, 00:58
     Top
  闪亮晨星 离线
4. Re:推荐 : Unix FAQ -每日一贴!
HP : 241 / 1208
MP : 1463 / 20807
EXP : 32%
Administrator


成员等级: 49
发表总数: 4389
金币总数: 608
所属组别: 管理员
注册日期: 2003/01/10



Unix Faq 3

3.1)?nbsp;要如何得知一个档案建立的时间?
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) 为什么有一?nbsp;script 是用 #! ... 做为档案的开端?

3.1) 我要如何得知一个档案建立的时间?

很遗憾,因为档案建立的时间并未储存在任何地方,所以答案是无法得知。
关于一个档案你只能查到最后修改的时间("ls -l"),最后读取的时间
("ls -lu") 与 inode 改变的使间。有一些 man pages 将最后一个时间当
成是建立的时间,这种说法是错的。因为 mv、ln、chmod、chmod、chown、
chgrp 等动作都会改变这个时间。

若需更详尽的说明可参考 "stat(2)" 的 man page.


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。

在许多状况下,这个复杂的命令当中有很多部份都是非必要的。


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


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就会因此而混淆。


3.5) 我要如何改变一个 symbolic link 的 permission 呢?

这个问题没有意义,因为 symbolic link的 permission 根本不代表什么。
那个 link 所指过去的档案的 permission 才有意义。


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)。


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")。


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 不应该依赖这两种作法其中的一种。


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 "$passwordr"
expect "*password:"
send "$passwordr"
expect eof

expect 为 telnet, rlogin,debugger 和一些没有内建 command language 的
程式提供了一个近乎自动化的方法。Expect 里面的有一用以在玩 rogue
(一个 Unix 中的古老游戏)时取得较佳初始情况,然后将控制权还回给使用者
的例子。用这个 script 你就能得到『成功的一半』。

再者,有一些已经写好的程式可以帮你这类与 pseudo-tty 有关的东西,所
以你只要?nbsp;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"


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。)


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,它会安排远方的机器回应远方指令的结束状态,并传回此结束状态。


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


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 的行匦陨璩?nbsp;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 都已收拾乾净W詈檬怯?nbsp;waitpid() 因为
它在 POSIX 标准中。

在一些 SysV-derived 的系统中,再 SIGCHLD 的 signal 处理函数结束后,
若还有子 process 等待清除,还是会产生 SIGCHLD signal。 因此,在大部
份的 SysV 系统中,在 signal 处理函数里可以假设要处理的 signal 只有一
个,



user posted image 努力传递美好和希望--NBO
One ought, every day at least, to hear a little song, read a good poem, see a fine picture, and if it were possible, to speak a few reasonable words. –Goethe
发表于2004/04/22, 00:59
     Top
  闪亮晨星 离线
5. Re:推荐 : Unix FAQ -每日一贴!
HP : 241 / 1208
MP : 1463 / 20807
EXP : 32%
Administrator


成员等级: 49
发表总数: 4389
金币总数: 608
所属组别: 管理员
注册日期: 2003/01/10

Unix Faq 4


4.1) 要如何在使用者不必按 RETURN 的情况下从 terminal 读进东西?
4.2) 我要如何在未曾真的读进东西的情况下检查是否有字元等待读取?
4.3) 要怎样才能得知一个已open 档案之档名?
4.4) 一个执行中的程式如何知道自己的 pathname?
4.5) 如何用 popen() 对一个 process 做读写的动作?
4.6) 在 C 程式中要怎么用 sleep() 才能够 sleep 小于一秒?
4.7) 如何让 setuid 的 shell script 可以使用?
4.8) 我要如何得知有哪些 process 开了某一档案,或某一 process 正在使用哪
一个 fileystem(以至于我无法 unmount 这个 filesystem)?
4.9) 我要怎么知道是谁在 finger 我啊?
4.10) 能不能在一个 process 和 terminal 的连接已经断掉之后再接回来,例如
在 background 跑一个程式然后就 logout 而断掉的程式?
4.11) 有没有办法可以偷听一个 terminal,就是说将其输出复制一份至其他的
terminal。

4.1) 要如何在使用者不必按 RETURN 的情况下从 terminal 读进东西?

在 BSD 中用 cbreak 模式,在 SysV 中则用 ~ICANON 模式。

如果你懒得用 "ioctl(2)" 来设定 terminal 的参数,也可以用 stty 来做,
不过有点慢又没有效率就是了。底下的程式自己看著办吧:

#include <stdio.h>
main()
{
int c;

printf("Hit any character to continuen");
/*
* ioctl() would be better here; only lazy
* programmers do it this way:
*/
system("/bin/stty cbreak"); /* or "stty raw" */
c = getchar();
system("/bin/stty -cbreak");
printf("Thank you for typing %c.n", c);

exit(0);
}

有好几个人送给我更正确的解法。不过很抱歉我不想把它们加进去,因为这已经
超出这份文件的范围了。

通常对这个问题有兴趣的人,都是想要做一些控制萤幕显示之类的事情。如果你
也是的话,那请参考 "curses" 的相关文件。 "curses" 是一个 portable 的萤
幕控制函数库。


4.2) 我要如何在未曾真的读进东西的情况下检查是否有字元等待读取?

一些版本的 UNIX 提供了检查某个 file descriptor 目前是否有东西待读取的
方法。在 BSD 中,可以用 "select(2),也可以用 FIONREAD ioctl,检查有几
个字元等待读取,不过这只对 terminal, pipe, 与 socket 有用。在 System
V Release 3 中可以用 poll(2),不过只对 stream 有用。在 Xenix 与 Sys V
r3.2 及其以后的版本里,有一个名叫 rdchk() 的 system call 可以用来检查
对一个 file descriptor 做 read() 会不会卡住。

没有方法可以用来判断是否有字元在 FILE pointer 中待读取。(你可以直接查
看 stdio 的资料结构,看看是否 input buffer 是空的,但是这方法有时会失
效,因为你没有办法知道当你下一次要填满这个 buffer 时会发生什么事。)

有时人们问这个问题是因为想写
if (characters available from fd)
read(fd, buf, sizeof buf);
以达成 nonblocking read。这不是一种好的做法,因为可能测的时候有东西,
要读的时候,已经没有东西可读了。正确的做法应该是用 fcntl(2) 里的
F_SETFL 设定 O_NDELAY。比较旧的系统(Version 7, 4.1 BSD) 没有
O_NDELAY,那就得用 alarm(2) 来设定 read 的 timeout,以达成近似
nonblocking read 的功能。


4.3) 要怎样才能得知一个已 open 档案之档名?

这个是非常困难的。若是这个 file descriptor 是对应到 pipe 或 pty 就没
有名字了。这个 file descriptor 对应的档案也有可能已被删除。若是有
symbolic link 或 hard link,则可能有许多个名字。

如果你经过一再考虑后别无选择一定要这么做的话,可以用 find 的 -inum 与
-xdev 选项,或用 ncheck,或用自己写类似的程式来做。在这么做时要耐心的
等,因为在一个几百 megabyte 甚至几 gigabyte 的 file system中找一个档
案,一定得花不少时间。


4.4) 一个执行中的程式如何知道自己的 pathname?

若果 argv[0] 是以 "/" 开始的字,它可能就是你的程式所在地的绝对路径。
如果不是那就得照顺序检查 PATH 里的每一个目录看看里面是否有与 argv[0]
一样的程式。如果找得到的话将那个目录与程式名称兜起来可能就是你要的
pathname 了。

不过上述方法找到的并不一定是正确的,因为在程式中用到 exec() 时,
argv[0] 是可以随便给的。将 argv[0] 设为与要执行的程式名称相同只是一
种惯用法罢了!

以下的例子可能会使你更清楚些:

#include <stdio.h>
main()
{
execl("/usr/games/rogue", "vi Thesis", (char *)NULL);
}

这个被执行的程式就会认为它的名字(argv[0] 之值)是 "vi Thesis")。


4.5) 如何用 popen() 对一个 process 做读写的动作?

用 pipe 将一个 process 的输出、输入转给任意的 process 所可能会发生的
问题就是 deadlock,譬如这两个 processes 刚好同时都在等待「尚未产生」
的输入时。唯一能避免 deadlock 的方法就是在 pipe 的两端都要遵循严格的
deadlock-free 协定,但是需要这些 processes 之间的互相合作才能达成,
而对于像 popen() 这类的函数来说并不太适合。

在 'expect' 这个软体中附有一个能够让 C 程式直接引用的函式库。其中有
一个函式不管是在读或写都能达到和 popen 相同的功能。但是这个函式使
?nbsp;
用 ptys 而不是 pipes,也没有 deadlock 的问题,并且在 BSD 或 SV 中都
能使用。若想对 'expect' 有进一步的了解,可参考下一个问题的解答。


4.6) 在 C 程式中要怎么用 sleep() 才能够 sleep 小于一秒?

首先要注意的是,你只能指定 delay 的「最短」时间;实际上会 delay 多久和
系统的 scheduling 方式有关,例如系统当时有负载。如果你倒楣的话,它还可
能会 delay 蛮长的时间。

并没有一个标准函式能够在「小睡」(很短的 sleep)期间提供你计数的功能。
某些系统有提供 usleep(n) 的函式,它能够暂停执行 n 微秒(microsecond)
的时间。如果你所使用的系统没有提供 usleep() 函式,那么以下有可在 BSD,
System V 使用中的作法。

接下来的这段程式码是 Doug Gwyn 在 System V 中模拟 4BSD 并利用 4BSD
中的 select() 系统呼叫。Doung 自己都叫它为 'nap()' ;你也可以把它叫做
"usleep()";

/*
usleep -- support routine for 4.2BSD system call emulations
last edit: 29-Oct-1984 D A Gwyn
*/

extern int select();

int
usleep( usec ) /* returns 0 if ok, else -1 */
long usec; /* delay in microseconds */
{
static struct /* `timeval' */
{
long tv_sec; /* seconds */
long tv_usec; /* microsecs */
} delay; /* _select() timeout */

delay.tv_sec = usec / 1000000L;
delay.tv_usec = usec % 1000000L;

return select( 0, (long *)0, (long *)0, (long *)0, &delay );
}

On System V you might do it this way:

/*
subseconds sleeps for System V - or anything that has poll()
Don Libes, 4/1/1991

The BSD analog to this function is defined in terms of
microseconds while poll() is defined in terms of milliseconds.
For compatibility, this function provides accuracy "over the long
run" by truncating actual requests to milliseconds and
accumulating microseconds across calls with the idea that you are
probably calling it in a tight loop, and that over the long run,
the error will even out.

If you aren't calling it in a tight loop, then you almost
certainly aren't making microsecond-resolution requests anyway,
in which case you don't care about microseconds. And if you did,
you wouldn't be using UNIX anyway because random system
indigestion (i.e., scheduling) can make mincemeat out of any
timing code.

Returns 0 if successful timeout, -1 if unsuccessful.

*/

#include <poll.h>

int
usleep(usec)
unsigned int usec; /* microseconds */
{
static subtotal = 0; /* microseconds */
int msec; /* milliseconds */

/* 'foo' is only here because some versions of 5.3 have
* a bug where the first argument to poll() is checked
* for a valid memory address even if the second argument is 0.
*/
struct pollfd foo;

subtotal += usec;
/* if less then 1 msec request, do nothing but remember it */
if (subtotal < 1000) return(0);
msec = subtotal/1000;
subtotal = subtotal%1000;
return poll(&foo,(unsigned long)0,msec);
}

在 System V 或其他 非-BSD 的 Unix 中要使用这类的「小睡」程式,可以用
Jon Zeeff 的 s5nap,它曾被发表在 comp.sources.misc, volume 4 中。它
需要安装一个驱动程式,但是装好后就可以跑得很好。(它的精确度会受到
kernel 中 HZ 这个变数的影响,因为它是用到了 kernel 中的 delay() 函
式。)

现在很多较新版本的 Unix 都有提供这类的「小睡」功能了。


4.7) 如何让 setuid 的 shell script 可以使用?

[ 这个问题的回答很长,但是这是一个复杂又常问的问题。在此要谢谢 Maarten
Litmaath 所提供的答案和以下所提到的 "indir" 程式。]

先假设你所用的 UNIX 是能认得「可执行的 shell script」的变异过的 UNIX
(如 4.3BSD 或 SunOS)。这类 script 的第一行一定是如以下一般:

#!/bin/sh

这样的 script 就是所谓可执行的 script,因为它和一般可执行的binary 档
一样有 magic number 做开头。在我们所用的例子中,magic number 为
'#!',OS 会把这行接下来的部份当作这整个 script 的解译程式,其后可能还
会有一些 option 如:

#!/bin/sed -f

假设这个 script 的名字叫做 'foo',并且放在 /bin 下,那么如果你用:

foo arg1 arg2 arg3

那么 OS 实际在执行时会把它看成是:

/bin/sed -f foo arg1 arg2 arg3

有一点不同的是:如果 'foo' 被设定成 setuid,那么 OS 会把它以第一种格
式来解释;如果你硬是以第二种格式输入,那么 OS 会以 /bin/sed 的
permission 为准,而它当然不会是 setuid。


好吧,那如果我的 shell script 并不是以 '#!' 做开头,或是我的 OS 根本就
不认得它呢?

嗯,如果这个 shell(或是其他的解译程式)试著要去执行它,那么 OS 会传回
一个错误讯息,表示这个档案不是以合法的 magic number 做开头。收到这个错
误讯息后,shell 会把这个档案认定成是 shell script,并以另一种方式来执行:

/bin/sh shell_script arguments

但是我们在前面已经看到了,在这样的情形下,被设成为 setuid 的
shell_script 并不会发生作用。


那么,设成 setuid 的 shell script 到底有什么安全上的问题呢?

嗯,假设这个 script 叫做 '/etc/shell_script',它的开头是:

#!/bin/sh

现在我们来看看以下的命令会发生什么事:

$ cd /tmp
$ ln /etc/setuid_script -i
$ PATH=.
$ -i

我们可以看出来,以上的最后一个命令会被解释成:

#!/bin/sh -i

而这样的命令会让我们得到一个可以输入命令的 shell,并且会被 setuid 成
这个 script 的拥有者。
幸好,这样的安全漏洞可以很轻易地防止,只需要把第一行改成:

#!/bin/sh -

'-' 这个符号代表著它是整个 option list 的结尾:所以如果再用前述的方法
的话,'-i' 就会如本来所期望的被解释成 script 档案的名字。


然而,还有更严重的问题:

$ cd /tmp
$ ln /etc/setuid_script temp
$ nice -20 temp &
$ mv my_script temp

第三个命令会被解释成:

nice -20 /bin/sh - temp

而因为这个命令的优先权被设得很低,那么第四个命令可能就有机会抢先在
shell 开启 'temp' 之前就用 'my_script' 把 'temp' 给盖掉!有四种方法
可以修补这个安全上的漏洞:

1) 让 OS 用另一个比较安全的方式执行 setuid script。如 System V R4 和
4.4BSD 利用 /dev/fd 来把该 script 的 file descriptor 传给解译程式。

2) 透过一个前端程式来间接解译要执行的 script,以确定在真正的解译程式
启动前一切正常。例如,你可以用 comp.sources.unix 中的 'indir' 程
式,那么你的 script 开头就会像这样:

#!/bin/indir -u
#?/bin/sh /etc/setuid_script

3) 造一个 'binary wrapper':一个真正的 setuid 可执行程式,这个程式的
唯一功能就是用来执行 script 中所指定的解译程式,并以该 script 的档
名为参数传给解译程式。

4) 造一个 'setuid script server' ,并把一些要用到、检查过的 setuid
script 存放在 database 中。当成功地被呼叫后,会去执行正确的解译程
式及正确的 script。


现在我们已经能确定所解译到的 script 是正确的,那么还有其他的危险吗?

很抱歉,当然还有!在使用 shell scipt 的时候,你一定不能忘记要把 PATH
这个变数很明确地设到正确的路径去。你能够指出这是为什么吗?除此之外,
还有 IFS 这个变数如果没设好也可能会造成问题。其他的环境变数也可能会形
成安全上的问题,如 SHELL... 更重要的,你必须要确定在 script 中没有命
令会让它产生出可下命令的 shell(interactive shell escape)!还有就是,
umask 可能被设成奇怪的值等等...

除此之外,你应该要知道 setuid script 会「继承」所有它所用到的命令的
bug 及安全问题。

总而言之,你应该知道 setuid shell script 真的是件非常危险的事吧!
最好还是写 C 程式啦。


4.8) 我要如何得知有哪些 process 开了某一档案,或某一 process 正在使用哪
一个 fileystem(以至于我无法 unmount 这个 filesystem)?

你可以用 fuser(system V),fstat(BSD),ofiles(public domain) 或是 pff
(public domain)。这些程式可以告诉你哪些 processes 正在使用哪些档案。

4.3BSD 的 fstat,有一份 Dynix,SunOS 与 Ultrix 都可以用的 port。你可以
找找放 comp.sources.unix, volume 18的地方。

Pff 是 kstuff 这套软体的一部分,很多系统上都可以用。欲取得 kstuff 请参
考问题 3.10。


4.9) 我要怎么知道是谁在 finger 我啊?

一般来说,你是无法找出在远端机器 finger 你的那个人的 userid 的。你大概
只能找出在从哪台机器 finger 的。另外有一种可行的方法,如果你的系统支援
并且假设 finger daemon 不反对的话,那么可以把你的 .plan 档用 "named
pipe" 而不用一般的文字档。(用 'mknod' 来造)

接下来,你执行一个程式去写(open for writing)你的 .plan 档;但是由于
你的 .plan 是一个 "named pipe",所以这个开档的动作要一直等到有其他的
process 去读(open for reading)你的 .plan 档时才会成功。现在你就可以
任意地把你所想让人 finger 到的 .plan 内容写入这个 pipe。在
comp.sources.misc, volumn 41 中有个 "planner" 的程式可以做这件事。

当然,如果你的系统不支援 "named pipe",或是你所用的 finger 程式只接受
纯文字的 .plan 档,那么以上的方法就行不通了。

你的程式也可以藉由查看 "netstat" 的输出,来找出这次的 finger 是从那里
连过来的,但是这并无法看出远端执行 finger 的人是谁。

想要知道远端的人是谁,必须要远端的机器有跑支援如 RFC 931 的识别程式才