7

I am reading this post on using functions in bash shell variables. I observe that to use shell functions, one has to export them and execute them in a child shell as follows:

$ export foo='() { echo "Inside function"; }' $ bash -c 'foo' Inside function 

I want to ask whether it is possible for functions to be defined in bash shell variables such that they can be executed in the current shell without using export & running new shell ?

4
  • 3
    What's wrong with just foo() { echo "Inside function"; }; foo?
    – cuonglm
    CommentedSep 30, 2015 at 19:01
  • No I meant if it was possible to define it and run it in the same shell but at a later time (say after running a few other commands)
    – Jake
    CommentedSep 30, 2015 at 19:02
  • 1
    Above syntax is exactly define it and run it later. Just define function as-is, you only need to export if you want it to available in sub-shell. And note that your example won't work anymore after shellshock fixed. You have to define function then export it explicitly.
    – cuonglm
    CommentedSep 30, 2015 at 19:05
  • If you get the function a name. $ function bar() { echo "Inside function"; } $ foo="bar" $ ${foo} Inside function $CommentedSep 30, 2015 at 19:21

2 Answers 2

9

No, you can't.

If you want to use a function in current shell, just defining it then use it later:

$ foo() { echo "Inside function"; } $ foo Inside function 

Before Shellshock day, you can store function inside a variable, export variable and using function in sub-shell, because bash support exporting function, bash will put function definition in environment variable like:

foo=() { echo "Inside function" } 

then interpret the function by replace = with space. There's no intention for putting function in variable and refer to variable will executing the function.

Now, after Stéphane Chazelas found the bug, that's feature was removed, no function definition stored in plain environment variable anymore. Now exported functions will be encoded by appending prefix BASH_FUNC_ and suffix %% to avoid clashed with environment variables. bash interpreter can now determine whether or not they are shell function regardless of variables's content. You also need to define the function and export it explicitly to use it in sub-shell:

$ foo() { echo "Inside function"; } $ export -f foo $ bash -c foo Inside function 

Anyway, if your example worked with your current bash, then you are using a vulnerable version.

26
  • 2
    BTW: This feature was always in conflict with the POSIX standard.
    – schily
    CommentedSep 30, 2015 at 19:25
  • 1
    I recently made the export, unexport and unset builtins from the Bourne Shell POSIX compliant and somewhere in these builtins is the explanation, I believe in export.
    – schily
    CommentedSep 30, 2015 at 19:41
  • 1
    @cuonglm Is there a man page that describes how to use functions in bash ?
    – Jake
    CommentedOct 1, 2015 at 3:01
  • 1
    @Jake - man bash. when you get it open, press the / key and use a regex like ^[[:space:]]*Func to find a line that starts with that phrase. Press n to skip through matches to your search. and judging by your question, you might want to have a look at alias as well.
    – mikeserv
    CommentedOct 1, 2015 at 3:11
  • 1
    @Jake: The semicolon is just a terminator, used to separated shell token, here's the command and the }. bash documentation have a details part about functions.
    – cuonglm
    CommentedOct 1, 2015 at 3:12
6
foo='foo(){ echo "Inside Function"; }' bash -c "$foo; foo" 

Inside Function 

A function, after all, is simply a named, pre-parsed, static command string stored in the shell's memory. And so the only real way to do it is by evaluating strings as code, which is exactly what the shell does each time you call your function.

It never was very different before, and it still isn't. The devs setup convenience methods for doing so, and they try to safeguard possible security holes as they might, but the fact of the matter is that the more convenient a thing becomes, the more likely it is you know less about it than you should.

There are many options available to you when it comes to importing data from parent shells to subshells. Grow familiar with them, become confident enough that you can identify their potential uses and their potential weaknesses, and use them sparingly but effectively.


I suppose I can elaborate on one or two of these while I'm at it. Probably the best way to pass static commands from one shell to another (whether it be up or down a subshelled command chain) is alias. alias is useful in this capacity because it is POSIX-specified to report its defined commands safely:

The format for displaying aliases (when no operands or only name operands are specified) shall be:

  • "%s=%s\n", name, value

The value string shall be written with appropriate quoting so that it is suitable for reinput to the shell. See the description of shell quoting in Quoting.

For example:

alias foo="foo(){ echo 'Inside Function'; }" alias foo 

