Is there any mechanism in Shell script alike "include guard" in C++?
Asked Answered
H

4

10

let's see an example: in my main.sh, I'd like to source a.sh and b.sh. a.sh, however, might have already sourced b.sh. Thus it will cause the codes in b.sh executed twice. Is there any mechanism alike "include guard" in C++?

Hyacinthe answered 22/9, 2011 at 16:57 Comment(2)
you can set a variable and use it as a guardCoop
The proper name for this pattern is apparently modulino: rosettacode.org/wiki/Modulinos#UNIX_ShellFranconia
R
9

If you're sourcing scripts, you are usually using them to define functions and/or variables.

That means you can test whether the script has been sourced before by testing for (one of) the functions or variables it defines.

For example (in b.sh):

if [ -z "$B_SH_INCLUDED" ]
then
    B_SH_INCLUDED=yes
    ...rest of original contents of b.sh
fi

There is no other way to do it that I know of. In particular, you can't do early exits or returns because that will affect the shell sourcing the file. You don't have to use a name that is solely for the file; you could use a name that the file always has defined.

Raymund answered 22/9, 2011 at 17:6 Comment(1)
Even though the value you set to the include guard is non-empty, it would be better if you use -z "${B_SH_INCLUDED+x}" instead.Shall
E
8

In bash, an early return does not affect the sourcing file, it returns to it as if the current file were a function. I prefer this method because it avoids wrapping the entire content in if...fi.

if [ -n "$_for_example" ]; then return; fi
_for_example=`date`
Edlun answered 31/7, 2014 at 2:22 Comment(0)
N
1
TL;DR:

Bash has a source guard mechanism which lets you decide what to do if executed or sourced.

Longer version:

Over the years working with Bash sourcing I found that a different approach worked excellently which I will discuss below.

The problem for me was similar to the one of the original poster:

  1. sourcing other scripts led to double script execution
  2. additionally, scripts are less testable with unit test frameworks like BATS

The main idea of my solution is to write scripts in a way that can safely sourced multiple times. A major part plays the extraction of functionality (compared to have a large script which would not render very testable).

So, only functions and global variables are defined, other scripts can be sourced at will.

As an example, consider the following three bash scripts:

main.sh

#!/bin/env bash

source script2.sh
source script3.sh

GLOBAL_VAR=value

function_1() {
  echo "do something"
  function_2 "completely different"
}

run_main() {
  echo "starting..."
  function_1
}

# Enter: the source guard
# make the script only run when executed, not when sourced)
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
  run_main "$@"
fi

script2.sh

#!/bin/env bash

source script3.sh

ALSO_A_GLOBAL_VAR=value2

function_2() {
  echo "do something ${1}"
}

# this file can be sourced or be executed if called directly
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
  function_2 "$@"
fi

script3.sh

#!/bin/env bash

export SUPER_USEFUL_VAR=/tmp/path

function_3() {
  echo "hello again"
}

# no source guard here: this script defines only the variable and function if called but does not executes them because no code inside refers to the function.

Note that script3.sh is sourced twice. But since only functions and variables are (re-)defined, no functional code is executed during the sourcing.

The execution starts with with running main.sh as one would expect.

There might be a drawback when it comes to dependency cycles (in general a bad idea): I have no idea how Bash reacts if files source (directly or indirectly) each other.

Northeast answered 3/11, 2022 at 10:43 Comment(1)
This is the correct answer - the other ones are workarounds. I also like the resemblance to pythons if __name__ == '__main__', this improves intuitivenessFranconia
R
0

Personally I usually use

set +o nounset # same as set -u

on most of my scripts, therefore I always turn it off and back on.

#!/usr/bin/env bash

set +u
if [ -n "$PRINTF_SCRIPT_USAGE_SH" ] ; then
    set -u
    return
else
    set -u
    readonly PRINTF_SCRIPT_USAGE_SH=1
fi

If you do not prefer nounset, you can do this

[[ -n "$PRINTF_SCRIPT_USAGE_SH" ]] && return || readonly PRINTF_SCRIPT_USAGE_SH=1
Retral answered 15/4, 2017 at 6:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.