为什么POSIX强制性实用程序未内置在Shell中?


45

这个问题的目的是回答好奇心,而不是解决特定的计算问题。问题是:为什么POSIX强制实用程序通常不内置在Shell实现中?

例如,我有一个脚本,该脚本基本上读取一些小的文本文件并检查它们的格式是否正确,但是由于大量的字符串操作,在我的计算机上运行该脚本需要27秒。通过调用各种实用程序,此字符串操作使成千上万的新进程变慢了。我非常相信,如果一些工具,建于,即grepsedcuttr,和expr,那么脚本会在第二或更小(根据我用C的经验)。

似乎在许多情况下,构建这些实用程序都会使Shell脚本中的解决方案是否具有可接受的性能产生差异。

显然,有一个选择不内置这些实用程序的原因。也许在系统级别使用一个实用程序版本可以避免各种外壳使用该实用程序的多个不相等版本。我真的想不出很多其他原因来保持创建这么多新流程的开销,并且POSIX对实用程序进行了足够的定义,只要它们各自是POSIX,那么使用不同的实现就似乎不是什么大问题符合。至少没有这么多流程的低效率那样大的问题。


15
如果27秒太慢,则可以使用Python,Perl或其他一些半编译语言。或者,发布脚本的慢速部分并寻求改进。可能是您在使用三个或四个命令,而其中一个(速度较快)则可以执行。
roaima

8
不幸的是,Shell并不是真正用于执行繁重的任务,而且自从您可以仅使用Shell脚本的时代以来,世界已经发生了很大的变化。我同意roaima的观点-每个合理的系统管理员都应该使用Python或Perl,并且不要指望shell能处理所有事情
Sergiy Kolodyazhnyy

16
Shell的主要目的是运行其他程序,而不是直接操作数据。多年以来,当它们提供的某些外部程序或功能(例如globbing,算术printf等)被认为足够有用时,它们已被合并到shell中。
chepner '17

8
如果您将脚本发布到codereview.stackexchange.com,我相信审阅者会提出一些建议,以加快脚本的运行速度(或至少指出为什么应使用Python / etc而不是shell编写脚本)。
chepner '17

5
@Kyle:awk在POSIX强制性的实用工具,尤其适合(即非常快)实施脚本,否则你可能实现使用sedcuttrgrep,和expr在shell脚本。
名义动物

Answers:


11

Shell脚本不应以这种速度运行。如果要提高脚本速度,请在perl中尝试。如果那仍然太慢,那么您将不得不使用静态类型的语言,例如java或c,或者为perl编写一个C模块,以运行太慢的部分。

Shell是原型设计的第一级,如果您可以使用Shell来证明这一概念,那么请使用一种更好的脚本语言,该脚本语言可以进行更多的边界检查,而这需要占用数英亩的Shell。

Unix OS有望包含许多小型程序,这些小型程序执行定义明确的任务,从而构成一个更大的画面。这是一件好事,因为它分隔了较大的程序。以qmail为例,将其与sendmail进行比较。qmail由许多程序组成:

http://www.nrg4u.com/qmail/the-big-qmail-picture-103-p1.gif

利用网络守护程序不会帮助您利用队列管理器。


OP明确不要求任何有关提高代码速度的建议。问题是,为什么某些实用程序没有像cd或那样的内置函数pwd
斯蒂芬C,

4
真正。答案是要表达整体式和分隔式之间的差异,并说明这样做的原因。
艾德·内维尔


1
@StephenC cd是一个内置函数,实际上是必须的,因为更改子进程中的工作目录不会影响父进程。
乔纳斯

67

为什么POSIX强制性实用程序未内置在Shell中?

因为要符合POSIX,所以要求系统1提供大多数实用程序作为独立命令。

内置它们意味着它们必须存在于外壳内部和外部两个不同的位置。当然,可以通过使用内置的Shell脚本包装程序来实现外部版本,但这将不利于非Shell应用程序调用实用程序。

请注意,BusyBox采用了您建议的路径,方法是在内部实现许多命令,并使用指向其自身的链接提供独立的变体。一个问题是命令集可能很大,而实现通常是标准的子集,因此不兼容。

还请注意,至少ksh93bashzsh通过为运行中的shell提供自定义方法以从共享库动态加载内建函数来进一步发展。从技术上讲,没有什么可以阻止所有POSIX实用程序被实现并可以作为内置函数使用。

