2

This question is, if it matters, preferably in the context of Bash, but I would appreciate a cross-language solution.

The problem I'm encountering is in the context of "sourcing" a script with another, but doing this recursively. The problem is that it seems to do it infinitely.

The specific case is as follows. I've created a simple bash script to load other bash scripts in the same directory. Since in my project all scripts are in the same directory, this for now is ok.

So the "script loader" is as follows (a file which defines the following function):

_source_script()
{
    # Define a few colors for the possible error messages below.
    RED=$(tput setaf 1)
    NORMAL=$(tput sgr0)

    EXPECTED_DIR="scripts"

    if [ "$#" -ge  "1" ]
    then
        EXPECTED_DIR=$1
    fi

    # Based on: http://stackoverflow.com/a/1371283/3924118
    CURRENT_DIR=${PWD##*/}

    if [ "$CURRENT_DIR" = "$EXPECTED_DIR" ]
    then
        for (( arg = 2; arg <= $#; arg++ ))
        do
            # Based on:
            # - http://askubuntu.com/questions/306851/how-to-import-a-variable-from-a-script
            # - http://unix.stackexchange.com/questions/114300/whats-the-meaning-of-a-dot-before-a-command-in-shell
            # - http://stackoverflow.com/questions/20094271/bash-using-dot-or-source-calling-another-script-what-is-difference

            printf ". ./${!arg}.sh\n"
            # Try to load script ${!arg}.sh
            . ./${!arg}.sh

            # If it was not loaded successfully, exit with status 1.
            if [ $? -ne 0 ]
            then
                printf "${RED}Script '${!arg}.sh' not loaded successfully. Exiting...${NORMAL}\n"
                exit 1
            fi
        done
    else
        printf "No script loaded: $CURRENT_DIR != $EXPECTED_DIR.\n"
        exit 1
    fi
} 

To use this "script loader", other scripts must first "source" it as follows:

. ./_source_script.sh

The problem is when I try to include other scripts by sourcing them through the function _source_script. For example, when I tried to do (in script some_script.sh):

_source_script scripts colors asserts clean_environment

it kept on running forever.

Inside colors I've:

#!/usr/bin/env bash

# Colors used when printing.
export GREEN=$(tput setaf 2)
export RED=$(tput setaf 1)
export NORMAL=$(tput sgr0)
export YELLOW=$(tput setaf 3)

Inside clean_environmnet I've:

 ...

. ./_source_script.sh
_source_script scripts colors

and inside asserts.sh I also have:

. ./_source_script.sh
_source_script scripts colors

From my understanding, this should run recursively until it finds either a script which does not load anything or it finds a cycle, which isn't the case.

So, my solution was to have in some_script.sh the following:

_source_script scripts colors 
_source_script scripts asserts
_source_script scripts clean_environment

that is, run them individually.

So, why can't I "source" multiple scripts in a loop?

  • Not really an answer, but you may want to have, in each of your "sourceable" files, a content which check if it was alerady sourced: Ie, each of those files should be on this basis: if [ -z "already_source_this_script_foobar" ]; then { ... here all the code to be sourced, including at the end : already_source_this_script_foobar="1" ;} fi (right now you have the ".... here all the code to be sourced" part, so I recommend you add the surrounding check. – Olivier Dulac Feb 15 '17 at 15:01
  • @OlivierDulac Yes, thanks. I also thought to do something like that, but then I forgot about it because of this problem. – nbro Feb 15 '17 at 15:12
  • @OlivierDulac Actually I also didn't know how to do it, so here's the opportunity to ask you. Why do you just use if [ -z "myscript.sh" ]; then ... to check if myscript.sh was already sourced? What's the -z flag here doing? – nbro Feb 15 '17 at 15:16
  • sorry, my bad, forgot the "$". -z tests if a variable is either unset or was set to the empty string. so you'd enclose the definitions by : if [ -z "$already_sourced_foobar" ]; do { .... here all the definitions, the last one being: already_sourced_foobar="1" ;} ; fi : on the first "source", the variable is unset, so -z returns true. On the next ones, it is set (to "1", or whatever), and the inside of the if (with all the definitions) is not sourced again. each file has its own specific variable to tell if that specific file was sourced or not (ie, another file: if [ -z "$other_var" ]; – Olivier Dulac Feb 15 '17 at 16:09
  • @OlivierDulac Must the argument (or variable) that comes after -z be a variable, or can I use the script name? Apparently the -z option indeed checks if a variable is set or not, but this could be a problem, since I would need to know which variables are defined in the script I'm trying to source... what am I misunderstanding, if something? – nbro Feb 15 '17 at 16:33
0

Not looking too closely at your code, the general approach would include something akin to C's "header guards":

#ifndef HEADER_H
#define HEADER_H

/* The contents of the header file, with typedefs etc. */

#endif

where HEADER_H is a C preprocessor macro specific to this header, used solely to avoid including the header again if it's been included already. I usually create a macro with the name derived from the header file itself, like MCMC_H for a header file called mcmc.h.

In a shell script, this may be as easy as

if [ -z "$script_source_guard" ]; then
script_source_guard=1

# ... do things (define functions etc.)

fi

The name of the variable script_source_guard needs to be specific to this file. Again, the name may arbitrarily be derived from the source file name; a file whose name is myfuncts.shlib may have a guard variable called MYFUNCTS or _MYFUNCTS or guard_myfuncts or whatever.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.