Contents

从命令行中运行脚本中的函数

1. 概述

在 shell 编程中,当我们创建函数时,通常我们将它们放在 shell 脚本中。shell 脚本中的函数包含一组我们可以重用的命令。

在本教程中,我们将学习如何从 shell 脚本文件外部调用这些函数。

2. 示例 Shell 脚本文件

在我们深入讨论如何从脚本外部调用函数之前,让我们尝试通过一个示例来理解这个问题。

让我们创建一个 shell 脚本文件myScript.sh

$ cat myScript.sh
#!/bin/bash
#A variable
VAR="VAR inside the script"
# print INFO message to stdout
# Argument: 
#    $1: INFO message to print
log_info() {
    local MSG="$1"
    printf "%s - [INFO] %s\n" "$(date)" "$MSG"
}
# print ERROR message to stderr
# Argument: 
#    $1: ERROR message to print
log_error() {
    local MSG="$1"
    printf "%s - [ERROR] %s\n" "$(date)" "$MSG" >&2
}

在 myScript.sh文件中,我们定义了一个变量VAR和两个函数:*log_info()*和 log_error()。我们可以调用脚本文件中的函数。

现在,使用heredoc 功能,让我们在myScript.sh中添加两行来调用这两个函数,然后,让我们执行脚本:

$ cat <<EOF>> myScript.sh 
heredoc> log_info "This is an INFO message."
heredoc> log_error "This is an ERROR message."
heredoc> EOF
$ ./myScript.sh 
Mon 24 Aug 2020 10:27:09 PM CEST - [INFO] This is an INFO message.
Mon 24 Aug 2020 10:27:09 PM CEST - [ERROR] This is an ERROR message.

而且,正如我们所见,这两个函数都被调用了——到目前为止,一切都很好。 现在,让我们看看如何从myScript.sh 外部调用函数。

3. 获取脚本

获取脚本文件 是调用脚本文件中定义的函数的最直接方法。

我们可以将源命令和函数调用与&& ”运算符 结合起来构建一个单行:

$ . myScript.sh && log_info "I am an INFO message outside the script file"
Mon 24 Aug 2020 10:47:34 PM CEST - [INFO] I am an INFO message outside the script file

如示例所示,在获取脚本文件后,我们可以从命令行调用函数。

4. 采购脚本的问题

我们已经看到,获取一个脚本文件来调用其中的函数非常简单。但是,在某些情况下,我们不能直接获取脚本文件。接下来,让我们看看几个采购存在问题的案例。

4.1. 变量被覆盖

在我们获取脚本后,如果变量名相同,它会覆盖当前 shell 中设置的变量值:

$ VAR="A very important message to print"; . myScript.sh && log_info "$VAR"
Mon 24 Aug 2020 11:05:16 PM CEST - [INFO] VAR inside the script

如上例所示,在我们获取脚本后,变量 VAR的值已被脚本中的值覆盖。这是因为当我们 source 一个脚本时,该脚本将在当前 shell 中执行。因此,我们没有得到预期的信息日志。

4.2. 脚本中的所有命令都被执行

采购脚本的另一个副作用是它将执行脚本中的所有命令。让我们向myScript.sh添加一些命令:

$ cat myScript.sh
#!/bin/bash
VAR="VAR inside the script"
log_info() ...
log_error() ...
# simulate some command to send emails to all users
echo "Sending emails to all email addresses in users.txt..."
echo "Done"
# log
log_info "Emails have been sent to all users."

新添加的命令模拟向所有用户发送电子邮件。

让我们看看如果我们获取文件并调用*log_info()*函数会发生什么:

$ . myScript.sh && log_info "An INFO message outside the script"
Sending emails to all email addresses in users.txt...
Done
Mon 24 Aug 2020 11:26:48 PM CEST - [INFO] Emails have been sent to all users.
Mon 24 Aug 2020 11:26:48 PM CEST - [INFO] An INFO message outside the script

如输出所示,已打印出预期的日志输出。但是,发送电子邮件的命令也已执行。当我们只想在脚本中调用*log_info()*函数来写入日志消息时,我们显然不希望此操作向所有用户发送电子邮件。

如果我们能找到一种方法来调用脚本中所需的函数而无需获取脚本,那就更好了。