最后,催生新进程已成为现代OS的快速操作。如果您确实受到性能问题的困扰,则可能需要进行一些改进以使脚本运行更快。

1 POSIX.1-2008

但是,所有标准实用程序,包括表中的常规内置程序,而不是特殊内置实用程序中描述的特殊内置程序,都应以某种方式实现,以便可以通过exec系列的exec系列进行访问。它的功能如POSIX.1-2008的“系统接口”卷中所定义,并且可以由需要它的那些标准实用程序(env,find,nice,nohup,time,xargs)直接调用。


4
这是正确的答案,但是我只想补充一点,因为这些实用程序的接口通常还是通过stdin / stdout进行的,即使它们中的每一个也都以bash的内置例程的形式实现,实际上仍然需要自行分叉并为管道中的每个命令创建管道,因此只有少量收益
Chunko

2
@Chunko是的。子外壳比fork / exec执行的进程轻。
jlliagre

3
@slebetman你错过了我的意思。子外壳既不是线程也不是执行过程,无论它们是否在Linux上运行。子shell只是其父级的克隆,由fork not后面跟exec;来创建。forkexec。相比,如今的操作非常轻巧。
jlliagre

3
我测算busybox nofork内建程序的开销比noexec内建程序少10倍 ,而内建程序的开销又比单独二进制文件的fork + exec低约5倍。符合unix.stackexchange.com/a/274322/29483 的定义有趣的是,busybox并不能解决nofork所有问题,尽管我知道某些不通过清理内存而缩短了busybox代码,而只是依赖于一个短暂的过程。
sourcejedi

1
@jlliagre:在linux上,fork创建了一个进程。您可能会遗漏的一点是,在Linux上,他们对流程进行了如此多的优化,以至于开发人员已经确定,创建任何更轻量的东西不再具有进一步的优势。基本上,在linux中,进程就像线程一样轻巧。
slebetman

9

BASH参考手册中

内置命令对于实现使用单独的实用工具无法获得或不方便的功能是必需的。

如您所知,UNIX理念在很大程度上依赖于功能有限的多个应用程序。每个内置插件都有很好的理由内置。其他所有内容都不是。我认为,更有趣的一类问题是:“为什么 pwd内置?”


2
一句话:模块化
Peschke

2
/ bin / pwd存在。我认为cd这将是一个更好的例子,它不可能作为单独的工具来实现。
Oskar Skog's

1
@OskarSkog这就是重点。 cd必须内置,pwd不是。那么bash实现者为什么选择包括它呢?
Stig Hemmer


@StigHemmer /bin/bash确实存在,但是它仍然是内置的。请参阅gnu.org/software/bash/manual/html_node/…
Stephen C

8

AT&T的家伙问自己同样的事情

如果您查看AT&T软件工具包的历史(自从核心团队离开以来,目前一直在github上处于休眠状态),这正是他们对AT&T Korn shell(又名ksh93)所做的工作。

性能始终是ksh93维护者动机的一部分,并且在构建ksh时,您可以选择构建许多常见的POSIX实用程序作为动态加载的库。通过将这些命令绑定到目录名称(例如)/opt/ast/bin,您可以根据目录名称在中的位置来控制使用哪个版本的命令$PATH

例子:

cat chmod chown cksum cmp cp cut date expr fmt head join ln
mkdir mkfifo mktemp mv nl od paste rm tail tr uniq uuencode wc

完整列表可以在github ast仓库中找到。

请注意,大多数ast工具都有自己的出处,并且与更常见的gnu实现有很大不同。AT&T Research团队遵循官方标准,这是在您无法共享代码时实现互操作性的一种方式。


6

因此,我们没有将资源用于优化原始工具,从而满足每个特定的需求。我想我们需要解释的是,这种特定的愿望要花多少钱才能实现。

POSIX对实用程序进行了足够的定义,以至于采用不同的实现似乎并不是什么大问题。

这是一个错误的假设:-P。

POSIX系统由于充分的理由而变得越来越强大和方便。作为事后的标准,它从来没有真正赶上。

Ubuntu开始努力切换到精简的POSIX shell脚本,以优化旧的System V初始化启动过程。我并不是说它失败了,但是它确实触发了许多必须清除的错误:“ bashisms”,即在/bin/sh假定bash功能可用的情况下运行的脚本。