foo='foo(){ echo '\''Inside Function'\''; }' 

See what it does with the quotes, there? The exact output is shell dependent, but, in general, you can rely on a shell reporting its alias definitions in a manner that is eval safe for reinput to the same shell. Mixing and matching source/destination shells might be iffy in some cases, though.

To demonstrate a cause for exercising such caution:

ksh -c 'alias foo=" foo(){ echo \"Inside Function\" }" alias foo' 

foo=$'\nfoo(){\n\techo "Inside Function"\n}' 

Also note that the alias namespace represents one of three independent namespaces you can generally expect to find fully fleshed out in practically any modern shell. Typically shells provide you these three namespaces:

  • Variables: name=value outside of lists

    • These may also be defined with some builtins such as export, set, readonly in some cases.
  • Functions: name() compound_command <>any_redirects in command position

    • The shell is specified not to expand or interpret any portion of the function definition it might find in the command at definition time.
      • This prohibition does not include aliases, though, as these are expanded during the parsing process of a shell's command read, and so alias values, if the alias is found in correct context, are expanded into the function definition command when found.
    • As mentioned, the shell saves the parsed value of the definition command as a static string in its memory, and it performs all expansions and redirections found within the string only when the function name is later found post-parse as called.
  • Aliases: alias name=value in command position

    • Like functions, alias definitions are interpreted when the definition names are later found in command position.
    • Unlike functions, though, as their definitions are arguments to the alias command, valid shell expansions found within are also interpreted at define time. And so alias definitions are always twice interpreted.
    • Also unlike functions, alias definitions are not parsed at all at define time, but, rather, it is the shell's parser that expands their values pre-parse into a command string when they are found.

You've probably noticed what I consider to be the primary difference between an alias or a function above, which is the shell's expansion point for each. The differences, actually, can make their combination quite useful.

alias foo=' foo(){ printf "Inside Function: %s\n" "$@" unset -f foo }; foo "$@" ' 

That is an alias that will define a function that unsets itself when called, and so it affects one namespace by the application of another. It then calls the function. The example is naive; it isn't very useful in a regular context, but the following example might demonstrate some other limited usefulness it might offer in other contexts:

sh -c " alias $(alias foo) foo \' " -- \; \' \" \\ 

Inside Function: ; Inside Function: ' Inside Function: " Inside Function: \ Inside Function: ' 

You should always call an alias name on an input line other than the one on which the definition is found - again, this is to do with the parse order of the command. When combined, aliases and functions can be used together to portably, safely, and effectively move information and parameter arrays in and out of subshelled contexts.


Almost unrelated, but here's another fun one:

alias mlinesh='"${SHELL:-sh}" <<"" ' 

Run that at an interactive prompt and any arguments you pass to it on the same execution line as the line on which you call it will be interpreted as arguments to a subshell. All lines thereafter until the first occurring blank line, though, are read-in by the current shell to pass as stdin to the subshell it prepares to call, and none of these are interpreted in any way at all.

$ mlinesh -c '. /dev/fd/0; foo' > foo(){ echo 'Inside Function'; } > 

Inside Function 

...but just...

$ mlinesh > foo(){ echo 'Inside Function'; } > foo > 

...is easier...

2
  • Aliases to the rescue, post-shellshock! On a serious note, this is one of the most helpful posts I've read in a long time. It not only provides a LOT of details on how to make functions more "portable/packageable" (no pun intended on portability, but that too), but you squeeze in a couple points that make one have to think for a bit. And I can't believe my eyes with the . /dev/fd/0 part -- I'm almost jealous I never thought of that nor read about it until now. That's going to change lots of my code!! THANK YOU!! Would upvote twice if I could.
    – Sean
    CommentedMay 18, 2021 at 3:38
  • I take it that you used a shell with a ... shall we say "more capable" lexer than my version of bash!! For others who come across this post, I had to make the following changes to get your code to produce the posted results. EITHER (1) In alias foo='..., remove the newline after the first single-quote, and in sh -c "..., also remove the newline after the first double-quote... OR (2) add an extra newline after sh -c "...foo \'. I didn't try it, but the shopt option expand_aliases may also fix the bash issues. Ref: gnu.org/software/bash/manual/html_node/Aliases.html
    – Sean
    CommentedMay 18, 2021 at 6:41

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.