Bash函数使用指南

时间:2020-01-09 10:37:22  来源:igfitidea点击:

Bash脚本可以被认为是学习编程语言的第一步。
我们可能处于任何IT流中,但是在职业生涯中会有一点要最终编写出第一个Shell脚本。
因此,让我们开始学习如何定义bash函数。

我编写本教程的目的是考虑到刚开始使用Shell脚本编写的绝对初学者。
对于经验丰富的用户,我们可以跳过该课程。

bash中的函数是什么。

  • function的定义不仅会针对bash进行更改,而且在所有编程语言中都将保持不变

  • 尽管函数上可能很少变化,所以我们只讨论shell函数

  • "函数"可视为命令,内部是多个命令,值,位置参数等的组合

  • 因此,我们将所有这些值和命令组合在一起,成为一个"函数",从而使我们更容易一起使用同一组命令

为什么需要使用函数。

让我们举一个简单的例子。
我们必须执行以下任务集:1.创建100个用户2.为各个用户分配不同的密码3.确保用户在首次登录时更改密码4.为用户分配单独的主目录

  • 我们有很多工作要做

  • 我们可以继续为每个任务编写单独的命令,或者

  • 我们定义一个函数,并将所有命令,变量放入一个函数中(或者我们可以创建多个函数)

  • 使用此单一函数,我们将能够执行所有任务

  • "仍然想知道为什么我们需要一个函数,为什么不只用简单的脚本来调用它呢。
    "

  • 是的,是的,我们可以用纯脚本调用这些命令,但是假设我们有10000多行代码,如果我们将其安排在单独的函数中,每个函数都能完成自己的工作,那会不会更容易

例如:1.一个用于创建用户和分配密码的函数2.另一个用于处理其主目录的函数3.最后一个用于确保用户在首次登录后更改其密码的函数

这都是非常理论性的,我将向我们展示一些"实时实践示例",以更好地理解。

我们如何定义bash函数。

在shell脚本中定义函数的基本syntax将是

<function_name> [()] {
	compound-command 
}

或者我们也可以使用

function <function_name> [()] {
	compound-command 
}

这些函数语法之间有什么区别。

我们一定想知道应该选择哪种语法编写第一个函数。

"一个衬里答案"是"我们可以使用上述语法中的任何一种",但我建议我们也阅读以下内容:

"详细答案"是:

  • function关键字最初与KSH Shell一起使用

  • 后来bash shell还引入了对function关键字的支持

  • 尽管bash手册页确实提到了将"括号"与"函数"一起使用,但在定义函数时没有理由使用"括号"。

  • 但是我们永远不知道,因为在将来的bash版本中可能会删除对()的支持

  • 因此,具有全局支持的shell脚本的" recommended"语法为

function <function_name> {
	compound-command 
	[redirection]
}

要了解有关不同语法及其行为的更多信息,可以在Unix Stack Exchange上阅读更多内容。

了解函数语法

  • 我们可以通过替换语法中的function_name来定义bash函数的名称。

  • 选择"函数"名称时没有"这样的限制"。
    它可以仅包含字母,数字和下划线,并且可以以字母或者下划线开头。

  • 大括号" {}"之间的块是放置命令的主要函数块

  • 与某些编程语言不同,使用分号没有这种限制。
    在每个命令之后

  • 但是,如果我们打算在同一行中编写两个不同的命令,则必须使用分号; ,例如," command 1;命令2`

# cat /tmp/shell_script.sh
  1 #!/bin/bash
  2
  3 function print_name {
  4   echo "My name is hynman"
  5 }
  6
  7 # Call your function
  8 print_name
  • 在第3行中,我们定义了function``print_name并以OPEN花括号{开始了我们的函数块。

  • 第4行包含要在function主体内执行的命令

  • 第5行包含CLOSE花括号}

  • 在"第8行"上,我们将仅通过写入"函数"名称来调用函数。
    无需特殊处理

这个函数只会在终端屏幕上执行echo"我的名字是hynman",该脚本的输出是:

# /tmp/shell_script.sh
My name is hynman

我们必须为此脚本chmod u + x/tmp/shell_script.sh提供可执行权限。

局部变量和全局变量

现在,既然我们了解bash函数,那么我们应该非常小心定义变量的方式。

为什么我需要脚本中的变量。

  • 强烈建议在编写任何脚本时尽量使用VARIABLES

  • 因为它避免了编写硬代码和重复值

  • 假设我们必须在脚本中的其他位置使用/tmp/dir1 /文件中的文件,而无需使用变量即可手动定义

  • 几天后,我们意识到需要将路径更改为/tmp/dir2/file,现在想象一下在脚本中的所有位置执行此任务,如果我们错过了即使在某个位置进行更新的操作,脚本也会失败

  • 因此,我们创建了一个名称为" FILE_PATH =/tmp/dir1/file"的变量,我将在脚本中使用此变量" $FILE_PATH"。
    现在,即使我们必须更改路径,也只需更新此变量即可。

了解"局部"变量和"全局"变量之间的区别很重要。
让我们看一下这个示例脚本:

