Yes, and this becomes clearer when you consider what a shell function really is.
For POSIX-compliant shells a function definition is standardized thus:
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 be IFS= 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. Robustly tput
would be used. do man termcap
for more information there.
- Best is the VT100 assumption is correct and you can do without either
printf
or tput
by entering the escape characters literally into the here-document like ^V{esc}[A^V{esc}@
where ^V
is a way of representing the CONTROL+V
key combination and {esc}
is your keyboard's ESC
key.
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