Bash脚本的语义?
比我知道的任何其他语言,我每次需要一些小东西时“学会”了Googling的Bash。因此,我可以拼凑在一起似乎工作的小脚本。然而,我真的不知道发生了什么,我希望更正式地介绍Bash作为一种编程语言。例如:什么是评估顺序?什么是范围规则?什么是打字规则,例如。是一个字符串吗?什么是程序的状态 – 是字符串到变量名的键值分配;有多少,例如。堆栈?有堆吗?等等。
我想到咨询GNU Bash手册这种洞察力,但它似乎不是我想要的;它更多的是句法糖的洗衣单,而不是核心语义模型的解释。百万和一个“bash教程”在线只是更糟。也许我应该先学习sh,并理解Bash作为一个语法糖顶部这一点?我不知道这是否是一个准确的模型,但。 有什么建议么? 编辑:我被要求提供的例子,理想情况下,我想找。一个相当极端的例子,我认为一个“正式语义”是this paper on “the essence of JavaScript”.也许稍微不太正式的例子是Haskell 2010 report。
shell是操作系统的接口。它通常是一个或多或少的健壮的编程语言本身,但具有旨在使其易于与操作系统和文件系统进行特别交互的功能。 POSIX shell(以下简称“shell”)语义是一个mutt,结合了LISP的一些特性(s-expressions和shell
word splitting有很多共同之处)和C(shell的大部分
arithmetic syntax语义从C)。
shell语法的另一个根源来自它作为单独的UNIX实用程序的混乱的成长。大多数内置shell通常是内部实际上可以实现为外部命令。当它们意识到/ bin / [存在于许多系统上时,它会抛出很多shell初始化的循环。 $ if '/bin/[' -f '/bin/['; then echo t; fi # Tested as-is on OS X,without the `]` t wat? 如果你看一下shell是如何实现的,这更有意义。这里是我做的一个练习。它在Python,但我希望这不是一个hangup的任何人。这不是很强大,但它是有启发性的: #!/usr/bin/env python from __future__ import print_function import os,sys '''Hacky barebones shell.''' try: input=raw_input except NameError: pass def main(): while True: cmd = input('prompt> ') args = cmd.split() if not args: continue cpid = os.fork() if cpid == 0: # We're in a child process os.execl(args[0],*args) else: os.waitpid(cpid,0) if __name__ == '__main__': main() 我希望上面的内容清楚地说明了shell的执行模型: 1. Expand words. 2. Assume the first word is a command. 3. Execute that command with the following words as arguments. 扩展,命令解析,执行。所有的shell语义都绑定在这三个东西之一,虽然他们比我上面写的实现更丰富。 不是所有的命令fork。事实上,有一些命令,不使a ton of sense实现为外部(这样他们将不得不fork),但即使是那些经常可用作外部严格的POSIX兼容性。 Bash通过添加新的功能和关键字来增强POSIX shell来建立这个基础。它几乎与sh兼容,并且bash是如此无处不在,一些脚本作者走了多年没有意识到一个脚本实际上可能不工作在POSIX严格的系统。 (我也想知道人们如何能够如此关心一种编程语言的语义和风格,而对于shell的语义和风格这么少,但我不同)。 评估顺序 这是一个棘手的问题:Bash解释表达式在其主要语法从左到右,但在其算术语法它遵循C优先级。表达式与扩展不同。从bash手册的EXPANSION部分:
如果你理解文字分割,路径名扩展和参数扩展,你很好地了解大多数bash的方式。请注意,路径名扩展来自后面的字符分割是至关重要的,因为它确保名称中的空格的文件仍然可以匹配glob。这就是为什么好的使用glob扩展比parsing commands,一般。 范围 功能范围 很像旧的ECMAscript,shell有动态作用域,除非你在函数中明确声明名字。 $ foo() { echo $x; } $ bar() { local x; echo $x; } $ foo $ bar $ x=123 $ foo 123 $ bar $ … 环境与过程“范围” Subshel??l继承其父shell的变量,但其他种类的进程不继承未导出的名称。 $ x=123 $ ( echo $x ) 123 $ bash -c 'echo $x' $ export x $ bash -c 'echo $x' 123 $ y=123 bash -c 'echo $y' # another way to transiently export a name 123 您可以合并这些范围规则: $ foo() { > local -x bar=123 # Export foo,but only in this scope > bash -c 'echo $bar' > } $ foo 123 $ echo $bar $ 打字纪律 嗯,类型。是啊。 Bash真的没有类型,并且一切都扩展为一个字符串(或者也许一个词更合适。)但是让我们来看看不同类型的扩展。 字符串 几乎任何东西都可以被当作字符串。 bash中的barewords是字符串,其含义完全取决于应用于其中的扩展。 无扩展 可能值得证明,一个裸字真的只是一个字,而引号什么也没有改变。 $ echo foo foo $ 'echo' foo foo $ "echo" foo foo 子串扩展 $ fail='echoes' $ set -x # So we can see what's going on $ "${fail:0:-2}" Hello World + echo Hello World Hello World 有关扩展的更多信息,请阅读手册的参数扩展部分。它相当强大。 整数和算术表达式 您可以使用整数属性来填充名称,以便让shell将赋值表达式的右侧视为算术。然后,当参数扩展时,它将被计算为整数数学,然后扩展为…一个字符串。 $ foo=10+10 $ echo $foo 10+10 $ declare -i foo $ foo=$foo # Must re-evaluate the assignment $ echo $foo 20 $ echo "${foo:0:1}" # Still just a string 2 数组 参数和位置参数 在谈论数组之前,可能值得讨论位置参数。可以使用编号的参数$ 1,$ 2,$ 3等访问shell脚本的参数。您可以使用“$ @”一次访问所有这些参数,该扩展与数组有许多共同之处。您可以使用set或shift内置函数设置和更改位置参数,或者只需使用以下参数调用shell或shell函数: $ bash -c 'for ((i=1;i<=$#;i++)); do > printf "$%d => %sn" "$i" "${@:i:1}" > done' -- foo bar baz $1 => foo $2 => bar $3 => baz $ showpp() { > local i > for ((i=1;i<=$#;i++)); do > printf '$%d => %sn' "$i" "${@:i:1}" > done > } $ showpp foo bar baz $1 => foo $2 => bar $3 => baz $ showshift() { > shift 3 > showpp "$@" > } $ showshift foo bar baz biz quux xyzzy $1 => biz $2 => quux $3 => xyzzy bash手册有时也将$ 0作为位置参数。我发现这个混乱,因为它不包括在参数count $#,但它是一个编号参数,所以meh。 $ 0是shell或当前shell脚本的名称。 数组 数组的语法是在位置参数之后建模的,所以如果你喜欢,将数组看作一种命名类型的“外部位置参数”是最健康的。数组可以使用以下方法声明: $ foo=( element0 element1 element2 ) $ bar[3]=element3 $ baz=( [12]=element12 [0]=element0 ) 您可以通过索引访问数组元素: $ echo "${foo[1]}" element1 你可以切片数组: $ printf '"%s"n' "${foo[@]:1}" "element1" "element2" 如果将数组视为正常参数,您将获得零索引。 $ echo "$baz" element0 $ echo "$bar" # Even if the zeroth index isn't set $ … 如果使用引号或反斜杠来防止字符分割,数组将维护指定的wordsplitting: $ foo=( 'elementa b c' 'd e f' ) $ echo "${#foo[@]}" 2 数组和位置参数之间的主要区别是: >位置参数不稀疏。如果设置了$ 12,您也可以确保设置了$ 11。 (可以设置为空字符串,但$#不会小于12.)如果设置了“$ {arr [12]}”,则不能保证设置了“$ {arr [11]}”并且阵列的长度可以小到1。 使用路径名扩展来创建文件名数组通常很方便: $ dirs=( */ ) 命令 命令是关键,但它们也覆盖在比我手册更好的深度。阅读SHELL GRAMMAR部分。不同类型的命令是: >简单命令(例如$ startx) 执行模型 执行模型当然涉及堆和堆栈。这是所有UNIX程序的特有。 Bash还有一个用于shell函数的调用堆栈,通过嵌套使用调用程序内置函数可见。 参考文献: > bash手册的SHELL GRAMMAR部分 如果你想让我在一个特定的方向进一步扩大,请作出评论。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |