Skip to content

Commit c8298b0

Browse files
authored
Concept: functions (#715)
* Concept: functions * reword sentence about local variable "overwriting" global variable * Use wording from the manual * add FUNCNAME and FUNCNEST notes
1 parent 9413a2a commit c8298b0

File tree

6 files changed

+520
-7
lines changed

6 files changed

+520
-7
lines changed

concepts/README.md

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,19 +45,17 @@ The [plan](http://forum.exercism.org/t/bash-syllabus-planning/11952)
4545
- for elem in elements ...
4646
- arithmetic for
4747

48-
6. conditionals 2
48+
6. pipelines and command lists
4949
- boolean operators `&&` `||`
5050
- how `A && B || C` != `if A; then B; else C; fi`
51-
52-
7. arrays
51+
52+
7. functions
53+
54+
8. arrays
5355
- numeric and associative
5456
- iteration
5557
- namerefs
5658

57-
8. functions
58-
59-
9. pipelines and subshells
60-
6159
...
6260

6361
- brace expansions and how it's different from patterns `/path/to/{foo,bar,baz}.txt`

concepts/functions/.meta/config.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"authors": [
3+
"glennj"
4+
],
5+
"contributors": [
6+
"kotp",
7+
"IsaacG"
8+
],
9+
"blurb": "Functions in Bash programs."
10+
}

concepts/functions/about.md

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
# Functions
2+
3+
Many Bash scripts are written in a strictly imperative style: execute one command, then execute another command, and so on.
4+
Sometimes you need to group together a sequence of commands that conceptually perform a single purpose.
5+
This is where _functions_ come in.
6+
7+
## Defining a Function
8+
9+
You define a function like this:
10+
11+
```bash
12+
my_function () {
13+
COMMANDS
14+
}
15+
```
16+
17+
The empty set of parentheses simply denotes that you are defining a function.
18+
Nothing goes inside them.
19+
20+
## Function Parameters
21+
22+
Functions, once defined, act like any other command (builtin or not).
23+
Like any command, you can provide _arguments_ to your functions.
24+
Inside the functions, you access the arguments using the _positional parameters_, `$1`, `$2`, etc.
25+
(Recall, we learned about positional parameters in the [Variables][variables] concept.)
26+
27+
~~~~exercism/advanced
28+
The special parameter `$0` is not changed inside a function; it is still the name of the executing script.
29+
The currently executing function can access its name with the `$FUNCNAME` variable.
30+
31+
See [3.4.2 Special Parameters][special] in the manual.
32+
33+
[special]: https://www.gnu.org/software/bash/manual/bash.html#Special-Parameters
34+
~~~~
35+
36+
## Variables
37+
38+
You can define variables inside a function.
39+
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).
40+
Otherwise, the variable is placed in the _global scope_.
41+
42+
Local variables can have the same name as a global variable.
43+
In that case, the local variable "shadows" the global variable.
44+
For instance, a local variable declared in a function hides a global variable of the same name: references and assignments refer to the local variable, leaving the global variable unmodified.
45+
When the function returns, the global variable is once again visible.
46+
47+
```bash
48+
x=5
49+
50+
myfunc () {
51+
local x=100
52+
echo "in my function, $x == 100"
53+
}
54+
55+
echo "in the global scope, $x == 5"
56+
57+
myfunc
58+
59+
echo "back in the global scope, $x == 5"
60+
```
61+
62+
This outputs
63+
64+
```none
65+
in the global scope, 5 == 5
66+
in my function, 100 == 100
67+
back in the global scope, 5 == 5
68+
```
69+
70+
Inside a function, you can access variables from the _caller_'s scope.
71+
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).
72+
73+
~~~~exercism/advanced
74+
Technically, "global" is not the right word to use.
75+
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.
76+
77+
This example is adapted from the [Shell Functions][man-funcs] section of the manual:
78+
79+
```bash
80+
func1() {
81+
local var='func1 local'
82+
func2
83+
}
84+
85+
func2() {
86+
echo "In func2, var = $var"
87+
}
88+
89+
var=global
90+
func1
91+
func2
92+
```
93+
94+
The output is:
95+
96+
```none
97+
In func2, var = func1 local
98+
In func2, var = global
99+
```
100+
101+
Similarly, _assigning_ a value to a variable will assign it _in the scope where it was declared_.
102+
This "action at a distance" can create hard-to-follow code, as it is not always obvious where a variable was assigned a value.
103+
~~~~
104+
105+
~~~~exercism/advanced
106+
The call stack can be examined using [the `FUNCNAME` array variable][funcname].
107+
108+
[funcname]: https://www.gnu.org/software/bash/manual/bash.html#index-FUNCNAME
109+
~~~~
110+
111+
## Return Values
112+
113+
A function, like any command, has an _exit status_.
114+
By default, the status of a function is the exit status of the _last command executed_.
115+
116+
You can use the `return` command to return from a function with a specific exit status.
117+
118+
```bash
119+
check_password () {
120+
if [[ $1 == "secret" ]]; then
121+
return 0
122+
else
123+
return 1
124+
fi
125+
}
126+
127+
read -sp "Enter your password: " pass
128+
129+
if check_password "$pass"; then
130+
echo "Correct!"
131+
else
132+
echo "Wrong password."
133+
fi
134+
```
135+
136+
Using `return` with no arguments returns the status of the last command executed.
137+
138+
~~~~exercism/note
139+
Note that the `check_password` function can be simplified to:
140+
141+
```bash
142+
check_password () { [[ $1 == "secret" ]]; }
143+
```
144+
145+
1. The `[[...]]` conditional construct has an exit status: `0` for "true", `1` for "false.
146+
2. The `{...}` grouping construct must have either a newline or a semicolon before the ending brace.
147+
~~~~
148+
149+
## Function Output
150+
151+
The return status of a function is just a number.
152+
How can a function produce output?
153+
154+
Your function can print to standard output.
155+
Use the familiar _command substitution_ to capture it:
156+
157+
```bash
158+
d6 () { echo "$(( 1 + RANDOM % 6 ))"; }
159+
160+
die=$( d6 )
161+
echo "You rolled a $die."
162+
```
163+
164+
### Using Both the Output and the Status
165+
166+
The exit status of a function is available to use even when you are capturing the output.
167+
168+
```bash
169+
roll () {
170+
local n=$1
171+
if (( 4 <= n && n <= 20 )); then
172+
echo "$(( 1 + RANDOM % n ))" # exit status is 0
173+
else
174+
return 1
175+
fi
176+
}
177+
178+
read -p "How many faces does your die have? " faces
179+
if die=$( roll "$faces" ); then
180+
echo "You rolled a $die."
181+
else
182+
echo "I can't roll a die with $faces faces."
183+
fi
184+
```
185+
186+
## Recursion
187+
188+
Functions can call themselves recursively.
189+
By default, there is no limit to the depth of recursion.
190+
191+
An example:
192+
193+
```bash
194+
fibonacci() {
195+
local n=$1
196+
if (( n <= 1 )); then
197+
echo "1"
198+
else
199+
local a=$(fibonacci "$(( n - 1 ))")
200+
local b=$(fibonacci "$(( n - 2 ))")
201+
echo "$(( a + b ))"
202+
fi
203+
}
204+
205+
for i in {1..10}; do fibonacci "$i"; done
206+
# => 1
207+
# => 2
208+
# => 3
209+
# => 5
210+
# => 8
211+
# => 13
212+
# => 21
213+
# => 34
214+
# => 55
215+
# => 89
216+
```
217+
218+
~~~~exercism/advanced
219+
The recursion depth can be controlled with [the `FUNCNEST` variable][funcnest].
220+
221+
```bash
222+
bash -c '
223+
recur() {
224+
echo $1
225+
recur $(($1 + 1))
226+
}
227+
FUNCNEST=5
228+
recur 1
229+
'
230+
```
231+
232+
```none
233+
1
234+
2
235+
3
236+
4
237+
5
238+
environment: line 1: recur: maximum function nesting level exceeded (5)
239+
```
240+
241+
[funcnest]: https://www.gnu.org/software/bash/manual/bash.html#index-FUNCNEST
242+
~~~~
243+
244+
[variables]: https://exercism.org/tracks/bash/concepts/variables
245+
[man-funcs]: https://www.gnu.org/software/bash/manual/bash.html#Shell-Functions

0 commit comments

Comments
 (0)