5.5 overwrite:改写文件
sort排序命令有一个选顶-o,表示覆盖文件:
如果filel和file2是同一个文件,重定向符号>在排序之前就把输入文件截断。然而,加上-o选项的命令将能正常工作,因为在输出文件建立前,sort先将输入排序并存放在一个临时文件中。
很多其他命令也可用-o选择。例如,sed可以编辑文件如下:
要对所有这样的命令都加上选项-o显然是不现实的。另外,这种做法也不可取:最好是将功能集中起来处理,就像shell使用运算符>那样。我们给出一个程序overwrite完成这项工作。第一种设计如下:
基本的实现思路直截了当—保存全部输入直到文件尾,然后把数据复制到参数文件:
这里使用cp命令,而不使用mv命令,从而当输出文件已存在时,访问权限和所属关系保持不变。
尽管这个版本有个诱人的优点:简单,但是它有一个致命的缺点:如果用户在cp过程中按下Delete键,原来的输入文件将被破坏。必须防止对输入文件的覆盖被任何中断终止:
如果到达原文件尾前发生了一个Delete中断,临时文件将被删除而原文件保留。备份完成后信号将被忽略,因此前一个cp不会被中断—一旦cp开始,overwrite将全力进行原文件的修改工作。
还有一个小问题。考虑下面的程序:
如果为overwrite提供输入的程序出错,它的输出将会被清零,overwrite也会尽职尽责地破坏参数文件。
这一问题有几种解决办法。overwrite可以在重写文件前请求确认,但是增加交互功能会大大削弱overwrite的优越性。overwrite可以检查其输入是否为空(利用test -z),但这种编程风格很糟糕,而且也不正确:一些输出数据会在探测到错误之前生成。
最好的解决方案是由overwrite运行并控制数据生成程序,以便检查它的退出状态。尽管这与传统和直觉相左—在一条管道中,overwrite通常放在最后,但是为了正确工作,这里它必须最先运行。所幸的是,overwrite不产生标准输出,所以这种处理没有丧失一般性。它的语法也并不陌生:time,nice和nohup都是以其他命令作为参数的命令。
下面是一个安全的版本:
shell的内部命令shift将整个参数表向左移动一个位置:$2成为$1,$3成为$2,依次类推。“$@”提供所有参数(移动后的),与$*类似,但不对参数作解释。在5.7节还要讨论这一问题。
注意,为运行用户命令而对PATH进行了重置;否则,overwrite将不能访问那些不在/bin和/usr/bin目录下的命令。
现在overwrite可以工作了(尽管有些笨拙):
(回忆一下,当for语句循环表为空时,默认等于$*。)使用@代替/表示定界符,因为@与输入字符串冲突的可能性更小。
replace把PATH设为/bin:/usr/bin,而不包括$HOME/bin。这表明overwrite必须在/usr/bin目录运行下才能使replace工作。做这一假定是为了处理简化;如果不能在/usr/bin里安装overwrite,就必须在replace里将$HOME/bin放入PATH,或者明确地写出overwrite的路径名。以后,我们假定所有命令都被有意地放在/usr/bin目录下。
练习5-17 为什么overwrite在trap中不用信号码0,从而在退出时删除文件?提示:试着在运行下列程序时按Delete键:
练习5-18 为replace命令加上选择项-v,用于在/dev/tty上打印所有变动的行。提示:s/$left/$right/g$vflag。
练习5-19 修改replace,使它可以工作于拥有任意字符的替换串。
练习5-20replace能否把程序中出现的所有变量i改为index?应该怎样修改?
练习5-21 把replace程序放在/us/bin目录是否方便和完全满足使用需要?需要时简单地键入正确的sed命令是否更加可取?为什么?
练习5-22(难)下一命令不能正常工作。解释原因并修改。
提示:参阅sh(1)中的eval。修改后程序对命令中元字符的解释产生了什么影响?