From 95ba5c4a5d9ba1d8dfa0027b03dc210b246989ae Mon Sep 17 00:00:00 2001 From: Glenn Jackman Date: Wed, 25 Dec 2024 15:37:29 -0500 Subject: [PATCH] review suggestions --- concepts/functions/.meta/config.json | 2 + concepts/functions/introduction.md | 63 +++++++++++++++++++++------- 2 files changed, 50 insertions(+), 15 deletions(-) diff --git a/concepts/functions/.meta/config.json b/concepts/functions/.meta/config.json index d418c130..3b60d370 100644 --- a/concepts/functions/.meta/config.json +++ b/concepts/functions/.meta/config.json @@ -3,6 +3,8 @@ "glennj" ], "contributors": [ + "kotp", + "IsaacG" ], "blurb": "Functions in bash programs." } diff --git a/concepts/functions/introduction.md b/concepts/functions/introduction.md index b08f4f80..24310422 100644 --- a/concepts/functions/introduction.md +++ b/concepts/functions/introduction.md @@ -1,24 +1,24 @@ # Functions Many Bash scripts are written in a strictly imperative style: execute one command, then execute another command, and so on. -But often you'll need to have a group of commands that conceptually perform a single purpose. +Sometimes you need to group together a sequence of commands that conceptually perform a single purpose. This is where _functions_ come in. ## Defining a Function -You declare a function is one of two ways. -The first is a "portable" style +You can declare a function in two ways. +The first is a "portable" style: ```bash -funcname () { COMMANDS; } +my_function () { COMMANDS; } ``` -This is the style that was created with the original Bourne shell. +This is the style from the original Bourne shell. Alternately, you can use the `function` keyword ```bash -function funcname { COMMANDS; } +function my_function { COMMANDS; } ``` There is no difference between the two styles. @@ -26,19 +26,23 @@ There is no difference between the two styles. ## Function Parameters Functions, once defined, act like any other command (builtin or not). -Like any command, you can provide _arguments_ for your functions. +Like any command, you can provide _arguments_ to your functions. Inside the functions, you access the arguments using the _positional parameters_, `$1`, `$2`, etc. (Recall, we learned about positional parameters in the [Variables][variables] concept.) ~~~~exercism/advanced The special parameter `$0` is not changed inside a function; it is still the name of the executing script. The currently executing function can access its name with the `$FUNCNAME` variable. + +See [3.4.2 Special Parameters][special] in the manual. + +[special]: https://www.gnu.org/software/bash/manual/bash.html#Special-Parameters ~~~~ ## Variables You can define variables inside a function. -If you declare the variables with the `local` command then the _scope_ of the variable is limited to the current function (and to any functions called from it). +If you declare the variables with the `local` command, the _scope_ of the variable is limited to the current function (and to any functions called by it). Otherwise, the variable is placed in the _global scope_. Local variables can have the same name as a global variable. @@ -59,14 +63,22 @@ myfunc echo "back in the global scope, $x == 5" ``` +This outputs + +```none +in the global scope, 5 == 5 +in my function, 100 == 100 +back in the global scope, 5 == 5 +``` + Inside a function, you can access variables from the _caller_'s scope. -That means you can use global variables, or local variables that are declared in some function that calls this one. +That means you can use global variables, as well as local variables that were declared in the caller (or in some function that calls the caller). ~~~~exercism/advanced Technically, "global" is not the right word to use. -Assignments to non-local variables will assign to the variable with that name that has been declared in some previous scope, up to the global scope. +To expand a variable in a function, Bash will traverse up the call stack, as far as the global scope, to find a function where that variable name has been declared. -This example is taken from the bash manual +This example is adapted from the [Shell Functions][man-funcs] section of the manual: ```bash func1() { @@ -89,6 +101,9 @@ The output is: In func2, var = func1 local In func2, var = global ``` + +Similarly, _assigning_ a value to a variable will assign it _in the scope where it was declared_. +This "action at a distance" can create hard-to-follow code, as it is not always obvious where a variable was assigned a value. ~~~~ ## Return Value @@ -116,6 +131,8 @@ else fi ``` +Using `return` with no arguments returns a zero status. + ~~~~exercism/note Note that the `check_password` function can be simplified to: @@ -132,11 +149,11 @@ check_password () { [[ $1 == "secret" ]]; } The return status of a function is just a number. How can a function produce output? -Your function simply emits output on standard output. +Your function can print to standard output. Use the familiar _command substitution_ to capture it: ```bash -d6 () { echo $(( 1 + RANDOM % 6 )); } +d6 () { echo "$(( 1 + RANDOM % 6 ))"; } die=$( d6 ) echo "You rolled a $die." @@ -144,13 +161,13 @@ echo "You rolled a $die." ### Using Both the Output and the Status -The exit status of a function is still available to use when you are capturing the output. +The exit status of a function is available to use even when you are capturing the output. ```bash roll () { local n=$1 if (( 4 <= n && n <= 20 )); then - echo $(( 1 + RANDOM % n )) # exit status is 0 + echo "$(( 1 + RANDOM % n ))" # exit status is 0 else return 1 fi @@ -169,5 +186,21 @@ fi Functions can call themselves recursively. By default, there is no limit to the depth of recursion. +An example: + +```bash +factorial() { + local n=$1 + if ((n <= 1)); then + echo "1" + else + local prev=$("$FUNCNAME" "$((n - 1))") + echo "$((n * prev))" + fi +} + +factorial 5 # => 120 +``` [variables]: https://exercism.org/tracks/bash/concepts/variables +[man-funcs]: https://www.gnu.org/software/bash/manual/bash.html#Shell-Functions