Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Concept: functions #715

Merged
merged 4 commits into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
glennj marked this conversation as resolved.
Show resolved Hide resolved

```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.
glennj marked this conversation as resolved.
Show resolved Hide resolved

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