19

I have defined a bash function in my ~/.bashrc file. This allows me to use it in shell terminals. However, it does not seem to exist when I call it from within a script.

How can I define a bash function to be used by scripts as well?

George M
  • 13,589
  • 4
  • 43
  • 53
Pythonist
  • 737
  • 7
  • 15
  • But my .bash_profile basically reads the .basrc file, so I would expect the result to be the same regardless I use login or non-login shell. – Pythonist Feb 04 '13 at 16:17
  • Are you using `/bin/sh` in the shebang line? – Kevin Feb 04 '13 at 16:21
  • 1
    [Quick summary of which files are automatically sourced when](http://unix.stackexchange.com/a/29811/11750) – Kevin Feb 04 '13 at 16:39

3 Answers3

13

~/.bash_profile and ~/.bashrc are not read by scripts, and functions are not exported by default. To do so, you can use export -f like so:

$ cat > script << 'EOF'
#!/bin/bash
foo
EOF
$ chmod a+x script
$ ./script
./script: line 2: foo: command not found
$ foo() { echo "works" ; } 
$ export -f foo
$ ./script
works

export -f foo could also be called in ~/.bash_profile to make this function available to scripts after login. Be warned that export -f is not portable.

A better solution would be to source the file containing the function using . file. This is much more portable, and doesn't rely on your environment being set up in a particular way.

Chris Down
  • 122,090
  • 24
  • 265
  • 262
  • why not just declare a function like `function myFunction { ... }` in `~/.bash_profile` and you're good to go? – amphibient Feb 04 '13 at 16:34
  • If I understood properly your answer (regarding sourcing a file with the function), this would mean that I have to source the file explicitly in every script, which is pretty much annoying. This is not much less complicated than just copy-pasting the function itself in every script. I would save some lines, sure, but that's all. However, the `export` solution seems to work properly. Thanks. – Pythonist Feb 04 '13 at 16:53
  • @foampile, this is precisely what I did and does not work when calling the function from a script. – Pythonist Feb 04 '13 at 16:56
  • 1
    @Onturenio : the second method @chris-down proposed is indeed better: 1) When someone read the script, they are aware that it needs some `foo` function from some `file` 2) You can better control the content of that `file` than make sure that the invoking shell didn't change `foo` before invoking your script. You could add safety checks on `file` to make sure it's untampered, for example. (not easy, but possible). (well, you could also do those checks on the defined foo function... but you get my drift ^^ I think the method 2 is cleaner.) 3) `file` will only contain what's needed, not more. – Olivier Dulac Feb 04 '13 at 17:25
9

.bashrc is only read by interactive shells. (Actually, that's an oversimplification: bash is quirky in this respect. Bash doesn't read .bashrc if it's a login shell, interactive or not. And there's an exception even to the exception: if bash's parent process is rshd or sshd, then bash does read .bashrc, whether it's interactive or not.)

Put your function definitions in a file in a known place, and use the . (also spelled source) builtin to include that file in a script.

$ cat ~/lib/bash/my_functions.bash
foo () {
…
$ cat ~/bin/myscript
#!/bin/bash
. ~/lib/bash/my_functions.bash
foo bar

If you want, you can follow ksh's autoload feature. Put each function definition in a file with the same name as the function. List the directories containing the function definitions in the FPATH variable (a colon-separated list of directories). Here's a crude approximation of ksh's autoload which actually loads the function immediately instead of on-demand:

autoload () {
  set -- "$(set +f; IFS=:;
            for d in $FPATH; do
              if [ -r "$d/$1" ]; then echo -E "$d/$1"; break; fi;
            done)"
  [[ -n $1 ]] && . "$1"
}
Gilles 'SO- stop being evil'
  • 807,993
  • 194
  • 1,674
  • 2,175
  • +1 for autoload, although I recommend using the ones that come with bash. The latest one supports lazy load. – Starfish Jan 29 '15 at 05:30
1

Do you need a function? If not, consider pulling out the logic into a separate, stand-alone Bash script in your $PATH. For example, I had this in my ~/.bashrc:

# echo public IP address
alias wanip='dig +short myip.opendns.com @resolver1.opendns.com'

~/bin is in my $PATH, so I created ~/bin/wanip with the following contents:

#!/bin/bash

# echo public IP address
dig +short myip.opendns.com @resolver1.opendns.com

And ran chmod 0755 ~/bin/wanip to make it executable. Now I can execute wanip from other scripts.

I like having wanip in a stand-alone Bash script. It reminds me that I want this logic generally available (besides just in my current interactive Bash session). The script nicely encapsulates the logic and documentation for same.

Adam Monsen
  • 604
  • 1
  • 7
  • 15