32

How do I create a function named from the contents a variable? I want to write a template script that defines a function named after the script’s own file name. Something like this (which of course doesn't work):

#!/bin/bash
bname="$(basename $0)" # filename of the script itself

someprefix_${bname}() { # function's name created from $bname variable
    echo test
}

So if the script's filename is foo.sh, then it should define a function named someprefix_foo that will echo "test".

4
  • 4
    Here comes the standard answer to unusual requests: Why do you want to do this? Commented Aug 22, 2011 at 9:21
  • 2
    Whenever that "answer" comes I find myself tempted to say "I just want to, deal with it!". But the explanation is pretty strange here: I often find myself writing scripts, that define their own auto completion when sourced (and do something else when not). So I figured I might write a template for those. For the auto completion function of those scripts I need an identifier unique to the script - so I thought why not build it form the script's file name. Don't know if that's a good idea but it's better than nothing. Commented Aug 22, 2011 at 9:31
  • 4
    Well, I understand your sentiment, but more often than not, unusual ideas come from Doing It Wrong(tm). On the other hand, sometimes it's a fully valid idea, such as this one (and I like it!). But for the majority of these questions, a gentle prod down the Right Way(tm) will be better help than implementing what was originally asked for. Commented Aug 22, 2011 at 10:49
  • The script in the question actually does work if you use #!/bin/zsh Commented Nov 8, 2019 at 22:00

6 Answers 6

34

You can use eval:

eval "someprefix_${bname}() { echo test; }"

Bash even allows "."s in function names :)

Sign up to request clarification or add additional context in comments.

1 Comment

You know - there are times when I doubt myself. Could have come up with that myself, but didn't. Thanks a lot, eugene!
20

Indeed, it is instructive that the aforementioned construction derails the possibility of having embedded quotation marks there within and is thus potentially bugulant. However, the following construction does proffer similar functionality whilst not having the constraints of the former answer.

For your review:

source /dev/stdin <<EOF
function someprefix_${bname}()
{
     echo "test";
};
EOF

3 Comments

IIRC, there was a bug in some versions of bash which were unable to successfully handle sourcing from a pipe. Solved in current releases, and moot if running this code on a version outside the range that was prone to it, but this may not work everywhere.
This is the best answer here. I'll just add that you have to escape the $ with / when you want to delay the execution to actual run time of the function.
Re @meridius comment you have to escape parameters inside the function declaration to make this work with parameters. Example: function someprefix_${bname}() { echo "\$@"; }
2

The 'eval and 'source' solutions work IFF you do not have anything in the function that could be evaluated at create time that you actually want delayed until execution time.

source /dev/stdin << EOF
badfunc () 
{
    echo $(date)
}
EOF

$ date;badfunc;sleep 10;date;badfunc
Tue Dec  1 12:34:26 EST 2015 ## badfunc output not current
Tue Dec  1 12:23:51 EST 2015
Tue Dec  1 12:34:36 EST 2015 ## and not changing
Tue Dec  1 12:23:51 EST 2015

Yes, it's a contrived example (just call date and don't echo a subshell call of it), but using the original answers, I tried something a more elaborate that required a run time evaluation and got similar results - evaluation at "compile" time, not run time.

The way that I solved the problem was to build a temp file with the function and then source that. By the judicious use of single and double quotes surrounding the test I print into the file, I can completely control what gets evaluated and when.

tmpsh=$(mktemp)
echo "goodfunc ()" > $tmpsh
echo '{' >> $tmpsh
echo 'echo $(date)' >> $tmpsh
echo '}' >> $tmpsh
. $tmpsh
rm -f $tmpsh

and then

$ date;goodfunc;sleep 10;date;goodfunc
Tue Dec 1 12:36:42 EST 2015 ## current
Tue Dec 1 12:36:42 EST 2015
Tue Dec 1 12:36:52 EST 2015 ## and advancing
Tue Dec 1 12:36:52 EST 2015

Hopefully, this will be of more use to people. Enjoy.

8 Comments

FYI, every time you run echo foo >>somewhere, a new file handle on somewhere is opened, and closed at the end of that single command's execution -- quite needless inefficiency. Better to open the file handle once and reuse it.
Also, the original answer could have been used with only trivial modification: echo \$(date)
I sit corrected. By applying backslashes as needed, I was able to take my original code based on the 'source /dev/stdin << EOF' example and get it to work without resorting to the temp file solution. Why that did not occur to me in the first place, I cannot fathom.
I can't seem to get the /dev/stdin version working with Cygwin. I keep getting the error -bash: /dev/stdin: No such file or directory even though Matthew@monolith /dev $ ls -la std* lrwxrwxrwx 1 Matthew None 15 Aug 27 22:22 stderr -> /proc/self/fd/2 lrwxrwxrwx 1 Matthew None 15 Aug 27 22:22 stdin -> /proc/self/fd/0 lrwxrwxrwx 1 Matthew None 15 Aug 27 22:22 stdout -> /proc/self/fd/1
Cygwin, mingw32 and msys all have their own set of leaky abstractions. I'd go with eval there.
|
2

I use the following syntax, which avoids /dev/stdin but still lets you use heredocs:

eval "$(cat <<EOF
  $function_name() {
    commands go here
    but "\$you \$have \$to \$escape $variables"
  }
EOF
)"

(notice $variables will be expanded in the function body, the other vars will not be).

Comments

1

To avoid uncertainty of what gets expanded when, I used to put all the stuff I want to make in an auxiliary function with a fixed name and just call that from a function with a dynamic name:

_function_main() {
  date
  printf '%s\n' "$@"
}

eval "function_${dynamicname}() { _function_main \"\$@\"; }"

Comments

-2

You can disable expansion of a Here Document like that:

eval "$(cat <<EOF
  $function_name() {
    commands go here
    and "$you $do $not $have $to $escape variables"
  }
EOF
)"

See: How to cat <<EOF >> a file containing code?

Bash Manual: 3.6.6 Here Documents / Here Document

No parameter and variable expansion, command substitution, arithmetic expansion, or filename expansion is performed on word. If any part of word is quoted, the delimiter is the result of quote removal on word, and the lines in the here-document are not expanded. If word is unquoted, all lines of the here-document are subjected to parameter expansion, command substitution, and arithmetic expansion, the character sequence \newline is ignored, and \ must be used to quote the characters \, $, and `.

1 Comment

This has different behavior than my answer; here $you and the other variables will be expanded in the function definition. You still need to \$escape any variables you want to preserve in the function definition, Which you'll want depends on the desired behavior, but this does not "disable expansion".

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.