Mihir Luthra
Mihir Luthra

Reputation: 6769

Detect if a script has been sourced in "/bin/sh"

MOST ANSWERS I FOUND ON HERE ONLY SEEM TO WORK FOR /bin/bash.

Tricks like $BASH_SOURCE and $SHLVL don't seem to be working with sh.

There was an answer which asked to use return, because it only works within functions and sourced scripts, and see if it generated any error but I didn't understand why on executing return on command-line I got logged out of the shell. If I "executed or sourced" a script containg return, it just exits that script. This was happening when I was on freebsd. Also I don't use any desktop environment there.

Simply typing on command line,

return

result: logged out

Executing or sourcing a script containing return:

$ cat testscript
#! /bin/sh

echo hello
return
echo hello
$ ./testscript
hello
$ . testscript
hello
$ 

This wasn't the case when I did the same on macOS(executed /bin/sh first). It worked perfectly fine there. There it just said

sh: return: can only `return' from a function or sourced script

just as expected.

I am looking for a solution to detect if a script is sourced in case of /bin/sh.

I am using freebsd and there I currently have default shell set to sh. I know I can install bash, but still I want to know how can I do the same for /bin/sh.

UPDATE:

I would like to mention a little more detail.

MacOS

In macOS I tried starting /bin/sh through command line, and I realised later that it is a non-login shell. So, when I types in logout there, reusult was:

sh: logout: not login shell: use `exit'

So I made /bin/sh my default shell and I am sure enough that /bin/sh was executed. When I typed in return there, the output I got is:

sh: return: can only `return' from a function or sourced script

Again just as expected. But when I typed, echo $SHELL, output was:

/bin/bash

And I checked /bin directory of of my machine and /bin/sh and /bin/bash don't seem to be linked.

FreeBSD

Now I tried executing /bin/sh there as well. The results were as follows:

$ /bin/sh
$ return
$ return
logged out on 2nd return

So in simple language it doesn't show any output if /bin/sh is a non-login shell and simply just exits that shell.

@user1934428 gave some nice amount of information in @CharlesDuffy 's answer. It's worth giving a read.

There he mentions that FreeBSD manual has no documentation for return statement. sh man page, FreeBSD

I checked if OpenBSD has the same case for man page, but it did define return as:

return [n] Exit the current function or . script with exit status n, or that of the last command executed.

sh man page, OpenBSD

One other issue is most man pages show bash manual on asking for man sh. Idk if its supposed to be like that or no.

Also, can someone suggest if I should start a new question for undefined behaviour of return? Because I think this question has went really off-topic. Not sure if it would be a good idea to do so.

Upvotes: 2

Views: 1622

Answers (3)

Mihir Luthra
Mihir Luthra

Reputation: 6769

I found the answer to this through FreeBSD mailing lists.

The man page where the entry for return was missing was the wrong man page.

Looking at the correct man page, the complete behaviour of return statement has been stated.

The syntax  of the return command is

       return [exitstatus]

     It terminates the current executional scope, returning from the closest
     nested function or sourced script; if no function or sourced script is
     being executed, it exits the shell instance.  The return command is im-
     plemented as a special built-in command.

As suggested by Ian in mailing lists, in case of /bin/sh a good possible way seems to keep a fixed name for your script and expand $0:

${0##*/}

and match it with the name. If the expansion produces anything else, it means script has been sourced. Another case could be that the user renamed it. So it's not completely error-prone but still should get my job done.

Upvotes: 1

Yuri Ginsburg
Yuri Ginsburg

Reputation: 2601

If you plan to use Bourne shell (/bin/sh) only testing $0 works nice.

$ cat t
#!/bin/sh

if [ $0 == "sh" ]; then
    echo "sourced"
else
    echo executed
fi

$ . t
sourced
$ . ./t
sourced
$ ./t
executed
$ sh t
executed
$ sh ./t
executed

If you want to call or source the script from other shells test $0 against list of shell names.

As @Mihir pointed FreeBSD shell works as described in manual page sh(1). In MacOS /bin/sh is basically bash albeit files /bin/sh and /bin/bash slightly differ. Note that comand man sh on Mac brings manual page for bash

Upvotes: 0

Charles Duffy
Charles Duffy

Reputation: 295373

$ sh ./detect-sourcing.sh
We were executed
$ sh -c '. ./detect-sourcing.sh'
We were sourced
#!/bin/sh
if (return 2>/dev/null); then
  echo "We were sourced"
else
  echo "We were executed"
fi

I haven't analyzed whether this is strictly required by the POSIX sh standard, but it works with /bin/sh on MacOS, the OP's stated platform.

Upvotes: 3

Related Questions