POSIX sh不是一种好的通用编程语言。它的主要目的是作为交互式外壳很好地工作。一旦开始将命令保存到脚本中,请注意您正在接近Turing tarpit。例如,不可能在正常管道的中间检测到故障。 为此bash添加set -o pipefail的,但这不在POSIX中。

几乎每一个比Windows更复杂的实用程序都提供了类似的有用但未标准化的功能true

对于您概述的任务类别,您可以为Awk,Perl和当今的Python绘制粗线。创建了不同的工具,并独立进行了开发。您是否希望将GNU Awk等包含在libutilposixextended中?

我并不是说我们现在可以找到一种普遍适用的更好的方法。我对Python情有独钟。尽管我对GNU Awk特有的某些功能感到沮丧,但Awk却异常强大。但是重点是,单独处理大量字符串(大概是从文件的行)并不是POSIX Shell的设计目标。


我想知道,如果外壳理解了有关命令的所有信息,那么假设从可配置的位置列表执行的任何命令都将被视为内置命令,那么外壳程序是否会有任何困难?如果脚本执行cat -@fnord foo了,外壳程序应该决定它,因为它不知道-@它意味着什么,因此需要调用实际的命令,但是仅给出cat <foo >bar外壳程序,就不需要产生另一个进程。
超级猫

1
@supercat的复杂性。
sourcejedi

2

还有一个问题:您要将其构建到哪个外壳中?

大多数Unix / Linux系统具有多个独立开发的外壳程序(sh / bash / korn / ???)。如果将这些工具构建到外壳中,则最终会为每个外壳使用这些工具的不同实现。这将导致开销,并且您可能最终会在grep中遇到不同的功能/错误,具体取决于您用来调用它的shell。


如今,zsh在某些圈子中非常受欢迎。从历史上看,csh / tcsh拥有大量追随者,但我认为您今天没有看到太多。还有
一大堆

模块化。使用内置插件时,每次对其中一个内置插件进行更改时,都需要重新编译或重新安装外壳。
can-ned_food

1

许多人回答很好。我只想补充这些答案。我认为UNIX的哲学是工具应该做一件事并且做好事。如果人们尝试制作一种全面的工具,那么失败的地方就更多了。以这种方式限制功能使工具集可靠。

另外,考虑一下,如果将诸如sedgrep之类的功能内置到外壳程序中,是否可以在需要时从命令行调用它一样容易?

最后,请考虑一下,您希望在BASH中使用的某些功能在BASH中。例如,使用=〜二进制运算符可实现BASH中RE匹配的功能(有关更多信息,请参见手册页中的Shell语法,具体请参考if的[[]]构造讨论)。举一个非常快速的例子,假设我正在文件中搜索2个十六进制数字:

while read line; do
    if [[ $line =~ 0x[[:xdigit:]]{2} ]]; then
        # do something important with it
    fi
done < input_file.txt

至于类似sed的功能,请在同一手册页的“扩展”标题下的“参数扩展”下查看。您会看到很多可以做的事,让人想起sed。我最经常使用sed对文本进行某些替换类型更改。建立在上述基础之上:

# this does not take into account the saving of the substituted text
# it shows only how to do it
while read line; do
    ${line/pattern/substitution}
done < input_file.txt

最终,以上是“更好”的选择吗?

grep -E "[[:xdigit:]]{3}" input_file.txt
sed -e 's/pattern/substitution/' input_file.txt


1

我想这是历史性的事故。

在1960年代末和1970年代初创建UNIX时,计算机的内存几乎没有今天的内存大。当时可以将所有这些功能实现为shell内置程序,但是由于内存的限制,它们将不得不限制其可以实现的功能数量,否则将面临内存不足和/或交换垃圾的风险问题。

另一方面,通过将给定的功能实现为单独的程序,并通过使两个必需的系统调用尽可能轻松地启动一个新进程,它们可以创建一个脚本环境,该脚本环境不存在这些问题,并且仍可以合理地运行速度。

当然,一旦将这些事情实现为单独的过程,人们就会从不是 shell的程序中启动它们,然后必须保持这种状态,否则所有这些软件都会突然崩溃。

但这并不是说您不能两次实现某些功能,并且确实有些Shell实现了某些功能,这些功能应该作为内置的Shell来作为外部程序;例如,bash将echo命令实现为内置命令,但是还有一个/usr/bin/echo

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.