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 ?

Jake
  • 1,353
  • 3
    What's wrong with just foo() { echo "Inside function"; }; foo? – cuonglm Sep 30 '15 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 Sep 30 '15 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 Sep 30 '15 at 19:05
  • If you get the function a name.
    $ foo="bar"
    $ ${foo}
    Inside function
    $```
    
    – Andy Dalton Sep 30 '15 at 19:21

2 Answers2

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.

cuonglm
  • 153,898
  • 2
    BTW: This feature was always in conflict with the POSIX standard. – schily Sep 30 '15 at 19:25
  • @schily: Would you please show me the part of POSIX standard which conflict with this bash feature? AFAICT, POSIX didn't deny or allow this feature. – cuonglm Sep 30 '15 at 19:27
  • 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 Sep 30 '15 at 19:41
  • @schily: it's in XRAT under the Function Definition Command heading. A separate issue ... is the availability of that function to child shells. A few objectors maintained that just as a variable can be shared with child shells by exporting it, so should a function. In early proposals, the export command therefore had a -f flag for exporting functions. ... This facility was strongly opposed,,, – mikeserv Oct 01 '15 at 00:30
  • @schily - p.s. - how could you make unexport POSIX compliant? – mikeserv Oct 01 '15 at 00:36
  • I tried: $ foo() { echo "hello"; }, $ export -f foo, $ env | grep foo. I see foo = () { echo "hello" The semi-colon and last brace are missing, it that due to vulnerable version of bash ? – Jake Oct 01 '15 at 02:54
  • @Jake: Yes, try env | sed -n '/foo/{N;p}' to see more. Fixed version will appent prefix BASH_FUNC_ and suffix %%. – cuonglm Oct 01 '15 at 02:57
  • I see foo = () { echo "hello" }, no semicolon though. – Jake Oct 01 '15 at 02:59
  • 1
    @cuonglm Is there a man page that describes how to use functions in bash ? – Jake Oct 01 '15 at 03: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 Oct 01 '15 at 03: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 Oct 01 '15 at 03:12
  • @cuonglm Quick question .. because you used the term variable in your answer, the ones that could contain function definition before Shellshock, is it correct to call them shell variables OR is that term reserved for variables pre-defined in shell ? – Jake Oct 01 '15 at 04:06
  • @Jake: Yes, that's shell variable. Like in your example, you don't need to use export -f to tell bash foo is a function. – cuonglm Oct 01 '15 at 04:15
  • 1
    @mikeserv - Sorry for the typo, I intended to write readonly – schily Oct 01 '15 at 09:47
  • @mikeserv - and thank you for pointing to the right text. The POSIX teleconferences are the central tool to discuss proposals or existing implementations for consistence and security relevance. There are usually ~200 years of UNIX experience in the conference. This helps to avoid taking over mistakes from people who did not yet discuss their idea with a sufficient number of people. In most cases, the rationale section gets a hint on why a specific proposal was rejected to avoid the same proposal to appear again. – schily Oct 01 '15 at 10:07
  • @cuonglm I tried another test (sorry for so many comments). In bash I wrote: $ foo() { echo "hello";}; echo "world";, and bash prints world. This is still in current shell. Although I haven't run the function yet, is this still related to shellshock bash vulnerability ? – Jake Oct 01 '15 at 17:33
  • @Jake: No, that's normal shell execution. Try env xx='() { echo vulnerable; }' bash -c xx, if you see vulnerable, you are. – cuonglm Oct 01 '15 at 17:34
  • @cuonglm I posted a related question - http://unix.stackexchange.com/q/233559/63934. Can you please take a look ? Thanks – Jake Oct 02 '15 at 18:28
  • @cuonglm Apple use prefix "__BASH_FUNC<" and suffix ">()" –  Oct 03 '15 at 21:39
  • @cuonglm I was looking at the bash source code. Is this the function call that contained the original 6271 bug ? Thanks ! – Jake Oct 06 '15 at 23:08
  • @Jake: No, see the patch from Chet for more details. – cuonglm Oct 07 '15 at 01:31
  • @cuonglm The link seems broken now. Do you have a different link ? Thanks ! – Jake Oct 08 '15 at 00:06
  • @Jake: Maybe gmane server was overload, wait it for available. Or access it from google cache. – cuonglm Oct 08 '15 at 01:34
  • @cuonglm I was checking patch for bash 4.2 at https://bugzilla.redhat.com/attachment.cgi?id=938976. I wanted to ask in evalstring.c, why do they need 2 break statements ? Wouldn't the break from SEVAL_FUNCDEF check be redundant if a break is from SEVAL_ONECMD check ? Sorry for so many comments. – Jake Nov 02 '15 at 19:27
  • @Jake: I'm not sure, but it doesn't seem to be a redundant check, because two if conditions appear at the same level. – cuonglm Nov 03 '15 at 02:13
  • @cuonglm: Can you please take a look: http://stackoverflow.com/q/33880183/682869. Thanks. – Jake Nov 24 '15 at 01:00
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...

mikeserv
  • 58,310
  • 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 May 18 '21 at 03: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: https://www.gnu.org/software/bash/manual/html_node/Aliases.html – Sean May 18 '21 at 06:41