5. 不使用脚本调用函数

通常,如果一个 shell 脚本包含函数,函数声明在那些执行具体工作的命令之前,例如在我们的myScript.sh中发送电子邮件的命令。

我们可以通过在我们想要公开的函数和脚本中包含的其他命令之间添加一些函数调用处理代码来修改脚本。

5.1. 扩展脚本以处理外部函数调用

一个例子将帮助我们更容易地理解这种方法。我们先来看看修改后的脚本文件,以及它是如何处理外部函数调用的:

$ cat myScript.sh 
#!/bin/bash
VAR="VAR inside the script"
log_info() ...
log_error() ...
case "$1" in
    "") ;;
    log_info) "[[email protected]](/cdn_cgi/l/email_protection)"; exit;;
    log_error) "[[email protected]](/cdn_cgi/l/email_protection)"; exit;;
    *) log_error "Unkown function: $1()"; exit 2;;
esac
# simulate to send emails to all users
....

我们在函数声明和电子邮件发送命令之间添加了一个case语句。case语句负责管理来自外部的函数调用。

它允许我们执行命令*./myScript.sh function_name arguments来调用myScript.sh*中定义的函数,而无需获取整个脚本:

$ ./myScript.sh log_info "An INFO message outside the script"
Tue 25 Aug 2020 02:01:08 PM CEST - [INFO] An INFO message outside the script

如示例所示,我们看到了预期的日志。此外,不会执行发送电子邮件的命令。

此外,由于我们不获取脚本,因此当前 shell 中的变量不会被脚本覆盖:

$ VAR="An ERROR message outside the script"; ./myScript.sh log_error "$VAR"
Tue 25 Aug 2020 02:04:25 PM CEST - [ERROR] An ERROR message outside the script

此外,如果我们尝试调用一个不存在的函数,我们会看到错误消息:

$ ./myScript.sh log_warn "A WARN message outside the script"
Tue 25 Aug 2020 02:07:42 PM CEST - [ERROR] Unkown function: log_warn()

最后,case语句不会影响脚本的正常执行:

$ ./myScript.sh 
Sending emails to all email addresses in users.txt...
Done
Tue 25 Aug 2020 02:13:54 PM CEST - [INFO] Emails have been sent to all users.

5.2. 这个怎么运作

现在,让我们了解case语句是如何处理外部函数调用的。

当我们使用命令*./myScript.sh function_name 参数调用函数时,* function_name成为脚本的第一个参数。因此,我们可以检查case语句中的*“$1”*变量:

  • “”);;– 如果 $1参数为空,则它是脚本的正常执行,而不是外部函数调用。因此,我们在case语句之外继续执行
  • log_info)“ [电子邮件保护] ”;出口;; – 如果一个函数名匹配,我们调用所有参数匹配的函数并在函数执行后退出执行
  • *) log_error “未知函数:$1()”;2号出口;;– 如果我们找不到匹配的函数名,我们认为调用者试图调用一个无效的函数。因此,我们打印错误日志并退出

6. 在单独的脚本中组织常用函数

我们了解到,检查脚本中的*$1*变量是路由外部函数调用的一种方法。但是,当我们编写 shell 脚本时,如果其他脚本可以重用某些函数,我们应该考虑将这些函数移动到一个单独的脚本中,并从我们当前的脚本中获取它

这使得功能的共享更加容易和清洁。

例如,我们可以将 myScript.sh分成两个脚本,logger.shsendEmail.sh

$ cat logger.sh 
#!/bin/bash
log_info() {
    local MSG="$1"
    printf "%s - [INFO] %s\n" "$(date)" "$MSG"
}
log_error() {
    local MSG="$1"
    printf "%s - [ERROR] %s\n" "$(date)" "$MSG" >&2
}
$ cat sendEmail.sh 
#!/bin/bash
source logger.sh
VAR="Inside Script"
# simulate to send emails to all users
echo "Sending emails to all email addresses in users.txt..."
echo "Done"
# log
log_info "Emails have been sent to all users."

这样,如果我们想调用日志函数,我们来源 logger.sh

$ . logger.sh && log_info "I am an INFO message outside the script file" 
Tue 25 Aug 2020 10:47:34 PM CEST - [INFO] I am an INFO message outside the script file