Skip to content

Commit

Permalink
Concept: functions
Browse files Browse the repository at this point in the history
  • Loading branch information
glennj committed Dec 27, 2024
1 parent 001340e commit 0626d48
Show file tree
Hide file tree
Showing 6 changed files with 452 additions and 7 deletions.
12 changes: 5 additions & 7 deletions concepts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,17 @@ The [plan](http://forum.exercism.org/t/bash-syllabus-planning/11952)
- for elem in elements ...
- arithmetic for

6. conditionals 2
6. pipelines and command lists
- boolean operators `&&` `||`
- how `A && B || C` != `if A; then B; else C; fi`

7. arrays

7. functions

8. arrays
- numeric and associative
- iteration
- namerefs

8. functions

9. pipelines and subshells

...

- brace expansions and how it's different from patterns `/path/to/{foo,bar,baz}.txt`
Expand Down
10 changes: 10 additions & 0 deletions concepts/functions/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"authors": [
"glennj"
],
"contributors": [
"kotp",
"IsaacG"
],
"blurb": "Functions in Bash programs."
}
211 changes: 211 additions & 0 deletions concepts/functions/about.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
# Functions

Many Bash scripts are written in a strictly imperative style: execute one command, then execute another command, and so on.
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 define a function like this:

```bash
my_function () {
COMMANDS
}
```

The empty set of parentheses simply denotes that you are defining a function.
Nothing goes inside them.

## Function Parameters

Functions, once defined, act like any other command (builtin or not).
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, 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.
In that case, the local variable _temporarily_ overrides the global one, and the global value is restored when the function returns.

```bash
x=5

myfunc () {
local x=100
echo "in my function, $x == 100"
}

echo "in the global scope, $x == 5"

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, 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.
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 adapted from the [Shell Functions][man-funcs] section of the manual:
```bash
func1() {
local var='func1 local'
func2
}
func2() {
echo "In func2, var = $var"
}
var=global
func1
func2
```
The output is:
```none
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 Values

A function, like any command, has an _exit status_.
By default, the status of a function is the exit status of the _last command executed_.

You can use the `return` command to return from a function with a specific exit status.

```bash
check_password () {
if [[ $1 == "secret" ]]; then
return 0
else
return 1
fi
}

read -sp "Enter your password: " pass

if check_password "$pass"; then
echo "Correct!"
else
echo "Wrong password."
fi
```

Using `return` with no arguments returns the status of the last command executed.

~~~~exercism/note
Note that the `check_password` function can be simplified to:
```bash
check_password () { [[ $1 == "secret" ]]; }
```
1. The `[[...]]` conditional construct has an exit status: `0` for "true", `1` for "false.
2. The `{...}` grouping construct must have either a newline or a semicolon before the ending brace.
~~~~

## Function Output

The return status of a function is just a number.
How can a function produce output?

Your function can print to standard output.
Use the familiar _command substitution_ to capture it:

```bash
d6 () { echo "$(( 1 + RANDOM % 6 ))"; }

die=$( d6 )
echo "You rolled a $die."
```

### Using Both the Output and the Status

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
else
return 1
fi
}

read -p "How many faces does your die have? " faces
if die=$( roll "$faces" ); then
echo "You rolled a $die."
else
echo "I can't roll a die with $faces faces."
fi
```

## Recursion

Functions can call themselves recursively.
By default, there is no limit to the depth of recursion.

An example:

```bash
fibonacci() {
local n=$1
if (( n <= 1 )); then
echo "1"
else
local a=$(fibonacci "$(( n - 1 ))")
local b=$(fibonacci "$(( n - 2 ))")
echo "$(( a + b ))"
fi
}

for i in {1..10}; do fibonacci "$i"; done
# => 1
# => 2
# => 3
# => 5
# => 8
# => 13
# => 21
# => 34
# => 55
# => 89
```

[variables]: https://exercism.org/tracks/bash/concepts/variables
[man-funcs]: https://www.gnu.org/software/bash/manual/bash.html#Shell-Functions
Loading

0 comments on commit 0626d48

Please sign in to comment.