当前位置: 首页 > 科技观察

带你了解Shell脚本编程的陷阱

时间:2023-03-16 00:35:41 科技观察

Shell脚本很棒,可以很轻松的写出有用的东西。或者甚至像这样的白痴命令:#MakenameswithwordscontainingGo:$grep-i^go/usr/share/dict/*|剪切-d:-f2|排序-R|head-n1goldfish如果使用其他编程语言,用多行代码实现就比较费脑力了,比如Ruby:puts(Dir['/usr/share/dict/*-english'].mapdo|f|File.open(f).readlines.select{|l|l[0..1].downcase=='go'}end.flatten.sample.chomp)Ruby版本的代码没有那么长也没有复杂的。但是shell版本非常简单,我什至无需实际测试就可以确定它是正确的。至于Ruby版本,我不敢肯定不会出错,只好测试一下。另外,它的长度是原来的两倍,而且看起来更复杂。这就是人们使用shell脚本的原因,它既简单又实用。这是另一个示例:curlhttps://nl.wikipedia.org/wiki/Lijst_van_Nederlandse_gemeenten|grep'^

  • (.+).*
  • |\1|'|grep-Ev'(^Tabelvan|^Lijstvan|Nederland)'这个脚本可以从维基百科上的GetalistofDutchgrassrootsregimes获得。几年前我写了这个ad-hoc脚本来快速生成数据库,现在还可以,写起来也没费多少功夫。但是在Ruby中做同样的事情要麻烦很多。现在针对外壳的缺点。随着代码量的增加,你的脚本会越来越难维护,但你不想用另一种语言重写它,因为你已经在这个shell版本上花费了很多时间。我将这种情况称为“Shell脚本陷阱”,这是沉没成本谬误浪费的特例,永远不要忘记)。事实上,许多脚本会增长到比预期更大的大小,并且您通常会花太多时间“修复错误”或“添加小功能”。如此循环往复,令人头晕目眩。如果你一开始就用Python、Ruby或者其他类似的语言来写这个程序,你可能会花更多的时间写最新版本,但是以后维护起来会容易很多,肯定会少一些错误很多。以我的packman.vim脚本为例。它从一个简单的for循环开始遍历所有目录,加上一个gitpull,但在那之后,它停止了,现在大约有200行代码,这当然不是最复杂的脚本,但如果我写了它从一开始就按计划在Go中添加“打印状态”或“从配置文件克隆新的git存储库”等功能会容易得多;添加对“并行克隆”的支持这几乎不是问题,而且很难(尽管并非不可能)在shell脚本中完成。事后看来,我本可以节省时间并获得更好的结果。出于类似的原因,我后悔写了很多这样的shell脚本,我在2018年的新年决心是不再犯类似的错误。附录:问题总结应该注意的是,shell编程确实有一些实际限制。以下是一些示例:在处理某些包含“空格”或其他“特殊”字符的文件名时,需要特别注意细节。绝大多数脚本都会出错,即使是有经验的作者(比如我)编写的脚本也是如此,因为很容易出错,仅添加引号是不够的。有许多所谓的“正确”和“错误”方法。你应该使用哪个还是命令?我应该使用$@还是$*,我应该加引号吗?您应该使用cmd$arg还是cmd"$arg"?等等等等您不能在变量中存储空字节(0x00);shell脚本与二进制数据杂乱无章。虽然您可以非常快速地编写有用的东西,但实现更复杂的算法要痛苦得多,即使使用ksh/zsh/bash扩展也是如此。我上面解析HTML的脚本适合临时使用,但您真的不想在生产中使用它。很难跨平台编写通用的shell脚本。/bin/sh可以是dash也可以是bash,不同的shell有不同的运行方式。grep、sed等外部??工具不一定支持相同的参数。您能确定您的脚本适用于所有版本的Linux、macOS和Windows(过去、现在和未来)吗?调试shell脚本可能很困难,尤其是因为您看到的语法很快就会变得模糊,而且并不是每个人都熟悉shell编程的上下文。错误处理可能很棘手(检查$?或set-e),排除超出“出现问题”级别的复杂错误几乎是不可能的。除非你使用set-u,undefined变量不会报错,而这会导致一些“有趣的事情”,比如rm-r~/$undefined会删除用户的整个主目录(见Github上的这个悲剧)。一切都是一个字符串。一些shell引入了数组,这可以工作,但语法非常难看和复杂。使用分数进行数字运算仍然很麻烦,并且依赖于外部工具,如bc或dc($((..))这种方式仅适用于整数)。反馈您可以发送电子邮件至martin@arp242.net,或在GitHub上创建问题以给我反馈、提出问题等。