1 #!/bin/bash
  2
  3 SOME_VAR="GLOBAL"
  4
  5 function print_name {
  6   SOME_VAR="LOCAL"
  7   echo "My name is hynman"
  8 }
  9
 10 # Check the value of VARIABLE before we call the function
 11 echo "Before calling function, SOME_VAR: $SOME_VAR"
 12
 13 # Call your function
 14 print_name
 15
 16 # Check the value of VARIABLE after we call the function
 17 echo "After calling function, SOME_VAR: $SOME_VAR"

该脚本的输出:

Before calling function, SOME_VAR: GLOBAL
My name is hynman
After calling function, SOME_VAR: LOCAL

"我们了解什么。
"

  • 第3行,我们定义了SOME_VAR,这将是我们的全局变量

  • "第6行"我们在函数print_name块内重新定义了" SOME_VAR",因为这仅限于"函数"块,因此将其视为局部变量

  • "第11行"我们检查SOME_VAR的值。
    在此阶段,由于尚未执行函数print_name,因此SOME_VAR的内容将为GLOBAL

  • "第14行"我们执行该函数,该函数还将在函数块内执行" SOME_VAR"变量

  • 在"第17行"中,我们重新检查" SOME_VAR"的内容,现在它将更改为" LOCAL",因为函数" print_name"已将其设置为全局变量。

  • 因此,在"第14行"之后,任何命令或者函数如果尝试使用" SOME_VAR",则此变量的值将为" LOCAL"。

用参数调用bash函数

  • 我们还可以选择将输入参数传递给bash函数。

  • "你为什么要问。
    "

  • 想象一下,我们正在尝试编写一个"函数"来比较两个整数,但是这些整数将由最终用户作为命令行参数提供

  • 那么函数将如何访问这些整数值进行比较。

  • 我们可以将它们作为脚本的输入传递,然后进一步传递给函数

  • 要在脚本中传递值,我们应该在"函数"旁边传递值,并以单个空格字符分隔

为此的"语法"将是:

function_name "[arg1]" "[arg2]" "[arg3]" ..

为了存储它,你还应该在你的function中收集这些输入值。
我们可以使用任何变量名称,我其中使用了ARG1ARG2ARG3

function function_name {
   ARG1=""
   ARG2=""
   ARG3=""
   
   COMMANDS
}

或者,我们可以将所有输入值传递到"单个数组/变量"中

function_name string1 string2 string3

要存储此值,我们还应该将函数内的这些输入值收集到"单个数组/变量"中

function function_name {
   ARG="$@"
   
   COMMANDS
}

将输入参数传递并存储到不同的变量中

