How can I get this script file's functions to load without having to source it every time? "command not found" (Bash/scripting basics)

353

Solution 1

mikeserv's answer is good for the details of what goes on "behind the scenes", but I feel another answer here is warranted as it doesn't contain a simple usable answer to the exact title question:

How can I get this script file's functions to load without having to source it every time?

The answer is: Source it from your .bashrc or your .bash_profile so it is available in each shell you run.

For example, I have the following in my .bash_profile:

if [ -d ~/.bash_functions ]; then
  for file in ~/.bash_functions/*.sh; do
    . "$file"
  done
fi

Solution 2

Your problem is that the script in your .../bin directory is execed 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 exported.

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.

Share:
353

Related videos on Youtube

Skeeve
Author by

Skeeve

Updated on September 18, 2022

Comments

  • Skeeve
    Skeeve almost 2 years

    I created a function:

    function createValidatorForForm(id) {
        var formValidator = $(id).validate({
            errorClass:'help-inline',
            errorElement:'p',
    
            highlight:function (element, errorClass, validClass) {
                $(element.parentNode.parentNode).addClass('error')
            },
            unhighlight:function (element, errorClass, validClass) {
                $(element.parentNode.parentNode).removeClass('error')
            }
        });
        return formValidator;}
    

    and then I use it in my code in the next way:

    var reportValidator = createValidatorForForm("#report-form");
    var areaValidator = createValidatorForForm("#area-form");
    var liquidationValidator = createValidatorForForm("#liquidation-form");
    var comparableValidator = createValidatorForForm("#comparable-form");
    

    but though Firebug knows about formValidator and I can see it's state in debugger, he alerts me that reportValidator and another three variables are undefined and I can't use them in my code: reportValidator.form() generates an error. But validation itself works on all forms.

    function saveReport() {
    if (!comparableValidator.form()){return};
    $.ajax({
        url : SAVE_REPORT, // + "?json=" + ko.toJSON(reportModel),
        data : {
            json : ko.toJSON(reportModel)
        }, ...
    
    • renathy
      renathy over 11 years
      what is reportValidator.form()? please, show full code how you call validators
    • Split Your Infinity
      Split Your Infinity over 11 years
      Validate is not a standard Jquery function. Which lib are you using?
    • Viktor S.
      Viktor S. over 11 years
      Are you calling createValidatorForForm in jquery.ready callback? Like $(document).ready(function() {...var reportValidator = createValidatorForForm("#report-form");...})
    • Skeeve
      Skeeve over 11 years
      docs.jquery.com/Plugins/Validation#Validator - that's the lib I'm using.
    • Skeeve
      Skeeve over 11 years
      2FAngel, no, not exactly, I'm using it in onDocumentReady() function.
    • Viktor S.
      Viktor S. over 11 years
      And where do you call reportValidator.form()? Outside that function, right?
    • Skeeve
      Skeeve over 11 years
      yes, in button onclick function
  • Skeeve
    Skeeve over 11 years
    TypeError: comparableValidator is undefined if (!comparableValidator.form()){return}; - still not working
  • Viktor S.
    Viktor S. over 11 years
    And scripts are not cached?
  • Viktor S.
    Viktor S. over 11 years
    If that is not a caching issue, you may try window.comparableValidator = createValidatorForForm("#comparable-form"); Code in answer should be a synonym to proposed here, but maybe there is something in your code that breaks it.
  • Skeeve
    Skeeve over 11 years
    stop, excuse me, I was wrong, only comparableValidator is undefined, all other work well, thank you very much
  • Viktor S.
    Viktor S. over 11 years
    Well, I see nothing wrong with your code. Possibly you are passing wrong form id or something else. Not clear
  • jamadagni
    jamadagni over 8 years
    Heh, I suppose the short answer is that "sorry, unless you source it you can't use the functions". For interactive shells you can write one source line in ~/.bashrc or such, but for non-interactive shells running scripts there's no other go other than to source it per script. This is pretty much like having to do #include or import in other languages: (almost) no function definitions are implicit...