为什么脚本语言(例如 Perl、Python、Ruby)不适合作为 shell 语言?

Muh*_*uri 341 ruby python bash shell perl

bashzshfish等 shell 语言与上述脚本语言之间有什么区别,使它们更适合 shell?

使用命令行时,shell 语言似乎要容易得多。对我来说,使用 bash 比在 ipython 中使用 shell 配置文件要顺畅得多,尽管有相反的报道。我认为大多数人都会同意我的观点,Python 中的大部分中型到大型编程比 bash 更容易。我使用 Python 作为我最熟悉的语言,Perl 和 Ruby 也是如此。

我试图阐明原因,但除了假设两者中对字符串的不同处理与此有关之外,我无法阐明原因。

这个问题的原因是我希望开发一种在两者中都可用的语言。如果你知道这种语言,也请张贴。

正如 S.Lott 所解释的,这个问题需要澄清一下。我在询问 shell语言的特性与脚本语言的特性。所以比较不是关于各种交互式(REPL)环境的特性,例如历史和命令行替换。该问题的另一种表达方式是:

一种适用于复杂系统设计的编程语言能否同时表达有用的单行代码,可以访问文件系统或控制作业?一种编程语言可以有效地扩展和缩减吗?

Jör*_*tag 467

我能想到一些不同之处;只是思想流在这里,没有特别的顺序:

  1. Python & Co. 旨在擅长编写脚本。Bash & Co. 旨在擅长编写脚本,绝对不妥协。IOW:Python 被设计成擅长脚本和非脚本,Bash 只关心脚本。

  2. Bash & Co. 是无类型的,Python & Co. 是强类型的,这意味着数字123、字符串123和文件123有很大的不同。然而,它们不是静态类型的,这意味着它们需要有不同的文字,以便将它们分开。
    例子:

                    | Ruby             | Bash    
    -----------------------------------------
    number          | 123              | 123
    string          | '123'            | 123
    regexp          | /123/            | 123
    file            | File.open('123') | 123
    file descriptor | IO.open('123')   | 123
    URI             | URI.parse('123') | 123
    command         | `123`            | 123
    
    Run Code Online (Sandbox Code Playgroud)
  3. 蟒蛇及公司设计规模10000,100000,甚至百万行程序,猛砸及公司设计规模下降到10名字符的程序。

  4. 在 Bash & Co. 中,文件、目录、文件描述符、进程都是一等对象,在 Python 中,只有 Python 对象是一等对象,如果要操作文件、目录等,必须将它们包装在一个首先是 Python 对象。

  5. Shell编程基本上是数据流编程。没有人意识到这一点,即使是编写 shell 的人也没有意识到,但事实证明,shell 非常擅长这一点,而通用语言则不然。在通用编程世界中,数据流似乎主要被视为一种并发模型,而不是一种编程范式。

我有一种感觉,试图通过将特性或 DSL 固定到通用编程语言上来解决这些问题是行不通的。至少,我还没有看到令人信服的实施。有RuSH(Ruby shell),它试图在 Ruby 中实现一个 shell,有rush,它是一个在 Ruby 中进行 shell 编程的内部 DSL,还有Hotwire,它是一个 Python shell,但 IMO 没有一个接近与 Bash、Zsh、fish 和朋友竞争。

实际上,恕我直言,目前最好的外壳是Microsoft PowerShell,考虑到几十年来,微软一直拥有最差的外壳evar,这非常令人惊讶。我的意思是,COMMAND.COM?真的吗?(不幸的是,他们仍然有一个蹩脚的终端。它仍然是从那以后一直存在的“命令提示符”,什么?Windows 3.0?)

PowerShell 基本上是通过忽略 Microsoft 曾经做过的所有事情(COMMAND.COM, CMD.EXE, VBScript, JScript)而创建的,而是从 Unix shell 开始,然后删除所有向后兼容的 cruft(如用于命令替换的反引号)并稍微调整一下以使其更像 Windows -友好(例如使用现在未使用的反引号作为转义字符而不是反斜杠,后者是 Windows 中的路径组件分隔符)。在那之后,就是魔法发生的时候。

