网站搜索

Bash 函数的入门介绍


如果你刚开始学习 Bash 脚本,你可能会发现自己在脚本中不断重复相同的命令,但有一种更好的方法。我将解释“DRY”是什么意思,以及你应该如何使用函数来实现它。

当我在 Windows 上编写我的第一个程序时,它是一个批处理脚本。我基本上是凭感觉写的,几乎没有什么理解,我写了一组非常字面的指令,夹杂着 GOTO 语句,并且重复了很多次,导致脚本几乎无法阅读。

在编程中,DRY 意思是“不要重复自己”。函数是实现这一点的机制,我会向你展示如何做到这一点。

Bash 函数入门

我是一个视觉型学习者,认为在了解技术细节之前先看到主题会有帮助。所以这里有一个函数:

foo() {
  echo "Hello, World!"
}

“foo” 是函数名,这就是我们调用它的方式:

foo
foo

注意到我们调用了这个函数两次吗?这就是它们的本质:可重用的代码片段,让我们的代码保持“简洁”。

我们也可以使用“function”关键字来定义它们,但这样做意味着我们无法在其他 shell 中使用该脚本:

function foo () {
   echo "Hello, World!"
}

继续。一个更现实的例子是封装一个有用的命令:

backup() {
    cp "foo.txt" "foo.txt.backup-$(date +%Y%m%d)"
}

此函数通过在文件名后简单地添加“backup”和日期来备份名为“foo.txt”的文件。我们将在下一节中使该函数可重复使用。

函数可以更大,跨越多行。可以把它们看作是迷你、可重复使用的脚本或命令。

向函数传递参数

函数如果不能传递值,那么它们用处不大,而我们可以传递——这些被称为参数或实参。

这些术语之间的区别很小。我会称它们为参数,但 Bash 手册可能称它们为参数值。

再一次,最好的方式是先给出一个例子:

foo() {
    echo "${1}" "${2}"
}

“${1}” 和 “${2}” 分别是第一个和第二个参数。你可以这样将它们传递给 “foo” :

foo "Hello, " "World!"
foo "Hello, " "Universe!"

在单词周围使用“引号”来定义它们的边界。注意“Hello”也包括一个逗号和一个空格吗?以这种方式使用引号可以让 Bash 清楚地识别它们的边界。

这些被称为字符串,我有一篇更高级的文章,讲述了如何以巧妙的方式更改字符串。

无论如何,回到函数。“${1}”和“${2}”在 Bash 中被称为位置参数(即带有位置的参数)。有多种方法可以访问参数;有些方法更高级,但“${1}”和“${2}”对于初学者来说已经足够了。

让我们在一个实际例子中使用参数:

backup() {
    cp "${1}" "${1}.backup-$(date +%Y%m%d)"
}

这改进了我们之前的“备份”功能。它只接受一个参数:文件名(或路径)。现在这个函数可以备份你想要的任何文件,比撒哈拉沙漠还干燥。

通过将 "${1}" 放在双引号中,Bash 可以将其替换(展开)为分配的值。单引号的工作方式不同,会使字符串变为字面量:

backup() {
    echo '${1}' '${1}.backup-$(date +%Y%m%d)'
}
backup "foo.txt"

我已将“cp”改为“echo”,这样你就可以看到“${1}”参数的样子。它不再是变量,而是一个字面值:完全按照你指定的方式。下面会详细介绍变量。

我还想介绍一个小概念,这将使你的代码更易读(非常重要):

backup() {
    local file_path="${1}"
    cp "$file_path" "$file_path.backup-$(date +%Y%m%d)"
}

“local file_path=${1}” 被称为变量赋值。变量是存储值的位置。我们创建了一个名为“file_path”的变量,并将“${1}”(函数参数)存储到其中。现在“file_path”等于“${1}”,我们在命令中使用它代替。

使用这种方式给变量命名来描述参数的含义。将来的你会感谢现在的自己,因为你可以一眼就理解它。

从函数返回值

从 Bash 函数返回值与其他语言有点不同。在 Bash 中,我们只能返回退出代码,这是一个表示命令执行成功状态的数字。例如:

foo() {
  return 0
}

foo && echo "success"

只有当“foo”具有成功退出码(“0”)时,“&&”才会“回显”“success”。

让我们看一个失败的退出代码(即命令执行失败):

foo() {
  return 1  # Non-zero means unsuccessful.
}

foo && echo "success"  # Doesn't echo "success".

简而言之,Bash 函数的返回值仅用于退出状态码。

继续。让我们从一个函数中获取一个有用的值。

订阅通讯,提升 Bash 技能

订阅通讯,提升 Bash 技能

订阅即表示您同意接收新闻通讯和营销邮件,并接受 Valnet 的使用条款和隐私政策。您可以随时取消订阅。

在 Bash 中,每个输出本质上都是一个字符串。当一个命令打印结果(数字或文本)时,它就是一个字符串。可以把 Bash 的输出想象成消息:你让一个命令做某件事,它就会发出一条消息。当我们想从函数返回一个值时,我们使用“echo”打印一条消息:

foo() {
  echo "Hello, World!"
}

如果命令(例如“echo”)失败,函数将隐式返回其退出代码——但你可以按自己的意愿返回自定义的退出代码。

foo() {
  echo "Hello, World!"
  return 0
}

让我们对结果做点什么,就像处理其他命令一样:

foo() {
  echo "Hello, World!"
  return 0 # This won't be processed by "tr".
}
foo | tr '[[:lower:]]' '[[:upper:]]'

"tr" 将把从 "foo" 回显的 "Hello, World!" 替换为大写字符串。这表明我们可以使用 "echo" 返回一个值,并像操作其他命令一样对其进行操作。

为了强调这一点,我们可以直接对任何命令的输出进行操作:

foo() {
  echo "Hello, World!" | tr '[[:lower:]]' '[[:upper:]]'
}
foo | sed 's/WORLD/UNIVERSE/'

这里的函数会回显“Hello, World!”,然后“tr”将其转换为大写。在函数之外,我们将结果通过管道传给“sed”,并将“WORLD”替换为“UNIVERSE。”


Bash 函数本质上是命令的可重用封装。你可以使用它们来定义复杂的命令管道,或者执行一些具体的工作并输出结果。它们接受参数并提供退出码,就像命令一样。

你通常会使用 Bash 函数来让你的 shell 使用更轻松;与其输入复杂的命令管道,不如创建一个函数并传入参数。你可以将它们放在你的 bashrc 文件中,这样它们就可以在 shell 中使用,就像其他命令一样。或者,你也可以在 PATH 中的脚本中使用它们。

相关文章