Here is my code:
function update_profile
{
echo "1. Update Name"
echo "2. Update Age"
echo "3. Update Gender"
echo "Enter option: "
read option
case $option in
1) update_name ;;
2) update_age ;;
3) update_gender ;;
esac
function update_name
{
echo "Enter new name: "
read name
}
}
Just want to make sure if it's possible to do this way. I do know that I can throw all the codes into the case, but it will be messy, so I was thinking to create a stand alone function, within a function, and to be called when needed to perform its commands.
4 Answers 4
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
-
thinking of using it so that i would not have so much codes in the case, will try your answer, thanksZac– Zac2015年01月17日 16:35:45 +00:00Commented Jan 17, 2015 at 16:35
-
1This is extremely useful for hiding a set of functions until needed.Matthaeus Gaius Caesar– Matthaeus Gaius Caesar2021年10月12日 01:47:27 +00:00Commented Oct 12, 2021 at 1:47
-
2Not sure I agree with the docs. For layout of code, when f2 will only be called from f1 this can be VERY useful. Just my twopennethjamiet– jamiet2022年03月17日 13:10:01 +00:00Commented Mar 17, 2022 at 13:10
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
-
1Note that in the Bourne shell (and all other Bourne-like shells except
bash
,yash
and recent versions ofposh
), you define a function by stickingfname()
in front of any command, not just compound ones. Last time I asked, nobody could tell me why POSIX changed that to compound only.bash
initially only supportedf() { ...; }
.Stéphane Chazelas– Stéphane Chazelas2015年01月20日 19:23:03 +00:00Commented Jan 20, 2015 at 19:23 -
@StéphaneChazelas - no kidding - never even tried that. Just checked it and it works with
dash
.posh
's parser doesn't handle even any compound command though:posh -c 'fname() if :; then echo not broken; fi; fname'
results in a syntax error. Alsozsh
screws upfname() { : ; } <redirect
unless the compound form is a subshell. Can I ask though - isn't this then worth an upvote? Or what improvement is necessary to make it so? You might also have missed upvoting your own contribution as quoted in jherran's answer, I think...mikeserv– mikeserv2015年01月20日 19:32:44 +00:00Commented Jan 20, 2015 at 19:32 -
1Though interesting, that's not really answering the question.Stéphane Chazelas– Stéphane Chazelas2015年01月20日 19:49:49 +00:00Commented Jan 20, 2015 at 19:49
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.
-
A more simple means of handling the
unset ...
stuff is just to dofn() ( : ... ; )
mikeserv– mikeserv2015年01月19日 04:51:25 +00:00Commented Jan 19, 2015 at 4:51 -
2
unset -f update_name
is not only simpler thanupdate_name () ( : ... ; )
or the less weirdupdate_name () { :; }
but more importantly it does what it's supposed to do, which is to undefine the function.update_name () (...)
defines a function which does nothing, so runningupdate_name
will do nothing instead of calling the external command by that name.Gilles 'SO- stop being evil'– Gilles 'SO- stop being evil'2015年01月19日 14:00:18 +00:00Commented Jan 19, 2015 at 14:00 -
2weird is a little silly. Moreover, what I meant was:
fn() ( fn2() { : define helper; }; do stuff; helper )
where the function is defined to run a subshell. There's nothing weird about that - a function is a named compound command (with new positionals) and not much else. So if a function is defined to run in a subshell from the start - you don't have to clean up its state when it closes.mikeserv– mikeserv2015年01月19日 14:49:25 +00:00Commented Jan 19, 2015 at 14:49 -
@Gilles 'SO- stop being evil' I was hoping that a function within a function would be visible only within that function, something like a local variable. Your suggestion to unset it before exiting the finction achieves exactly that effect. Thanks.Pourko– Pourko2021年03月31日 20:55:00 +00:00Commented Mar 31, 2021 at 20:55
I re-add my answer here that Jeff Schaller has deleted, to ensure there is an answer that shows there is indeed a use case and it is inherently wrong to say "this is not very useful" which is hinted by the accepted answer.
Here is comes once again (fixed to show what I mean by the answer):
It is "not very useful"? I think this is actually pretty cool.
I have project-specific initializer functions in my bashrc
/ bash_aliases
that initializes my environment to a given project.
Before that I just had all the aliases and functions (shortcuts like cdprojectname
and modulename_build
and such) globally defined. The problem with that is that you can easily get to use very long names to avoid name clashes after you work on a lot of projects.
So what I did is that I took the cdprojectname
(like cdsd
for project sd
) and that is a function that not only go to the project directory, but initialize all my developing environmant when it comes to the project.
It is kind of cool that you can make a function that defines shell functions in my opinon. Will need to test though if this works when I use my aliases from within vim
- yeah I checked and it does not work there with my tricks
- jaja whatever it does not even work if I issue
cdsd
from within vim now - still cool feature as it is!
As for comments on the answer
"Please don't add "thank you" as an answer. Once you have sufficient reputation, you will be able to vote up questions and answers that you found helpful. – Jeff Schaller"
Sorry I have no way to write to you, but I did not write what I wrote as a thank you, but I saw from earlier comments / answers that do not really highlight a possible use case for this, some even say "It is even possible to nest a function within another function, although this is not very useful."
This is not a thanksgiving, but wanting to point out that instead of "this is not really useful" this is actually a "really-really useful and neat feature" that would be great for other scripting/shell languages to have actually!
I posted my answer because highly disagree that this is not very useful, but the currently accepted answer hints that way ... please edit the way you want or just keep it otherwise there is false information/hint in the accepted answer I think.
-
Welcome to the site. Please note that the comment "although this is not very useful" is a quote from the corresponding page in the Linux Documentation Project, and not necessarily the opinion of the post author. It would rather seem that this line was included as reference for the statement "Yes, it's possible", and that the part you may have considered judgemental was included for completeness.AdminBee– AdminBee2020年04月23日 12:41:33 +00:00Commented Apr 23, 2020 at 12:41
-
Hmmm I indeed did not know about that it is quoted from Linux Documentation Project so it might be useful indeed to include here that it might have sounded judgemental even though it might have been just a quote. I still find it an awsome feature.prenex– prenex2020年04月24日 15:41:41 +00:00Commented Apr 24, 2020 at 15:41
-
I don't intend to contradict you on that; if you have found good use for it then it is a useful feature. Only sometimes heated discussions get started because contributors consider statements as unjustified, so I felt it was mandated to put the original comment on the usefulness "into context" ;)AdminBee– AdminBee2020年04月27日 07:15:38 +00:00Commented Apr 27, 2020 at 7:15