一些背景:在 bash 中编写可重用代码时,我看到了一些 包含多个函数定义的 shell 脚本。有时我无法弄清楚脚本中定义的函数是否相关,或者脚本实际上是一团糟,或者我不知道如何在一个脚本中组织多个函数(为此目的您有什么建议吗?) 。因此,我正在考虑编写一个仅包含一个函数定义的脚本,并且我猜我将不得不组织多个文件。在一个脚本中组织多个函数定义还是组织多个脚本文件,每个脚本文件都包含一个函数定义,哪一种更好?
在下文中,我将假设只编写一个仅包含一个函数定义的脚本。
我的实际问题从这里开始:
写作时仅包含一个函数定义的脚本,我可以写
#! /bin/bash
function myfunc() {
echo $1, $2, $3
# do some work...
}
然后我可以使用它
source /path/to/myscript
myfunc 1 2 3
或者,我可以通过消除函数定义并将函数体中的代码移至脚本来实现代码可重用性的相同目标:
#! /bin/bash
echo $1, $2, $3
# do some work...
然后我可以使用它
source /path/to/myscript 1 2 3
?就脚本内容和用法而言,第二种方式看起来比第一种方式更简洁,但我不确定是否有人会出于某种充分的理由不喜欢第二种方式。我想知道以上两种方式的优点和缺点是什么?您在实践中有何建议?
如果您在实践中使用第一种方法,您会将脚本命名为与函数相同的名称,即如果您的脚本包含名为 的函数myfunc
,您也会将脚本命名为myfunc
吗?
谢谢。
答案1
我根据情况两者都做。在大多数情况下,我会在将使用它们的同一脚本中声明这些函数,但是我确实有一个脚本“工具包”,其中包含要在所有脚本之间共享的变量和函数文件。
发挥作用或不发挥作用
函数的主要目的是 DRY(不要重复自己)。如果您有一些代码将在脚本中多次使用,那么它应该位于函数中。
在非重复代码上使用函数是一个偏好问题。有些人(包括我自己)认为它更整洁。此外,我认为它更类似于“真正的”编程语言的使用方式。
谷歌的外壳风格指南指出如果您的代码至少包含一个函数,您应该使用“主”函数作为所有代码的包装器。 (本质上,您的脚本只是一系列以一次调用结束的函数声明main
)
在单个脚本内声明函数(或编写代码)
- 简单(任何查看代码的人都可以更轻松地追踪每个函数的作用)
- 提高可移植性(只需将一个文件从一个系统移动到另一个系统)
如果您正在编写一个独立的脚本来执行单个任务,这可能是正确的选择。
在单独的文件中声明函数
- DRY(不要重复自己)
- 可以更容易管理
如果您正在创建一组共享许多通用功能的工具,那么这可能是正确的选择。
在我的示例中,我有工具包中的所有或至少大多数脚本使用的函数。这包括一些查询我们的库存管理系统并返回有关服务器的信息(例如 IP 地址、操作系统版本等)的函数。这些信息对于许多工具都很有用,因此在一个文件中声明此类函数而不是在所有文件中声明这些函数是有意义的。单独文件。此外,我们最近对库存管理系统进行了更改,因此不必更改 10 多个不同的文件,我只需更改公共文件即可查询新系统,其他文件无需修改即可正常工作。
这样做的一些缺点是它更复杂。每个文件都有以下语句来获取此公共文件:
if [[ -f "${0%/*}/lib/common.sh" ]]; then
. "${0%/*}/lib/common.sh"
else
echo "Error! lib/common.sh not found!"
exit 1
fi
如果用户获取工具包并修改目录结构,或者不获取整个目录结构,则工具将无法按预期工作,因为他们将无法获取公共文件。
答案2
这将是一个很长的答案,而不是一个是或否类型的答案。它描述了 Bash 函数涵盖的不同编程方面,在选择操作路线时应考虑这些方面。
最后,您可以在单独但参数化的源文件中找到单个函数定义的演示。
一个函数是:
- A命名的
- 堵塞代码的
- 这可以是重复使用(多次调用)
考虑到上述内容,它有什么好处,并且有其他选择吗?
逻辑性组织代码的
将一批用于特定目的的命令组合在一起非常有意义。毕竟,这就是这个词的含义之一功能-某物有这样那样的功能。不过,可以通过其他方式实现相同的目标:
- 命令可以直观地分组,例如。占据连续的行,并用空行和注释与其余代码分开。或者只是放在同一行并在末尾添加注释。
- 它们可以分组在一个单独的文件中,该文件可以是被处决或者来源。
- 可以使用花括号来阻止它们
{ ... }
。 - 他们甚至可以被分组并单独执行子外壳,当放在子壳层括号内时
( ... )
。1
批量操作
函数具有使用标准 Bash 运算符轻松进行批量 I/O 重定向的优势。块、子 shell、脚本执行和源也可以,但对于纯粹的可视命令组,I/O 文件描述符更改必须通过
exec
.本土化
这是函数相对于简单块的特殊之处之一。能够制造变量
local
。这通常是一种很好的编程实践,因为它将变量可见性保持在最低水平,从而减少了更广泛的命名空间中的混乱并引入了隔离。这反过来又是一项有价值的安全功能。这里可以选择单独的脚本和子 shell。关于位置参数,源文件也是如此。
参数化
每个函数都有自己的位置参数集
$1
,$2
等等。这允许在命令调用中以显式方式修改函数的行为。这里的替代方案包括独立脚本和源文件。
代码鉴别
一个函数的姓名是独一无二的,这不仅意味着它们的明确认可,还意味着它们被执行的能力。并且多次需要。块、子 shell 或纯文本分组都无法
{}
提供此功能 - 除非它们被循环包围。尽管脚本和源文件可以。信息
最后,函数名称和它可能包含的任何注释都会影响代码的可理解性。所有其他选项都存在回旋余地,但像子 shell 和
{}
块这样的匿名解决方案无法为代码块提供正式的标题(和地址)。
下面的示例说明了如何将单个函数合理地放置在单独的源文件中。
case "$1" in
a)
f () { echo "This function was defined in a)."; }
;;
b)
f () { echo "This function was defined in b)."; }
;;
*)
f () { echo "This function was defined with no recognised argument."; }
;;
esac
这个条件定义就像一个函数工厂。从其他地方获取的文件会f
在那里安装该功能。所有这些代码都可能包含在另一个函数中,就目前情况而言,几乎没有什么区别。但是,一旦函数生成器变得足够大,能够独立成为一个实体,这种方法就结合了两者的优点:函数和源文件。
也可以看看
事实上 Bash 允许类似的函数定义:f () ( ...;)
.如此定义,该f
函数将在其自己的子 shell 中执行。