25

Is there any way to mark a script to be "run as source" so you don't have to add the source or "." command to it every time? i.e., if I write a script called "sup", I'd like to call it as

sup Argument

rather than

source sup Argument

or

. sup Argument

Basically, I'm trying to use cd within a script.

phuclv
  • 37,963
  • 15
  • 156
  • 475
typeoneerror
  • 55,990
  • 32
  • 132
  • 223
  • 1
    please clarify. the issue of making the script exectuable and the issue of using "cd" within a script are not the same thing. – simon Apr 15 '09 at 16:35
  • 1
    "Is there any way to mark a script to be "run as source" so you don't have to add the source or "." command to it every time?" – typeoneerror Apr 15 '09 at 17:30
  • 1
    You have two different issues here, which is why I asked for clarification. You can make scripts executable, and then you won't need "." or "source", but they won't be able to modify the callers environment (the shell you execute from). So cd makes no change to caller. Different problems. – simon Apr 15 '09 at 17:35

4 Answers4

34

Bash forks and starts a subshell way before it or your kernel even considers what it's supposed to do in there. It's not something you can "undo". So no, it's impossible.

Thankfully.

Look into bash functions instead:

sup() {
    ...
}

Put that in your ~/.bashrc.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
lhunath
  • 120,288
  • 16
  • 68
  • 77
25

When you are running a shell, there are two ways to invoke a shell script:

  • Executing a script spawns a new process inside which the script is running. This is done by typing the script name, if it is made executable and starts with a

    #!/bin/bash
    line, or directly invoking
    /bin/bash mycmd.sh
  • Sourcing a script runs it inside its parent shell (i.e. the one you are typing commands into). This is done by typing

    source mycmd.sh
    or
    . mycmd.sh

So the cd inside a shell script that isn't sourced is never going to propagate to its parent shell, as this would violate process isolation.

If the cd is all you are interested about, you can get rid of the script by using cd "shortcuts"... Take a look into the bash doc, at the CDPATH env var.

Otherwise, you can use an alias to be able to type a single command, instead of source or .:

alias mycmd="source mycmd.sh"
Varkhan
  • 16,601
  • 5
  • 31
  • 24
  • I did modify my CDPATH, but it still doesn't seem to work when the script is executing in its "own" shell – typeoneerror Apr 15 '09 at 17:10
  • Yes, of course, because the change doesn't get propagated to the parent shell, the one that you are using. My remark was more about a way to get rid of the script altogether. – Varkhan Apr 15 '09 at 18:18
  • that's perfect! I never even thought to just alias it – Sophie Dec 13 '15 at 03:33
10

Create an alias for it:

alias sup=". ~/bin/sup"

Or along those lines.

See also: Why doesn't cd work in a bash shell script?


Answering comment by counter-example: experimentation with Korn Shell on Solaris 10 shows that I can do:

$ pwd
/work1/jleffler
$ echo "cd /work5/atria" > $HOME/bin/yyy
$ alias yyy=". ~/bin/yyy"
$ yyy
$ pwd
/work5/atria
$

Experimentation with Bash (3.00.16) on Solaris 10 also shows the same behaviour.


Community
  • 1
  • 1
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • Hmm, alias worked, but still executes in a separate shell so the 'cd' within the script does nothing. Thanks though! – typeoneerror Apr 15 '09 at 17:20
  • In that case, probably go with the CDPATH solution in the referenced question? It's what I use mostly. What is actually in the script? Just a 'cd' or a whole lot more stuff? – Jonathan Leffler Apr 15 '09 at 19:15
8

It is not possible to source a script in your current environment if you sub-shell the script at invoke.

However, you can check that script is sourced and force the script to terminate if not:

if [ -z "$PS1" ] ; then
    echo "This script must be sourced. Use \"source <script>\" instead."
    exit
fi

The same way, you can force a script not to be sourced but to be sub-shelled instead (preserve current shell environment):

if [ "$PS1" ] ; then
    echo "This script cannot be sourced. Use \"./<script>\" instead."
    return
fi

Both versions are available as gists: see please source and don't source.

Tim
  • 2,052
  • 21
  • 30
  • 1
    BTW, `echo -e` is not ideal -- even if the shell is always bash, sometimes it'll just print `-e` on output, depending on the value of the `xpg_echo` and `posix` flags (as the POSIX `echo` spec permits special handling *only* for `-n`, a strictly POSIX-compliant `echo` is not allowed to do anything with `-e` other than print it on output). See https://pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html, particularly including the APPLICATION USAGE and RATIONALE sections. – Charles Duffy Nov 29 '19 at 16:06
  • Thanks it was a typo, no use here. Removed. – Tim Nov 29 '19 at 16:10
  • I'd print to stderr (i.e. `>&2 echo 'This script must be sourced.'). Otherwise, this is great advice! – Max Truxa Feb 06 '23 at 21:40