跳转到内容

Bourne Shell 脚本/模块化

来自维基教科书,开放书籍,开放世界

如果你曾在 shell 之外的其他环境中进行过编程,你可能熟悉以下场景:你在编写程序,愉快地在键盘上敲打,直到你注意到

  • 你必须重复之前键入的一些代码,因为你的程序必须在两个不同的位置执行完全相同的操作;或者
  • 你的程序太了,已经无法理解了。

换句话说,你已经到了需要将程序分成可以作为独立子程序运行并根据需要调用的模块的点。在这方面,在 Bourne Shell 中工作与在其他任何语言中工作没有什么不同。迟早你会发现自己写了一个 shell 脚本,它太长了,已经不实用。到了将你的脚本分成模块的时候了。

命名函数

[编辑 | 编辑源代码]

当然,将脚本分成模块的简单而明显的方法是创建几个不同的 shell 脚本——只需几个带有可执行权限的独立文本文件即可。但是,使用独立的文件并不总是最实用的解决方案。将脚本分散到多个文件可能会使维护变得困难。尤其是当你最终得到了一些 shell 脚本,这些脚本只有在从另一个特定 shell 脚本中调用时才有意义。

特别是对于这种情况,Bourne Shell 包含命名函数的概念:将名称与命令列表相关联,并使用名称作为命令来执行命令列表。它看起来是这样的

name () command group
* 其中name 是一个文本字符串
command group 是任何分组的命令列表(使用花括号或圆括号)。

此功能在整个 shell 中可用,并在多种情况下很有用。首先,你可以使用它将一个长 shell 脚本分成多个模块。但其次,你可以使用它在你自己的环境中定义你自己的小宏,你不希望为其创建完整的脚本。许多现代 shell 都包含一个用于此的内置命令,称为alias,但老式的 shell(如原始的 Bourne Shell)没有;你可以使用命名函数来实现相同的结果。

创建命名函数

[编辑 | 编辑源代码]

具有简单命令组的函数

[编辑 | 编辑源代码]

让我们从简单地创建一个打印“Hello World!!”的函数开始。让我们称它为“hw”。它看起来是这样的

Hello world 作为命名函数
hw() {
>  echo 'Hello World!!';
>}


我们可以在 shell 脚本或交互式 shell 中使用完全相同的代码——上面的示例来自交互式 shell。关于这个示例,有几点需要注意。首先,我们不需要单独的关键字来定义函数,圆括号就可以了。对于 shell 来说,函数定义就像扩展的变量定义。它们是环境的一部分;你只需通过定义名称和含义来设置它们。

第二点需要注意的是,一旦你越过了圆括号,所有正常的规则都适用于命令组。在我们的例子中,我们使用了一个带花括号的命令组,因此我们需要在 echo 命令之后加一个分号。我们想要打印的字符串包含感叹号,因此我们必须对其进行引用(像往常一样)。而且,我们允许在多行上拆分命令组,即使在交互模式下也是如此,这与普通命令一样。

以下是使用新函数的方法

调用我们的函数

代码:

$ hw

输出:

Hello World!!

在独立进程中执行的函数

[编辑 | 编辑源代码]

函数的定义采用一个命令组。任何命令组。包括使用圆括号而不是花括号的命令组。因此,如果我们愿意,我们可以定义一个在自己的环境中作为子进程运行的函数。以下是 hello world 再次在子进程中运行

Hello world 作为命名函数
hw() ( echo 'Hello World!!' )


这次所有内容都放在一行上,以保持简短,但与之前相同的规则适用。当然,相同的环境规则也适用,因此函数中定义的任何变量在函数结束时将不再可用。

带参数的函数

[编辑 | 编辑源代码]

如果你曾在其他编程语言中进行过编程,你就会知道,最有用的函数是那些带参数的函数。换句话说,那些不总是严格地做同样的事情,而是可以受调用函数时传入的值影响的函数。因此,这里有一个有趣的问题:我们可以将参数传递给函数吗?我们可以像这样创建定义吗?

functionWithParams (ARG0, ARG1) { 使用 ARG0 和 ARG1 做一些事情 }


然后进行类似“functionWithParams(Hello, World)”的调用?好吧,答案很简单:不行。圆括号只是为了让 shell 知道前面的名称是函数的名称而不是变量的名称,并没有为参数留出空间。

或者说,更确切地说是上面的答案很简单,而不是答案很简单。你看,当你执行函数时,你实际上是在执行一个命令。对于 shell 来说,执行命名函数和执行“ls”之间几乎没有区别。它就像任何其他命令一样。它可能无法拥有参数,但就像任何其他命令一样,它当然可以拥有命令行参数。因此,我们可能无法像上面那样定义带参数的函数,但我们当然可以这样做

带命令行参数的函数

代码:

$ repeatOne () { echo $1; }
$ repeatOne 'Hello World!'

输出:

Hello World!

你也可以使用环境中的任何其他变量。当然,这是你在交互式 shell 中从命令行调用函数时的不错技巧。但在 shell 脚本中呢?命令行参数的位置变量已经被 shell 脚本的参数占用,对吧?啊,等等! shell 中执行的每个命令(无论如何执行)都有自己的一套命令行参数!因此,不存在干扰,你可以使用相同的机制。例如,如果我们像这样定义一个脚本

function.sh:shell 脚本中的函数
#!/bin/sh

myFunction() {
  echo $1
}

echo $1
myFunction
myFunction "Hello World"
echo $1


然后它按我们想要的方式执行

执行 function.sh 脚本

代码:

$ . function.sh 'Goodbye World!!'

输出:

Goodbye World!


Hello World

Goodbye World!

环境中的函数

[编辑 | 编辑源代码]

我们之前已经提到过,但现在让我们更深入地探讨一下:函数到底是什么?我们已经暗示过它们是命令列表的别名或宏,并且它们是环境的一部分。但函数到底是什么

从 Shell 的角度来看,函数只是一个非常详细的变量定义。它实际上就是:一个名称(一个文本字符串)与一个值(更多文本)相关联,并且当使用该名称时可以被该值替换。就像 Shell 变量一样。我们也可以证明这一点:只需在交互式 Shell 中定义一个函数,然后执行“set”命令(列出当前环境中的所有变量定义)。您的函数将出现在列表中。

因为函数实际上是一种特殊的 Shell 变量定义,所以它们的行为与“普通”变量完全相同。

  • 函数通过列出其名称、定义运算符和函数的值来定义。但函数使用不同的定义运算符:'()' 而不是 '='。这告诉 Shell 对函数添加一些特殊注意事项(例如,在使用函数时不需要 '$' 字符)。
  • 函数是环境的一部分。这意味着当从 Shell 发出命令时,函数也会被复制到传递给已发出命令的环境副本中。
  • 如果函数被标记为导出,使用 'export' 命令,它们也可以传递给新的子进程。一些 Shell 需要对函数使用特殊的命令行参数才能进行 'export'(例如,bash 需要你执行 'export -f' 来导出函数)。
  • 您可以使用 'unset' 命令删除函数定义。

当然,当你使用它们时,函数的行为就像命令一样(毕竟它们被扩展成一个命令列表)。我们已经看到你可以将命令行参数与函数一起使用,以及使用位置变量来匹配。但是你也可以重定向输入和输出到命令和从命令中,以及将命令管道在一起。


下一页: 调试和信号处理 | 上一页: 文件和流
主页: Bourne Shell 脚本
华夏公益教科书