我写了一个bash脚本,执行时没有先编译它。效果很好。它可以在有权限或无权限的情况下工作,但是涉及到C程序,我们需要编译源代码。为什么?
我写了一个bash脚本,执行时没有先编译它。效果很好。它可以在有权限或无权限的情况下工作,但是涉及到C程序,我们需要编译源代码。为什么?
Answers:
这意味着不编译shell脚本,而是对其进行解释:shell一次解释一个命令,并在每次执行时弄清楚如何执行每个命令。这对于shell脚本来说是有意义的,因为它们会花费大部分时间来运行其他程序。
另一方面,C程序通常会被编译:在运行它们之前,编译器会一劳永逸地将它们完整地转换为机器代码。过去曾经有C解释器(例如AtSoft ST上的HiSoft的C解释器),但是它们非常不寻常。如今,C编译器非常快。TCC是如此之快,您可以使用它来创建“ C脚本”,并且可以使用#!/usr/bin/tcc -run
shebang 来创建C程序,这些C程序的运行方式与Shell脚本相同(从用户的角度来看)。
某些语言通常同时具有解释器和编译器:BASIC是一个让人想到的示例。
您还可以找到所谓的Shell脚本编译器,但我所见过的只是混淆了包装程序:它们仍然使用Shell来实际解释脚本。正如mtraceur指出的那样,尽管适当的shell脚本编译器当然是可能的,但并不是很有趣。
考虑这种情况的另一种方法是认为外壳程序的脚本解释功能是其命令行处理功能的扩展,这自然会导致一种解释方法。另一方面,C旨在产生独立的二进制文件;这导致了一种编译方法。通常编译的语言也往往会发芽解释器,或者至少是命令行解析器(称为REPL,read-eval-print循环; shell本身就是REPL)。
execve
,open
,close
,read
,write
,和pipe
系统调用,穿插了一些getenv
,setenv
和内部的HashMap /阵列操作(未导出的变量)等。Bourneshell和派生类也不是从低级编译器调整(例如代码重新排序等
考虑以下程序:
2 Mars Bars
2 Milks
1 Bread
1 Corn Flakes
在bash
这样,你在店里徘徊寻找火星吧,最后找到他们,然后闲逛寻找牛奶等,这工作,因为你正在运行一个名为“有经验的购物者”复杂的程序,当你看到一个能够识别面包以及购物的所有其他复杂性。bash
是一个相当复杂的程序。
或者,您可以将购物清单交给购物编译器。编译器会思考一会儿,然后给您一个新列表。此列表是LONG,但包含更简单的说明:
... lots of instructions on how to get to the store, get a shopping cart etc.
move west one aisle.
move north two sections.
move hand to shelf three.
grab object.
move hand to shopping cart.
release object.
... and so on and so forth.
如您所见,编译器确切地知道商店中所有物品的位置,因此不需要整个“寻找事物”阶段。
这是一个独立的程序,不需要“有经验的购物者”即可执行。它所需要的只是具有“基本人性操作系统”的人。
返回计算机程序:bash
是“有经验的购物者”,可以使用脚本,而无需编译任何内容。AC编译器生成一个独立的程序,不再需要任何帮助即可运行。
解释器和编译器都有其优点和缺点。
一切都归结为技术之间的技术差异,即您可以将人类可以读写的程序转换为计算机可以理解的机器指令,而每种方法的优缺点是编写某些语言需要编译器的原因,有些是为了解释而写的。
(注意:为了解决这个问题,我在这里做了很多简化。为了更深入地理解,我回答底部的技术说明详细阐述/完善了此处的一些简化方式,并且对此答案的评论中有一些有用的说明和讨论。)
基本上有两种编程语言类别:
C是第一类(C 编译器将C 语言翻译为计算机的机器代码:机器代码保存到文件中,然后在您运行该机器代码时,它会执行您想要的操作)。
bash在第二类中(bash 解释器读取bash 语言,bash 解释器执行您想要的操作:因此本质上没有“编译器模块”,解释器执行解释和执行,而编译器执行读取和翻译) 。
您可能已经注意到这意味着什么:
使用C,您只需执行一次 “解释”步骤,然后每当需要运行该程序时,您就告诉您的计算机执行机器代码-您的计算机可以直接运行它而无需进行任何额外的“思考”。
使用bash,您每次运行程序时都必须执行“解释”步骤-您的计算机正在运行bash解释器,并且bash解释器会进行额外的“思考”,以弄清楚每次每次命令需要做什么。
因此,C程序需要更多的CPU,内存和时间来准备(编译步骤),但是运行所需的时间和工作却更少。bash程序需要较少的CPU,内存和时间来准备,但是要花更多的时间和精力来运行。您可能大部分时间都不会注意到这些差异,因为当今的计算机速度非常快,但这确实有所作为,并且当您需要运行大型或复杂程序或许多小程序时,这种差异就会加起来。
另外,由于C程序已转换为计算机的机器代码(“本机语言”),因此您无法将程序复制到具有不同机器代码的另一台计算机上(例如,将Intel 64位复制到Intel 32上)位,或者从Intel到ARM或MIPS或其他)。您必须花时间再次为其他机器语言编译它。但是bash程序可以直接移到另一台安装了bash解释器的计算机上,并且可以正常运行。
C的开发者从几十年前开始就在硬件上编写操作系统和其他程序,但受到现代标准的限制。由于各种原因,当时将程序转换为计算机的机器代码是实现该目标的最佳方法。另外,他们从事的工作是有效地运行他们编写的代码。
而Bourne shell和bash的制造商则希望相反:他们想编写可以立即执行的程序/命令-在命令行的终端中,您只想编写一行,一条命令并拥有它执行。他们希望您编写的脚本可以在安装了Shell解释器/程序的任何地方使用。
简而言之,对于bash不需要编译器,而对于C则需要编译器,因为这些语言被不同地转换为实际的计算机操作,并且选择了不同的执行方式,因为这些语言具有不同的目标。
您实际上可以创建C 解释器或bash 编译器。没有什么能阻止这种可能性的发生:仅仅是那些语言是为不同的目的而制作的。仅用另一种语言重写程序通常比为复杂的编程语言编写好的解释器或编译器容易。尤其是当这些语言具有特定的功能时,它们就很擅长,并且首先以某种特定的工作方式进行设计。C被设计为可编译的,因此它在交互式外壳程序中缺少许多您想要的方便快捷方式,但是它对于表达非常特定的,低级的数据/内存操作以及与操作系统的交互非常有用。 ,这是您要编写高效的已编译代码时经常发现自己要执行的任务。同时,bash非常擅长执行其他程序,
更高级的细节:实际上,有些编程语言是两种类型的混合(它们以“大多数方式”翻译源代码,因此它们一次只能执行大部分解释/“思考”),而只能做一点点解释/“思考”)。Java,Python和许多其他现代语言实际上就是这样的混合体:它们试图为您提供一些解释型语言的可移植性和/或快速开发的好处,以及一些编译语言的速度。有很多可能的方法可以将这些方法结合起来,并且不同的语言也有不同的用法。如果您想深入研究本主题,则可以阅读编译成“字节码”的编程语言(有点像编译成自己的“机器语言”
您询问了执行位:实际上,可执行位只是在告诉操作系统允许执行该文件。我怀疑bash脚本在没有执行许可的情况下为您工作的唯一原因实际上是因为您是从bash shell内部运行它们的。通常,当操作系统要求不设置执行位的情况下执行文件时,操作系统只会返回错误。但是某些shell(如bash)将看到该错误,并通过基本模拟操作系统通常会执行的步骤来自行承担运行该文件的麻烦(在文件开头查找“#!”行,然后尝试执行该程序来解释文件(默认值为它本身或/bin/sh
没有“#!”行)。
有时,您的系统上已经安装了编译器,有时,IDE带有自己的编译器和/或为您运行编译器。这可能会使已编译的语言像未编译的语言一样“感觉”到使用,但是技术上的区别仍然存在。
“编译”语言不一定要编译为机器代码,而整个编译过程本身就是一个主题。基本上,该术语被广泛使用:它实际上可以指代一些东西。在特定的意义上,“编译器”只是从一种语言(通常是人类更易于使用的“高级”语言)到另一种语言(通常是计算机更易于使用的“低级”语言)的翻译器-有时,但实际上不是很常见,这是机器代码)。此外,有时当人们说“编译器”时,他们实际上是在谈论多个程序一起工作(对于典型的C编译器,实际上是四个程序:“预处理器”,编译器本身,“汇编器”和“连接器”)。
编程/脚本语言可以被编译或解释。
编译的可执行文件总是更快,并且在执行之前可以检测到许多错误。
解释型语言通常比编写的语言更易于编写和适应,并且不像编译的语言那么严格,并且不需要编译,从而使它们更易于分发。
想象一下英语不是您的母语(如果英语不是您的母语,这对您来说可能很容易)。
您可能会通过3种方式来阅读:
计算机具有各种“本机语言”-处理器可以理解的指令与操作系统(例如Windows,Linux,OSX等)可以理解的指令的组合。这种语言是人类不可读的。
脚本语言(例如Bash)通常分为1类和2类。它们一次占用一行,翻译并运行该行,然后移至下一行。在Mac和Linux上,默认情况下针对不同的语言(例如Bash,Python和Perl)安装了许多不同的解释器。在Windows上,您必须自己安装这些。
许多脚本语言会做一些预处理-尝试通过编译经常运行的代码块或否则会使应用程序变慢的代码块来加快执行速度。您可能会听到的一些术语包括提前(AOT)或即时(JIT)编译。
最后,编译语言(如C)在运行它们之前先翻译整个程序。这样做的好处是,转换可以在与执行不同的机器上完成,因此,当您将程序提供给用户时,尽管可能仍然存在错误,但已经可以清除几种类型的错误。就像您将其提供给翻译人员一样,并且我提到了garboola mizene resplunks
,这对您来说似乎是有效的英语,但是翻译人员可以告诉您我在胡说八道。运行编译的程序时,它不需要解释器-它已经在计算机的本地语言中
但是,编译语言有一个缺点:我提到计算机有一种本地语言,由硬件和操作系统的功能组成-好吧,如果您在Windows上编译程序,则不会期望编译后的程序在Mac。一些语言通过编译为一种中途语言来解决此问题-有点像Pidgin英语-这样,您可以获得编译语言的好处,并且速度有所提高,但这确实意味着您需要捆绑带有您的代码的解释器(或使用已安装的代码)。
最后,您的IDE可能正在为您编译文件,并且可以在运行代码之前告诉您有关错误的信息。有时,这种错误检查可能比编译器更深入。编译器通常只会检查所需的内容,以便生成合理的本机代码。IDE通常会进行一些额外的检查,并且可以告诉您,例如,是否两次定义了变量,或者是否导入了未使用的东西。
很多人都在谈论解释与编译,但是我认为如果仔细观察它可能会产生误导,因为某些解释语言实际上是在执行之前被编译为中间字节码。
最后,将C程序需要编译为可执行格式的真正原因是,计算机需要做大量工作才能将C源文件中的代码转换为可以运行的内容,因此保存产品很有意义将所有这些工作都存储到可执行文件中,因此您无需在每次运行程序时都再次执行该操作。
另一方面,Shell解释器只需要做很少的工作即可将Shell脚本转换为“机器操作”。基本上只需要逐行读取脚本,在空白处分割脚本,设置一些文件重定向和管道,然后执行fork + exec。由于与在Shell脚本中启动进程相比,解析和处理Shell脚本的文本输入的开销非常小,因此将Shell脚本编译为中间机器格式而不是仅仅解释会显得过大。源代码直接。