他们从上面解决了问题 1 和 3,与 Python 相比,他们基本上做出了相反的选择。Python首先关心大型程序,其次编写脚本。Bash 只关心脚本。PowerShell 首先关心脚本编写,其次才是大程序。对我来说,一个决定性的时刻是观看 Jeffrey Snover(PowerShell 的首席设计师)的采访视频,当面试官问他可以用 PowerShell 编写多大的程序时,Snover 毫不犹豫地回答:“80 个字符。” 在那一刻我意识到这终于是微软的一个“获得”shell编程的人(可能与PowerShell既不是由 Microsoft 的编程语言组(即 lambda 演算数学书呆子)或操作系统组(内核书呆子)开发,而是由服务器组(即实际使用shell 的系统管理员)),我可能应该认真研究一下 PowerShell。

数字 2是通过静态输入参数来解决的。因此,您可以编写123并且 PowerShell 知道它是字符串、数字还是文件,因为 cmdlet(这是在 PowerShell 中调用的 shell 命令)向 shell 声明其参数的类型。这有很深的影响:与 Unix 不同,在 Unix 中,每个命令负责解析自己的参数(shell 基本上将参数作为字符串数组传递),PowerShell 中的参数解析由shell完成. cmdlet 将所有选项、标志和参数,以及它们的类型、名称和文档(!)指定给 shell,然后 shell 可以在一个集中的地方执行参数解析、制表符完成、智能感知、内联文档弹出等。(这不是革命性的,PowerShell 设计者承认像 DIGITAL 命令语言 (DCL) 和 IBM OS/400 命令语言 (CL) 这样的 shell 是现有技术。对于曾经使用过 AS/400 的任何人来说,这听起来应该很熟悉. 在 OS/400 中,您可以编写一个 shell 命令,如果您不知道某些参数的语法,您可以简单地将它们省略并点击F4, 这将带来一个带有标签字段、下拉菜单、帮助文本等的菜单(类似于 HTML 表单)。这是唯一可能的,因为操作系统知道所有可能的参数及其类型。)在 Unix shell 中,这些信息通常是重复三遍:在命令本身的参数解析代码中,在bash-completion制表符完成的脚本中和联机帮助页中。

第 4 个问题是通过 PowerShell 对强类型对象(包括文件、进程、文件夹等内容)进行操作这一事实来解决的。

数字 5特别有趣,因为 PowerShell 是我所知道的唯一一个 shell,编写它的人实际上意识到shell 本质上是数据流引擎,并故意将其实现为数据流引擎。

PowerShell 的另一个好处是命名约定:所有 cmdlet 都被命名Action-Object,此外,还有用于特定操作和特定对象的标准化名称。(同样,这对于 OS/400 用户来说应该很熟悉。)例如,与接收某些信息相关的所有内容都称为Get-Foo。并且在(子)对象上操作的所有东西都被称为Bar-ChildItem。所以,相当于lsGet-ChildItem(虽然PowerShell还提供了内置的别名lsdir -事实上,只要它是有道理的,他们提供Unix和CMD.EXE别名以及缩写(gci在这种情况下))。

但IMO的杀手级功能是强类型对象管道。虽然 PowerShell 源自 Unix shell,但有一个非常重要的区别:在 Unix 中,所有通信(通过管道和重定向以及通过命令参数)都是使用无类型、无结构的字符串完成的。在 PowerShell 中,它都是强类型的结构化对象。这是如此令人难以置信的强大,以至于我真的很想知道为什么没有其他人想到它。(嗯,他们有,但他们从来没有流行过。)在我的 shell 脚本中,我估计多达三分之一的命令只是在其他两个不同意通用文本格式的命令之间充当适配器. 许多适配器在 PowerShell 中消失了,因为 cmdlet 交换结构化对象而不是非结构化文本。如果你看命令内部,它们几乎由三个阶段组成:将文本输入解析为内部对象表示,操作对象,将它们转换回文本。同样,第一和第三阶段基本上消失了,因为数据已经作为对象进来了。

然而,设计者通过他们所谓的Adaptive Type System非常小心地保留了 shell 脚本的动态性和灵活性。

无论如何,我不想把它变成一个 PowerShell 商业广告。PowerShell有很多不太好的地方,尽管其中大部分都与 Windows 或特定实现有关,而与概念无关。(例如,它是在 .NET 中实现的,这意味着如果 .NET 框架由于某些其他应用程序需要它而尚未在文件系统缓存中,那么第一次启动 shell 可能需要几秒钟的时间。考虑您经常使用 shell 不到一秒钟,这是完全不可接受的。)

