10

I have a growing collection of scripts which should be sourced, not run. At the moment they have the shebang

#! /bin/cat

but I would prefer the have them be sourced into bash when run, in the same way as I had done

$ . /path/to/script.sh

or

$ source /path/to/script.sh

But . and source are bash builtins, so is an alternative shebang line for such scripts possible?

5 Answers 5

20

No. By the time a shebang comes into play, you have already lost. A shebang is applied when a process is exec()'d and typically that happens after forking, so you're already in a separate process. It's not the shell that reads the shebang, it's the kernel.

9

The she-bang is interpreted by the kernel when a command is executed, not the shell. So, by that time, it's too late.

You can make it instead:

#! /bin/echo Please run (from a Bourne-like shell): .

Or:

#! /bin/sed 2,5!d;s/^#.//
# This script must be sourced from within a shell
# and not executed. For instance with:
# 
#   . path/to/that/script
rest of the script

To tell the user what they did wrong.

Which should work on Linux. Replace all but the first space with one of the non-ASCII spacing characters (like U+00A0, U+2006...) on some other OSes. You may have to adapt the path of the echo or sed utilities.

1
  • These are interesting options to avoid the user from executing the script instead of sourcing them, but it will not work if the script is called as bash script.sh. I use the check "${BASH_SOURCE[0]}" == "${0}" for that.
    – Vinicius
    Commented May 5, 2023 at 9:27
7

As user @muru says, it's not possible to do because you have already left the shell session behind when you get to the #!-line.

However, depending on what your shell files do, there might be another solution.

I'm guessing that they set environment variables that you use for some project.

Let's call a project subtool (because that's a project I have). Then you could have a script that sets up a shell environment for projects, project-env for example:

#!/bin/bash

PROJECT="$1"
PROJECT_ROOT="$HOME/projects/$PROJECT"

cd "$PROJECT_ROOT" || exit 1
source "$PROJECT.env"

export PS1="[$PROJECT: \W] \$ "
exec bash -i

Run with:

$ ./project-env subtool

This will automatically cd into the given project subfolder beneath $HOME/projects, read a project environment file called subtool.env in this case (in which you initialize variables), give you a command line prompt for the project and leave you at an interactive bash session:

[subtool: subtool] $

When your work is done, simply exit.

This also has the benefit of isolating the project environment from your "ordinary" login shell session and from other projects.

2
  • I "have already left the shell session behind", but I'll be returning in a moment, and an executable can leave symbols to be read by the supershell on stdout
    – jalanb
    Commented Jun 23, 2016 at 9:34
  • 1
    @jalanb Well, of course, and you could execute the variable assignments from stdin with export $( script ). That however doesn't solve your initial problem of not wanting to type . or source.
    – Kusalananda
    Commented Jun 23, 2016 at 10:15
0

What is your point?

You don't want the script to be executed on its own? If the script is intended to be sourced, it should not even have the executable bit set. Nor the shebang. Nor be placed in your PATH.

Are you annoyed about typing those additional two extra characters, a dot and a space, from a live bash session? Just define an alias.

alias script.sh='. /path/to/script.sh'

And, like executables in your PATH, aliases will be auto completed when typing.

PS. For PATH-like inclusion of your growing script collection, you could create all aliases in your ~/.bashrc by some snippet like:

for F in $(find /path/to/collection); do alias ${F##*/}=". $F"; done
-1

The things you want to do in those .'ed scripts are changing the shell process; so you need to call them from the shell process; which means either aliases, or their more powerful brothers shell functions. This means you have to do some setup in .profile or equivalent.

The alias trick is sort of simplistic: alias mytool1=". /my/library/mytool1.sh"

Can you read everything in at the beginning? .profile: . /my/library/define_tools.sh

define_mytools.sh: mytool1() { ... contents of mytool1.sh ... } mytool2() { ...; }

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .