通过脚本实现操作的自动化

本文介绍如何使用Shell脚本自动化日常任务,包括监视、存档和更新等操作。通过示例展示了如何创建简单的Shell脚本来同步目录内容,以及更复杂的脚本如何处理动态参数和执行系统命令。
了解Shell脚本如何实现所有个人或系统任务自动化。脚本可以执行监视、存档、更新、报告、上载和下载操作。实际上,任务无论大小,均可通过脚本来处理。下面是简介:

如果您曾经在资深UNIX®用户工作时站在他的背后注视屏幕,可能会对命令行上不断滚动的咒语般的奇怪内容感到相当迷惑。如果您阅读过对话UNIX系列中以前的文章(请参见参考资料),那么至少所输入的某些诗一般的神秘内容——如波形符(~)、管道(|)、变量和重定向(<>)——看起来是熟悉的。您也许还会认出某些UNIX命令名称和组合,或者了解何时使用别名来作为某个命令组合的简写形式。

尽管如此,还有其他命令组合可能是您无法理解的,因为资深的UNIX用户通常以Shell脚本的形式收集一大堆小的、高度专门化的命令组合,以简化或自动化经常重复的任务。与输入或重新输入(可能)复杂的命令来完成某个繁琐任务不同,Shell脚本可以自动化该工作。

对话UNIX系列(请参见参考资料)的第6部分中,您将学习如何编写Shell脚本和更多命令行诀窍。

核心就是一个词:“自动化”

有些Shell脚本完全就是反复运行同样的命令,并处理同样的一组文件。例如,将您的整个主目录内容传播到三台远程计算机的ZShell脚本可以像清单1一样简单。


清单1.跨多台计算机同步主目录的简单Shell脚本
 
#!/bin/zsh

foreachmachine(grouchochicoharpo)
rsync-essh--times--perms--recursive--delete$HOME$machine:
end

若要将清单1用作Shell脚本,可以将上述内容保存到某个文件——例如simpleprop.zsh——并运行chmod+xsimpleprop.zsh以使该文件成为可执行文件。您可以通过输入./simpleprop.zsh来运行该脚本。

如果您想查看ZShell如何展开每个命令,可以将-x选项添加到脚本的#!(#号-感叹号对通常称为shuh-bang)行的结尾,如下所示:

#!/bin/zsh-x

该脚本对grouchochicoharpo中的每一台计算机运行rsync命令,并将$HOME替换为您的主目录(例如,/home/joe),将$machine替换为计算机名称。

清单1所示,变量和诸如循环等脚本控制结构使脚本更容易编写和维护。如果您想将第四台计算机(例如zeppo)包括到计算机池中,只需将其添加到该列表。如果您必须更改rsync命令,比如说添加另一个选项,则只需编辑一个实例。与在传统编程中一样,您也应该努力避免在Shell脚本中进行剪切和粘贴。


blue_rule.gif
c.gif
c.gif


使用恰当的参数

其他Shell脚本需要参数,或要处理的对象——文件、目录、计算机名称——的动态列表。例如,考虑清单2,这是前一示例的变体,它允许您使用命令行来指定您想要与之同步的计算机。


清单2.允许您指定要处理的计算机的清单1的变体
 
#!/bin/zsh

foreachmachine
rsync-essh--times--perms--recursive--delete$HOME$machine:
end

假设您将清单2保存在名为synch.zsh的文件中,您得按照zshsynch.zshmoelarrycurly的形式调用该脚本,以将主目录复制到另外的计算机larry和curly。

foreach行上缺少的列表并不是输入错误:如果您省略某个列表,则foreach结构将处理命令行上给出的参数列表。命令行参数也称为位置参数(positionalparameter),因为某个参数在命令行上的位置通常在语义上非常重要。

例如,如果您指定任何参数,则清单2可以利用位置参数的存在性或非存在性来提供有帮助的用法信息。增强的脚本如清单3所示。


清单3.许多脚本将在未提供参数时提供有帮助的消息
 
#!/bin/zsh

if[[-z$1||$1=="--help"]]
then
echo"usage:$0machine[machine...]
fi

foreachmachine
rsync-essh--times--perms--recursive--delete$HOME$machine:
end

命令行上的每个空格分隔的字符串变成了位置参数,包括所调用的脚本的名称。因此,命令synch.zsh只有一个位置参数$0synch.zsh--help命令有两个位置参数:$0$1,其中$1是字符串--help

所以,清单3表示“如果第一个位置参数为空(-z操作符测试空字符串)或(由||表示)如果第一个参数等于‘—help’,则打印用法信息”。(如果您刚开始编写脚本,可以考虑在每个脚本中提供用法信息作为提示。它提醒其他人——甚至您自己,如果您忘了的话——如何使用该脚本。)

