Bash脚本的语义?

前端之家收集整理的这篇文章主要介绍了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部分:

The order of expansions is: brace expansion; tilde expansion,parameter
and variable expansion,arithmetic expansion,and command substitution
(done in a left-to-right fashion); word splitting; and pathname 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 => %s\n" "$i" "${@:i:1}"
> done' -- foo bar baz
$1 => foo
$2 => bar
$3 => baz
$ showpp() {
>   local i
>   for ((i=1;i<=$#;i++)); do
>     printf '$%d => %s\n' "$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。
>数组的第零个元素明确是该数组的第零个元素。在位置参数中,第零个元素不是第一个参数,而是shell或shell脚本的名称
>要移动数组,你必须切割和重新分配它,如arr =(“$ {arr [@]:1}”)。你也可以unset arr [0],但这将使第一个元素在索引1。
>数组可以作为全局变量在shell函数之间隐式共享,但是你必须将位置参数显式地传递给shell函数,以便查看它们。

使用路径名扩展来创建文件名数组通常很方便:

$ dirs=( */ )

命令

命令是关键,但它们也覆盖在比我手册更好的深度。阅读SHELL GRAMMAR部分。不同类型的命令是:

>简单命令(例如$ startx)
>管道(例如$ yes | make config)(lol)
>列表(例如$ grep -qF foo file&& sed’s / foo / bar /’file> newfile)
>复合命令(例如$(cd -P / var / www / webroot&& echo“webroot is $ PWD”))
>协处理(复杂,无示例)
>函数(可以被视为简单命令的命名复合命令)

执行模型

执行模型当然涉及堆和堆栈。这是所有UNIX程序的特有。 Bash还有一个用于shell函数调用堆栈,通过嵌套使用调用程序内置函数可见。

参考文献:

> bash手册的SHELL GRAMMAR部分
> XCU Shell Command Language文档
>在Greycat的wiki上的Bash Guide
> Advanced Programming in the UNIX Environment

如果你想让我在一个特定的方向进一步扩大,请作出评论

猜你在找的Bash相关文章