在这个示例shell脚本中,我已经将三个输入字符串传递给我的bash函数pass_arg,并将这三个字符串存储到三个不同的变量中,然后打印该变量的内容。
"建议"将所有输入值放入反逗号"""中,前提是这些输入值中的任何一个包含空格。

#!/bin/bash
function pass_arg {
  # Collect Input Values
  ARG1=""
  ARG2=""
  ARG3=""
  # Print the variable content
  echo "First: $ARG1"
  echo "Second: $ARG2"
  echo "Third: $ARG3"
}
# Executing the function
pass_arg "argument1" "argument2" "argument3"

从该脚本的输出中,我们看到该脚本已成功地将所有三个输入值存储到三个不同的变量中:

First: argument1
Second: argument2
Third: argument3

将输入参数传递并存储到单个变量中

在下一个示例shell脚本中,我将所有输入值存储在单个VARIABLE中。
在这种情况下,我们不应该对单个字符串使用反逗号"",否则bash函数只会考虑第一个值" string1"。
我们可以将整个字符串列表放在诸如"" string1 string2 string3""这样的反向逗号下,也可以像在此脚本中所做的那样,将整个列表不带反向逗号传递给函数。

function pass_arg {
  # Collect Input Values
  ARG="$@"
  # Print the variable content
  echo "Input argument list: $ARG"
  echo "Variable Length: ${#ARG[@]}"
}
# Call your function
pass_arg string1 string2 string3

从该脚本的输出中,我们可以看到该函数已成功解析了输入值列表,并将其存储到单个变量中。
我还打印了可变长度,以便我们知道脚本未将其视为数组。

Input argument list: string1 string2 string3
Variable Length: 1

即使有3个字符串,每个字符串之间都用空格隔开,bash仍将其视为变量。

将输入参数传递并存储到数组中

要将输入值存储为ARRAY,必须确保两件事:1.不要在半冒号下传递输入值。
2.将输入值存储在括号内的bash函数中。

我建议还阅读如何在bash中使用4种简单方法将字符串拆分为数组

#!/bin/bash
function pass_arg {
  # Collect Input Argument and convert the VAR to ARRAY using parenthesis
  ARG=("$@")
  # Print the array content
  echo "Input argument list: $ARG"
  echo "Variable Length: ${#ARG[@]}"
}
# Call your function
pass_arg string1 string2 string3

该脚本的输出:

Input argument list: string1 string2 string3
Variable Length: 3

现在bash认为ARG是ARRAY,因为它可以从长度语法中识别出ARG中的三个不同的字符串。

让我也给我们一个"实际例子":

在shell脚本中,我们通常有很多echo语句,并且大多数时候我们还必须将这些echo语句存储到某些日志文件中,因此我们创建了一个function,它将echo语句发送到屏幕并发送相同的消息到日志文件,这样我们就不必重复两次相同的命令:

#!/bin/bash
# Define Log file variable
LOGFILE=/var/log/myapp.log
function log_content {
   # print on output
   echo "$*"
   
   # store the logs
   echo "$*" >> $LOFILE
}
# Execute the function
log_content "Starting the process"

我们还可以"增强"函数,以便"将日期和时间与主机名一起打印",同时将日志存储在日志文件中,就像我们在"/var/log/messages"中有消息的格式一样

#!/bin/bash
# Define Log file variable
LOGFILE=/var/log/myapp.log
function log_content {
   # print on output
   echo "$*"
   
   # store the logs with timestamp
   echo "`date +"%b %e %T"` `hostname` "$*" >> LOGFILE
}
# Execute the function
log_content "Starting the process"

因此,现在我们有了一个"函数",该函数可以获取输入值,然后使用它在屏幕上打印信息并将其存储在日志文件中。

从最终用户收集命令行参数并将其传递给函数

  • 我们还可以从最终用户那里收集输入作为命令行参数,然后将其作为输入传递给函数

  • 我们将输入值存储到单个变量中,即" ARG"

  • 然后我们将这个变量传递给函数

  • 接下来,在函数内部,我们需要再次收集并将其存储在另一个变量" INPUT_ARG"中,以进行进一步处理

  • 现在我们可以在函数中使用INPUT_ARG了

#!/bin/bash
ARG="$*"
function pass_arg {
  INPUT_ARG=""
  echo "Input Argument: $INPUT_ARG"
}
# Call your function
pass_arg "$ARG"

然后我们以输入值hynman执行脚本

# /tmp/shell_script.sh hynman
Input Argument: hynman

"函数"已经收集了输入值,将其存储在" ARG"中,然后将该" $ARG"传递给函数。
该函数读取此输入并将其存储在INPUT_ARG中,之后将其用作$INPUT_ARG

在函数中使用返回值

  • 使用bash函数时,我们必须熟悉函数内部使用的return关键字

  • 我们不能在函数块之外使用返回

  • 用外行术语来说," return"被用作" exit"语句的替代。
    (尽管两者都不相同)

  • return的优点是我们可以选择从function中退出,而无需实际退出脚本。

  • 如果我们已经听说过" continue"," break"(如我在较早的文章中用" for"和" while"循环所解释的),那么我们可能会对" return"感到困惑,因为"听起来很熟悉"。

  • continuebreak仅用于退出循环条件,但是return的另一个优点是在退出循环时也传递退出代码

让我们在shell脚本中使用它来"正确理解用法":

  • 第3行,我们定义了一个函数return_value

  • 第5行,我们正在使用echo在屏幕上打印一个简单的语句

  • 6-10行我们正在检查echo命令的退出状态

  • 在"第7行"处,如果退出状态为0,则将调用"返回0"

  • 如果退出状态为非零,则在"第9行"处,将调用"返回1"

  • 在"第13行"中,我们在同一"函数"块中定义了另一个echo语句。

  • 现在,"我所做的错误"是,我还在"返回"值后面加上了"回声"。
    此部分将永远不会被调用,因此请注意这种情况。

  • 注意如果循环触发了返回值,那么将不执行`函数'的其余部分

  • "第18行"我们执行"函数",然后再次在"第19行"中检查"函数"的存在状态

  • "第21行"我们打印一个带有函数退出代码的语句。
    该退出代码应与function块中的return代码匹配。

1 #!/bin/bash
  2
  3 function return_values {
  4
  5   echo "We do something"
  6   if [ $? == 0 ];then
  7      return 0
  8   else
  9      return 1
 10   fi
 11
 12   # This will not be called as the function will exit at return stage above
 13   echo "Good Day"
 14
 15 }
 16
 17 # Call your function
 18 return_values
 19 exit_status=$?
 20
 21 echo "The exit status of function is: $exit_status"

现在我们执行脚本,脚本的退出代码与函数的返回值相同

We do something
The exit status of function is: 0

我们知道我们在上面的脚本中犯了一些错误,`编写上面的脚本的正确方法是:

#!/bin/bash
function return_values {
  echo "We do something"
  if [ $? != 0 ];then
     return 1
  fi
  # This will not be called as the function will exit at return stage above
  echo "Good Day"
  return 0
}
# Call your function
return_values
exit_status=$?
echo "The exit status of function is: $exit_status"

因此,现在我们不从第一个命令退出,而是添加一个否定检查。
只有在第一个echo语句失败时bash函数才会退出,否则我们将同时获得echo语句

该脚本的输出为:

We do something
Good Day
The exit status of function is: 0