短语[[-z$1||$1=="--help"]]if语句的条件,但您也可以将同样的条件子句用作命令,并将其与其他命令组合使用以控制通过脚本的流。请查看清单4。它枚举您的$PATH中的所有可执行命令,并将条件与其他命令组合使用以执行适当的工作。


清单4.列出$PATH中的命令
 
#!/bin/zsh

directories=(`echo$PATH|column-s':'-t`)

fordirectoryin$directories
do
[[-d$directory]]||continue

pushd"$directory"

forfilein*
do
[[-x$file&&!-d$file]]||continue
echo$file
done

popd
done|sort|uniq

此脚本中执行了相当多的操作,我们将它细分为以下几部分:

  1. 第一个实际脚本行——directories=(`echo$PATH|column-s':'-t`)——创建指定目录的数组。您在zsh中通过将参数放在括号中来创建数据,例如directories=(...)。在此例中,数组元素是通过在每个冒号(column-s':')处分拆$PATH以产生空格分隔的目录列表(column-t参数)来生成的。
  2. 对于列表中的每个目录,该脚本尝试枚举该目录中的可执行文件。步骤3至步骤6描述了该过程。
  3. [[-d$directory]]||continue行是所谓的short-circuiting命令的一个示例。short-circuiting命令在其逻辑条件产生确定的结果时立即终止。

    例如,[[-d$directory]]||continue短语使用逻辑“或”(||)——它首先执行第一个命令,并且——当且仅当——第一个命令失败时才执行第二个命令。因此,如果$directory中的条目存在,并且是一个目录(-d操作符),则测试成功,求值结束,并且continue命令(它跳过当前元素的处理)永远不会执行。

    然而,如果第一个测试失败,则会执行该逻辑的下一个条件或执行continue。(continue始终成功,因此它通常出现在short-circuiting命令的最后)。

    基于逻辑“与”(&&)的Short-circuiting首先执行第一个命令,并且——当且仅当——第一个命令成功时才执行第二个命令。

  4. pushd和对应的popd分别用于在处理前切换到新目录和在处理后切换到先前的目录。使用目录堆栈是一种理想的脚本技术,用于维持您在文件系统中的位置。
  5. 内部的for循环枚举当前工作目录中的所有文件——通配符*(星号)匹配所有条目——然后测试每个条目是否为文件。[[-x$file&&!-d$file]]||continue行表示“如果$file存在并且是可执行文件而且不是目录,则处理它;否则执行continue”。
  6. 最后,如果前面的所有条件都满足,则使用echo来显示文件名。
  7. 您弄明白该脚本的最后一行了吗?您可以将大多数控制结构的输出发送给另一个UNIX命令——毕竟,Shell将该控制结构视为一个命令。因此,整个脚本的输出通过sort、然后通过uniq进行管道传输,以产生在您的$PATH中找到的唯一命令的字母排序列表。

如果将清单4保存到一个名为listcmds.zsh的可执行文件,则输出可能类似如下:

$./listcmds.zsh
[
a2p
ab
ac
accept
accton
aclocal

short-circuiting命令在脚本中非常有用。它在单个命令中组合了条件和操作。而且由于每个UNIX命令都返回一个指示成功或失败的状态代码,因此,您可以使用任何命令作为“条件”——而不仅仅是使用测试操作符。根据约定,UNIX返回零(0)表示成功,返回非零表示失败,其中非零值反映所发生的错误类型。

例如,如果将[[-d$directory]]||continue行替换为cd$directory||continue,则可以从清单4中消除pushdpopd。如果cd命令成功,则它会返回0,并且逻辑“或”的求值可以立即结束。然而,如果cd失败,则它会返回非零,并且会执行continue


blue_rule.gif
c.gif
c.gif


不要删除。应存档!

现代UNIXShell——bashkshzsh——提供了许多控制结构和操作以创建复杂的脚本。由于您可以调用所有UNIX命令来将数据从一种形式处理为另一种形式,Shell脚本编程几乎与诸如C或Perl等完整语言中的编程一样丰富。

您可以使用脚本来自动化几乎所有个人或系统任务。脚本可以监视、存档、更新、上载、下载和转换数据。一个脚本可以只有单行或包括无数个子系统。任务无论大小,均可通过脚本来处理。实际上,如果您查看/etc/init.d目录,会看到在每次启动计算机时运行服务的各种Shell脚本。如果您创建了一个非常有用的脚本,您甚至可以将它部署为系统范围的实用程序。只需将其放到用户的$PATH上的某个目录中。

让我们创建一个实用程序,以练习您新发现的诀窍。脚本myrm将替换系统自己的rm实用程序。与彻底删除某个文件不同,myrm把要删除的文件复制到某个存档,对其进行唯一命名以便您以后能够找到它,然后再删除原始文件。myrm脚本有效但是非常简单,并且您还可以添加许多杂项功能。您还可以编写一个广泛的unrm(撤销删除)脚本作为配套实用程序。(您可以搜索Internet来找到各种各样的实现。)

myrm脚本如清单5所示。


清单5.用于在从文件系统中删除文件之前备份该文件的简单实用程序
 
#!/bin/zsh

backupdir=$HOME/.tomb
systemrm=/bin/rm

if[[-z$1||$1=="--help"]]
then
exec$systemrm
fi

if[[!-d$backupdir]]
then
mkdir-m0700$backupdir||echo"$0:Cannotcreate$backupdir";exit
fi

args$=$(getoptdfiPRrvw$*)||exec$systemrm

count=0
flags=""
foreachargumentin$args
do
case$argumentin
--)break;
;;

