Your problem is that the script in your .../bin
directory is exec
ed in another shell environment - its environment does not survive its execution and so the x() { ... ; }
definition does not survive into the current shell environment when it completes.
When you . ./somescript.sh
(or source
in some shells such as bash
and zsh
) the current shell reads the script into the current environment and executes the contents as if issued from the prompt - so x() { ... ; }
is defined in the current shell environment - your interactive shell.
Basically the problem can be demonstrated like this:
sh <<\HEREDOC_CMD_FILE #runs sh shell with heredoc input file
{ #begins current shell compound expression
( #begins subshell compound expression
x() { echo 'I am function x.' ; } #define function x
x #x invoked in subshell
) #ends subshell compound expression
x #x invoked in current shell
} #ends current shell compound expression
#v_end_v
HEREDOC_CMD_FILE
###OUTPUT###
I am function x.
sh: line 6: x: command not found
In the same way the environment defined in the ( : subshell )
in the above example does not survive its completion, neither does that defined in your executed script. Similarly, when sh
reads in the HEREDOC_CMD_FILE
it is performing the same function as your source ../file
and running its contents in its current shell execution environment - though its environment is a subshell child to the shell issuing the prompt at which it is run, and so none of its environment survives its completion from there, either. You can make it do so, though, like:
. /dev/fd/0 <<\HEREDOC_CMD_FILE && x
x() { echo 'I am function x.' ; }
HEREDOC_CMD_FILE
###OUTPUT###
I am function x.
... which is pretty much exactly what you do with source ${script}
except that here we .dot
source the contents of stdin
whereas you source the contents of a file on disk
The primary difference, though, between a ( : subshell )
and an executed script is what an executed script's execution environment looks like. When you execute a script it is provided a new environment in which to operate - so any variables declared in your current shell do not carry over to its environment unless explicitly exported or declared on its command line. This behavior can be demonstrated thus:
{ x() { printf "$FMT" "$0" "$var2" x ; } #describe env
export FMT='argv0: %s\t\tvar2: %s\t\tI am function %s.\n' \
var1=val1 #var1 and FMT are explicitly exported
var2=shell_val2 #var2 is not
cat >./script && #read out stdin to >./script
chmod +x ./script && #then make ./script executable
var3=val3 ./script #then define var3 for ./script's env and execute
} <<\SCRIPT ; x ; y #send heredoc to block's stdin then call {x,y}()
#!/usr/bin/sh
#read out by cat to >./script then executed in a separate environment
y() { printf "$FMT" "$0" "$var2" y ; } #describe env
echo "${var1:-#var1 is unset or null}" #$var1 if not unset or null else :-this}
echo "${var2:-#var2 is unset or null}"
echo "${var3:-#var3 is unset or null}"
export var2=script_val2
x ; y #run x() ; y() in script
#v_end_v
SCRIPT
###OUTPUT###
val1
#var2 is unset or null
val3
./script: line 8: x: command not found
argv0: ./script var2: script_val2 I am function y.
argv0: sh var2: shell_val2 I am function x.
sh: line 18: y: command not found
This behavior differs from that of ( : subshells )
run at the prompt or otherwise as children of the current shell because they automatically inherit the environment of their parent, whereas - as demonstrated above - executed children require that it be explicitly export
ed.
var1=val1 ; export var2=val2
x() { echo 'I am function x.' ; }
(
printf '%s\n' "$var1" "$var2"
x
)
###OUTPUT###
val1
val2
I am function x.
The last thing I should note is that there is no portable way to export functions from a parent shell to an executed script without .dot
sourcing a file containing the function definition within the executed script, as you do with source
in your current shell. Basically, you cannot portably export function
as you can with variables. Though, I suppose you might export fn_def='fn() { : fn body ; }'
then eval "$fn_def"
in your executed script. And, of course, any child environment - executed or otherwise - dies with the child.
So if you want the function defined in the script, but do not want to source the script itself, you have to read the function as output from some command and eval
it.
eval "$(cat ./script)"
But this is practically the same thing as . ./script
- just less efficient. You might as well just source it.
The best way to do this is to remove the function definition from the script itself and put it instead in its own file, then source it from both your script and from the current shell when you want it. Like this:
{
echo 'x() { echo "I am function x and my argv0 is ${0}." ; }' >./fn_x
echo '. ./fn_x ; x' >./script
chmod +x ./script && ./script
. ./fn_x ; x
}
###OUTPUT###
I am function x and my argv0 is ./script.
I am function x and my argv0 is sh.
To have this always available in an interactive shell add a . /path/to/fn_x
to your shell's ENV
file. For example, for bash
with a script containing functions called foo
located in /usr/bin
you could add this line to ~/.bashrc
:
. /usr/bin/foo
If the script is for whatever reason not available at that location during startup your shell will still read in and source the rest of the ENV
file as expected, though there will be a diagnostic message printed to stderr
to let you know there was an issue sourcing your function definition file.
~/.oh-my-zsh/custom/
Here's the full setup I use now: github.com/mrgnw/mzsh