我想说明的最重要的一点是,如果您想查看脚本语言和 shell 中的现有工作,您不应该止步于 Unix 和 Ruby/Python/Perl/PHP 系列。例如,已经提到了TclRexx将是另一种脚本语言。Emacs Lisp将是另一个。在外壳领域,有一些已经提到的大型机/中端外壳,例如 OS/400 命令行和 DCL。此外,Plan9 的 rc。

  • 4 个月 13 票,然后 20 小时 84 票?我被reddied了还是什么?@Mauricio Scheffer:我浏览了几乎所有 Channel9 上的视频,甚至可以追溯到 PS 仍然被称为 Monad 的时候,但没有任何内容让我感到抱歉,抱歉。@Dave:有一个名为 [Pash (PoSh + Bash)](http://PaSh.SF.Net/) 的项目,但三年前只有一个版本。[BUSH](http://BUSH.SF.Net/) 和 [FISH](http://FISH.SF.Net/) 远离 Bourne *语法*,但它们仍然处理文本。@codebliss:问题是关于 Python 作为 *shell* 语言。这里有趣的数字是 *one LOC*。 (11认同)
  • @Muhammad Alkarouri:另一种看待它的方式:PowerShell 不支持多态或重载。在具有多态性的语言中,语言根据参数的类型决定执行哪个过程。(例如,在`x.ToString()` 中取决于`x` *哪个* `ToString()` 被执行的类型。)在PowerShell 中,这正是双重的:语言决定解释参数的类型,根据程序。然而*另一种*解释是语法是根据参数类型动态构建的,因此只有解析标记的方法。 (3认同)
  • @Alexandru:PowerShell(因此得名)仍然是一个shell,因此使执行程序变得容易(尽管根据可能需要引用的参数,并非完全没有痛苦)。因此,您可以像在其他任何地方一样使用 ImageMagick,而无需首先编写/查找 cmdlet 或包装它的 PowerShell 函数。*但是* PowerShell 本地实现可以更酷,例如,通过传递图像对象并将图像过滤器实现为 cmdlet。我还在考虑写一些东西来允许像`Load-Image foo.bmp | 应用-模糊-半径 5 | 保存图像 x.png`。 (3认同)
  • 对问题的两个部分都有很好的回答。关于第 2 点和第 4 点:这是因为静态类型,还是因为强制转换操作?在我知道的大多数非 shell 语言(静态或动态)中,`123` 不能定义两种不同的类型。您还有解释 Powershell 的自适应类型系统的链接吗?谷歌似乎没有向我抛出任何我能理解的东西。 (2认同)
  • 很好的答案!你还记得那个对杰弗里·斯诺弗的采访的链接吗? (2认同)
  • @Muhammad Alkarouri:基本上,因为 PowerShell *知道*,比如说,`repeat` 循环的第一个参数是迭代次数,第二个参数是要重复的代码,它*知道*在 `repeat 123 123` 中第一个“123”是一个数字,第二个是一个命令。(完全编造的例子。)它基本上是根据它们的结果类型重载文字,这起初听起来很奇怪,但实际上 C# 中的 lambda 文字也是如此。(根据它们绑定到的变量/参数的类型,它们被解释为 `Action/Func` 或 `Expression`。) (2认同)
  • 维基百科页面有一些有用的 PowerShell 命令示例:http://en.wikipedia.org/wiki/Windows_PowerShell#Examples (2认同)
  • @Jorg ... Unix shell 的强大之处在于,所有艰苦的工作都是由普通可执行文件完成的,其中许多是 POSIX 的一部分或只是一个事实上的标准(找我一个没有 Perl、grep 或 awk 的 Unix 盒子)。“cmdlet”是使用 5 年前描述的特殊约定在 .NET 之上编写的。如果您必须为 ImageMagick 搜索/编写 cmdlet,强类型有什么好处?……与微软所提出的相反,标准的 Unix 工具不会每隔几年就被重新发明。 (2认同)

SFE*_*ley 56

这是文化。在Bourne shell中几乎是25岁; 它是最早的脚本语言之一,也是 Unix 管理员核心需求的第一个很好的解决方案。(即,将所有其他实用程序连接在一起并执行典型的 Unix 任务而不必每次都编译该死的 C 程序的“胶水”。)

按照现代标准,它的语法是残暴的,其奇怪的规则和标点符号风格(在 1970 年代每个字节都计算在内时很有用)使得非管理员很难理解它。但它完成了工作。 这些缺陷和缺点已通过其后代(ksh、bash、zsh)的进化改进得到解决,而无需重新构思其背后的思想。管理员坚持使用核心语法,因为虽然很奇怪,但没有其他方法可以更好地处理简单的事情而不会妨碍。

对于复杂的东西,Perl 出现并演变成一种半管理半应用程序语言。但是越是复杂的东西,就越是被视为应用程序而不是管理工作,所以业务人员倾向于寻找“程序员”而不是“管理员”来做这件事,尽管正确的极客倾向于两者兼而有之。所以这就是重点所在,Perl 应用程序功能的进化改进导致了......好吧,Python 和 Ruby。(这是一种过度简化,但 Perl 是这两种语言的几个灵感之一。)

结果?专业化。管理员倾向于认为现代解释语言对于他们每天有偿做的事情来说太重了。总的来说,他们是对的。他们不需要对象。他们不关心数据结构。他们需要命令。 他们需要胶水。 没有比 Bourne shell 概念更好地执行命令的方法了(也许 Tcl 除外,这里已经提到过);而伯恩就足够好了。

程序员——如今他们不得不越来越多地了解 devops——看着 Bourne shell 的局限性,想知道怎么会有人忍受它。但他们知道,虽然他们肯定对于I / O和文件操作的Unixish风格倾斜的工具,不是更好为宗旨。我已经在 Ruby 中编写了诸如备份脚本和一次性文件重命名之类的东西,因为我比 bash 更了解它,但是任何专门的管理员都可以在 bash 中做同样的事情——可能用更少的行和更少的开销,但是无论哪种方式,它都会起作用。

经常会问“为什么当Z更好时每个人都使用Y?” ——但是技术的进化,就像其他一切的进化一样,往往会止步于足够好。 除非差异被视为破坏交易的挫败感,否则“更好”的解决方案不会获胜。Bourne 类型的脚本可能会让感到沮丧,但对于一直使用它的人以及它所要完成的工作来说,它总是能胜任。


Ivo*_*ijk 50

Shell 语言必须易于使用。您想键入一次性命令,而不是小程序。即你想输入

ls -laR /usr
Run Code Online (Sandbox Code Playgroud)

不是

shell.ls("/usr", long=True, all=True, recursive=True)
Run Code Online (Sandbox Code Playgroud)

这(也)意味着 shell 语言并不真正关心参数是选项、字符串、数字还是其他东西。

此外,shell 中的编程结构是一个附加组件,甚至并不总是内置的。即在(ba)sh中考虑if[的组合,seq用于生成序列,等等。

最后,shell 具有您在编程中需要较少或不同的特定需求。即管道、文件重定向、进程/作业控制等。

  • 那么你不是在使用 perl,而只是通过 perl 执行 shell 命令。 (7认同)
  • 您仍然需要正确引用并平衡您的括号。你在这里提供了一个微不足道的论点,例如“ls --sort=time -R /bin /usr”怎么样? (4认同)

sle*_*man 41

如果你知道这种语言,也请张贴。

Tcl 就是这样一种语言。主要是因为它主要被设计为 CAD 程序的 shell 解释器。以下是一位核心 Python 程序员的*体验,了解为什么 tcl 是这样设计的:http : //www.yosefk.com/blog/i-cant-believe-im-prising-tcl.html

对我来说,我已经编写并一直在使用和改进 tcl shell(当然是用 tcl 编写的)作为我自制路由器上的主要 Linux 登录 shell:Pure tcl readline

我喜欢 tcl 的一些原因通常与它的语法与传统 shell 的相似性有关:

  1. 最基本的,tcl 语法是command argument argument.... 没有别的了。这与 bash、csh 甚至 DOS shell 相同。

  2. 一个裸字被认为是一个字符串。这再次类似于传统的 shell,允许您编写:open myfile.txt w+而不是open "myfile.txt" "w+".

  3. 由于 1 和 2 的基础,tcl 最终只有很少的无关语法。你用更少的标点符号编写代码:puts Hello而不是printf("Hello");. 在编写程序时,您不会感到如此痛苦,因为您花了很多时间思考要写什么。当您使用shell来复制一个文件,你不觉得你只需要输入,并具有输入(",);一次又一次很烦人的速度非常快。

*注意:不是我,我是铁杆 tcl 程序员


Cha*_*ens 17

谁说他们不是?看看ZoidbergREPL(读取评估打印循环)制作糟糕的外壳,因为每个命令都必须在语法上正确,并且运行程序从:

foo arg1 arg2 arg3
Run Code Online (Sandbox Code Playgroud)

system "foo", "arg1", "arg2", "arg3"
Run Code Online (Sandbox Code Playgroud)

甚至不要让我开始尝试进行重定向。

因此,您需要一个自定义 shell(而不是 REPL)来理解命令和重定向以及您想用来将命令绑定在一起的语言。我认为zoid(Zoidberg shell)在这方面做得很好。

  • 我实际上已经修复了一些主要错误(我是新的维护者!)。查看 [`Zoidberg`](http://p3rl.org/zoiduser) 了解您所有基于 Perl 的 shell 需求! (6认同)

Joe*_*ger 13

这个帖子激励我接管基于 Perl 的 shell Zoidberg 的维护。经过一些修复后,它又可以使用了!查看用户指南Bundle::Zoidberg使用您最喜欢的 CPAN 客户端进行安装。


Dig*_*oss 9

不。


不,脚本语言可能不适合 shell。


问题在于宏语言和其他所有语言之间的二分法。

shell 与其他遗留宏语言(如nroffm4)属于同一类别。在这些处理器中,一切都是字符串,处理器定义了从输入字符串到输出字符串的映射。

所有语言在两个方向上都跨越了某些界限,但通常很清楚系统的类别是还是,嗯,我不知道官方术语......我会说“真正的语言”。

可以肯定的是,您可以使用 Ruby 等语言输入所有命令,它甚至可能是真正的 shell 的次佳选择,但它永远不会是宏语言。要尊重的语法太多了。它需要太多的引用。

但是当您开始使用宏语言进行编程时,它有其自身的问题,因为必须做出太多妥协才能摆脱所有这些语法。输入的字符串不带引号。需要重新引入各种数量的魔法来注入缺失的语法。我曾经code-golf在 nroff 做过一次,只是为了与众不同。这很奇怪。宏语言中大型实​​现的源代码是可怕的。


rog*_*ack 6

我认为这是解析的问题。Shell 语言默认假定 $ command 表示您的意思是要运行的命令,Python/Ruby 需要您执行 system("command") 或其他操作。不是他们不合适,只是还没有人真正做到过,至少我是这么认为的。Rush http://rush.heroku.com/是 Ruby 中的一个示例尝试,Python 有“iPython”或类似的东西。


小智 6

由于两者都是正式的编程语言,您可以用一种语言做的事情,也可以用另一种语言做。其实这是一个设计重点问题。Shell 语言是为交互式使用而设计的,而脚本语言则不是。

设计中的基本区别在于命令之间的数据存储和变量的作用域。在 Bash 等中,您必须跳过箍来存储值(例如,像 的命令set a='something'),而在像 Python 这样的语言中,您只需使用赋值语句 ( a = 'something')。在 shell 语言中使用这些值时,您必须告诉语言您想要变量的值,而在脚本语言中,您必须告诉语言何时需要字符串的直接值。这在交互使用时会产生影响。

ls被定义为命令的脚本语言中

a = some_value

ls a*b  
Run Code Online (Sandbox Code Playgroud)

(是什么a意思?这意味着 some_value * (无论 b 是什么)还是你的意思是“a'anystring'b'?。在脚本语言中,默认值是存储在内存中的 a。)

ls 'a*b'  Now means what the Unix ls a*b means.
Run Code Online (Sandbox Code Playgroud)

用类似 Bash 的语言

set a=some_value

ls a*b   means what the Unix ls a*b means.

ls $a*b  uses an explicit recall of the value of a.
Run Code Online (Sandbox Code Playgroud)

脚本语言使存储和调用值变得容易,并且难以对值具有瞬时作用域。Shell 语言可以存储和调用值,但每个命令都有一个简单的瞬态范围。