*)flags="$flags$argument";
((count=$count+1));
;;
esac
done
shift$(($count))

forfile
do
[[-e$file]]||continue
copyfile=$backupdir/$(basename$file).$(date"+%m.%d.%y.%H.%M.%S")
/bin/cp-R$file$copyfile
done

exec$systemrm$=flags"$@"

您应该发现该Shell脚本很容易理解,尽管其中存在一些之前尚未讨论过的新内容。让我们探讨一下那些新内容,然后查看整个脚本。

  1. 当Shell运行某个命令(如cpls)时,它会为该命令产生一个新进程,然后在继续之前等待该(子)进程完成。exec命令还启动另外一个命令,但是与产生新进程不同,exec使用一个新命令来“替换”当前进程——即Shell进程——的任务。换句话说,exec重用同一进程来启动一个新任务。在该脚本的上下文中,exec立即“终止”该脚本并启动指定的任务。
  2. UNIX实用程序getopt扫描位置参数以获得您指定的命名参数。这里,dfiPRrvw列表查找-d-f-i-P-R-r-v-w。如果出现别的选项,则getopt将会失败。否则,getopt返回一个以特殊字符串--结尾的选项字符串。
  3. shift命令从左到右删除位置参数。例如,如果命令行为myrm,-r-f-Pfile1file2file3,则shift3将分别删除$0$1$2,或-r-f-Pfile1file2file3将被重新编号为$0$1$2
  4. case语句的工作方式与传统编程语言中的对应结构相似。它将其参数与列表中的每个模式比较;当找到匹配项时,则执行对应的代码。与在Shell中非常类似,*匹配所有条目,并且可用作在未找到其他匹配项时的缺省操作。
  5. 特殊符号$@展开为所有(其余)的位置参数。
  6. zsh操作符$=在空白边界处拆分单词。当您有一个非常长的字符串,并且希望将该字符串拆分为各个参数时,$=是非常有用的。例如,如果变量x包含字符串'-r-f'——这是一个具有五个字符的单词——$=x将变为两个单独的单词-r-f

给出这些解释之后,您现在应该能够详细分析该脚本了。下面让我们逐块地研究一下该代码:

  • 第一个块设置整个脚本中使用的变量。
  • 下一个块应该是非常熟悉的:它在未提供参数时打印用法信息。它为什么执行(exec)实际的rm实用程序呢?如果您将此脚本命名为“rm”并将其放在$PATH中靠前的位置,则它就可以充当/bin/rm的替代者。该脚本的错误选项也是/bin/rm的错误选项,因此该脚本允许/bin/rm提供用法信息。
  • 下一个块在备份目录不存在时创建该目录。如果mkdir失败,则该脚本终止并显示适当的错误消息。
  • 下一个块查找位置参数列表中的dash参数。如果getopt成功,则$args具有一个选项列表。如果getopt失败,例如在它无法识别某个选项的时候,则它会打印错误消息,并且该脚本将退出并显示用法信息。
  • 随后的块捕获一个字符串中旨在提供给rm的所有选项。当遇到特殊getopt选项--时,选项收集过程停止。shift从参数列表中删除所有已处理的参数,保留待处理的文件和目录列表。
  • 从以forfile开头的块复制每个文件和目录,以便在您自己的存档目录中保存它们。每个文件的目录被逐字(-R)复制到存档目录,并附带当前日期和时间作为后缀,以确保该副本是唯一的,并且不会改写以前存档的具有相同名称的条目。
  • 最后,使用传递给该脚本的相同命令行选项来删除文件和目录。

然而,如果您碰巧需要刚才删除(意外删除?)的文件或目录,您可以在存档中查找原始副本。


blue_rule.gif
c.gif
c.gif


向自动化进军

您使用UNIX的时间越多,就越有可能创建脚本。脚本可以节省重新输入复杂的较长命令序列所需的时间和精力,并且还可以防止发生错误。Web上充满了其他人已创建的用于许多目的的有用脚本。很快您也会发布自己的神奇脚本。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值