Is it possible to add a function within a function?
Yes, it's possible.
It is even possible to nest a function within another function, although this is not very useful.
f1 ()
{
f2 () # nested
{
echo "Function \"f2\", inside \"f1\"."
}
}
f2 # Gives an error message.
# Even a preceding "declare -f f2" wouldn't help.
echo
f1 # Does nothing, since calling "f1" does not automatically call "f2".
f2 # Now, it's all right to call "f2",
#+ since its definition has been made visible by calling "f1".
# Thanks, S.C.
Source: The Linux Documentation Project
You can define a function anywhere the shell is expecting a command, including in a function. Note that the function is defined at the moment the shell executes its definition, not when the shell parses the file. So your code won't work if the user chooses option 1 the first time update_profile
is executed, because when update_name
is called in the case
statement, the definition of the function update_name
won't have been executed yet. As soon as the function update_profile
has been executed once, the function update_name
will also be defined.
You need to move the definition of update_name
before the point where it is used.
Defining update_name
inside update_profile
isn't particularly useful. It does mean that update_name
won't be defined until the first time update_profile
is executed, but it will remain available afterwards. If you want update_name
to be available only inside update_profile
, define the function inside it and call unset -f update_name
before returning from the function. You won't really gain anything by doing that though, compared to doing the simple thing and defining all functions globally.
Yes, and this becomes clearer when you consider what a shell function really is.
For POSIX-compliant shells a function definition is standardized thus:
2.9.5 Function Definition Command
- A function is a user-defined name that is used as a simple command to call a compound command with new positional parameters. A function is defined with a "function definition command"... as follows:
fname() compound-command[io-redirect ...]
The function is named
fname
... The implementation shall maintain separate name spaces for functions and variables.The argument compound-command represents a compound command, as described in Compound Commands.
- When the function is declared, none of the expansions in Word Expansions shall be performed on the text in
compound-command
or<<&io-redirect&>
; all${expansions}
shall be performed as normal each time the function is called. Similarly, the optional<<&io-redirect&>
redirections and anyvariable=assignments
withincompound-command
shall be performed during the execution of the function itself, not the function definition. See Consequences of Shell Errors for the consequences of failures of these operations on interactive and non-interactive shells.
- When the function is declared, none of the expansions in Word Expansions shall be performed on the text in
And so, at its heart, a shell function named fname
is a literal string composed of at least one compound command that the shell will call up from memory and execute in place of fname
when it occurs in input in command position - which means wherever a cmd will do. This definition opens a lot of possibilities for the use of a function in a POSIX shell. Either of the following is acceptable:
fn() {
command; list;
fn() { : redefines itself upon first execution; }
}
...and...
fn() {
helper1() { : defines another function which it can call; }
helper2() { : and another; }
helper1 "$@" | helper2 "$@" #processes args twice over pipe
command "$@"; list; #without altering its args
}
But that is a small example. If you consider the meaning of compound command you might begin to see that the conventional fn() { : cmds; }
form is only one way a function can work. Consider some different kinds of compound commands:
{ compound; list; of; commands;} <>i/o <i >o
(subshelled; compound; list; of; commands) <>i/o <i >o
if ...; then ...; fi <>i/o <i >o
case ... in (...) ...;; esac <>i/o <i >o
for ... [in ... ;] do ...; done <>i/o <i >o
(while|until) ...; do ....; done <>i/o <i >o
And others besides. Any one of the above should work like...
fname() compound list
...and from among those any that can be nested when not assigned as a function can still be nested even if defined as a command.
Here's one way I might write your function:
update_prof(){
cat >&3
read "${2-option}" <&3
case "${1-$option}" in
1) update_prof '' name ;;
2) update_prof '' age ;;
3) update_prof '' gender ;;
*) unset option ;;
esac
} <<-PROMPT 3<>/dev/tty
${1-
1. Update Name
2. Update Age
3. Update Gender
}
Enter ${2:-option}: $(
printf '\033%s' \[A @
)
PROMPT
Some notes about the above:
- The
read
in update is subject to IFS and backslash interpretation. Robustly it could beIFS= read -r "$1"
but I'm unsure how you wish those things to be interpreted. Look for other answers on this site for more and better information on that score. - The
printf '\033%s...
in the here-doc assumes/dev/tty
is linked to a VT100 compatible terminal, in which case the escapes used should keep the here-doc's final newline from displaying on-screen. Robustlytput
would be used. doman termcap
for more information there.- Best is the VT100 assumption is correct and you can do without either
printf
ortput
by entering the escape characters literally into the here-document like^V{esc}[A^V{esc}@
where^V
is a way of representing theCONTROL+V
key combination and{esc}
is your keyboard'sESC
key.
- Best is the VT100 assumption is correct and you can do without either
The above function will pull double duty depending on its parameter set - it will execute twice and re-evaluate its prompt only as required - and so it doesn't need a second function - because it can do both as long as it is initially called without parameters in the first place.
So if I run...
update_prof; printf %s\\n "$name"
The ensuing terminal activity looks like:
1. Update Name
2. Update Age
3. Update Gender
Enter option: 1
Enter name: yo mama
yo mama