2

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.

Community
  • 1
  • 1
Mihir Luthra
  • 6,059
  • 3
  • 14
  • 39
  • How about this answer: https://stackoverflow.com/a/2687092/401499 ? – Tomasz Kapłoński Aug 18 '19 at 19:19
  • @Moby04, read the comments in that answer. – Mihir Luthra Aug 18 '19 at 19:20
  • And what about more detailed answer from @mklement0 there? – Tomasz Kapłoński Aug 18 '19 at 19:22
  • @Moby04, I mentioned in my question that on executing `return` i get logged out. – Mihir Luthra Aug 18 '19 at 19:25
  • 1
    if you source a script from your terminal cmd-line, than includes a call to `return` that gets executed, it will log you out in any shell. (I'm almost sure). This is different from running a script from your cmd-line (or cron or ?? ), which sources a script that returns. That will return you to the cmd-line. Good luck. – shellter Aug 18 '19 at 19:32
  • @shellter, actually I got a little confused. If I execute return in a script, it just exits that script(no matter if it is sourced or executed). If i type in `return` on cmd-line, it logs out. Whereas if i start `/bin/sh` in macOS and there i type in return , it just says return can only be used in a sourced script or func. (it doesn't quit that `/bin/sh`) – Mihir Luthra Aug 18 '19 at 19:46
  • Your link to "sh man page, FreeBSD" shows man Unix Seventh Edition which is 40 years old. There is a menu on this page allows to choose man for one of many Unix/Linux versions. Great resource BTW. – Yuri Ginsburg Aug 19 '19 at 20:42
  • @Mihir : You can start /bin/sh as login shell if you do first a `ln -s /bin/sh /bin/-sh` and invoke it as `-sh`. This is because the shell inspects $0, and if it starts with a dash, it considers itself a login shell. This sounds weird, but is a carry-over from the old Unix times. – user1934428 Aug 20 '19 at 04:56
  • @Mihir : The reason why you see on Linux the bash man page if you ask for sh, is that on Linux `sh` is a symlink to `bash`. The bash just behaves a little bit different if it is invoked as `sh`, but it still is not exactly equivalent to a POSIX shell. – user1934428 Aug 20 '19 at 04:59
  • @YuriGinsburg, well obv won't link that on purpose. Just didn't notice that it was old man page. In my answer I did mention link to correct man page. – Mihir Luthra Aug 20 '19 at 06:44
  • @user1934428, surely is wierd but good to know about that. Although wierd thing about `macOS` I don't see any links between `/bin/sh` and `/bin/bash` – Mihir Luthra Aug 20 '19 at 06:46
  • Ah, on Mac this is not supposed to be linked. But I guess since the bash is simply built from the GNU sources, they also installed the GNU man-pages, and perhaps this has silently replaced the BSD-sh-man-page. I don't think Apple is very careful about bash anymore, because they now are going mainly towards Zsh. – user1934428 Aug 20 '19 at 10:16
  • 1
    @user1934428 bash is build from GNU sources on any system but BSD and Linuxes have a separate man page for sh. – Yuri Ginsburg Aug 20 '19 at 19:25

3 Answers3

3
$ 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.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • 2
    result in POSIX is unspecified: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_24 – jhnc Aug 18 '19 at 20:22
  • I apologise if my question was unclear, I will edit it as soon as I have my lappy. My platform is `freebsd` which is where it shows unexpected results. I tested the same on `macOS` where everything works fine. – Mihir Luthra Aug 19 '19 at 02:28
  • Gotcha. Unfortunately, I'm not aware of anywhere the POSIX standard mandates a detectable behavior difference, so unless someone else can find such a point, we'll just need to retune to find a difference detectable in FreeBSD's implementation as opposed to Apple's. – Charles Duffy Aug 19 '19 at 04:15
  • This still leaves open why a `return` in an interactive shell terminates it. Interestingly the [FreeBSD's man page of sh](https://www.freebsd.org/cgi/man.cgi?query=sh&manpath=Unix+Seventh+Edition) doesn't even seem to mention the _return_ statement, while for instance the man page of _ash_, which is supposed to be very close to _sh_, plus the [bash man page](https://linux.die.net/man/1/sh) define the effect for `return` only inside a function, so what it does outside seems to be unspecified behaviour. – user1934428 Aug 19 '19 at 10:11
  • Somewhat off-topic, but maybe interesting: `zsh` defines the effect of a `return` at least also for its used inside a `trap` statement, but also is silent, about its use from the interactive prompt. Trying it out, shows for my zsh (5.5.1), that a `return` **does not** terminate the current shell. bash **also** does not log me out, while ash and dash do. – user1934428 Aug 19 '19 at 10:15
  • @user1934428, thanks for all this information, I updated my question with some of it. – Mihir Luthra Aug 19 '19 at 16:55
1

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.

Mihir Luthra
  • 6,059
  • 3
  • 14
  • 39
  • Looks somewhat like overkill for me. Why to remove path name if script is executed using full path – Yuri Ginsburg Aug 19 '19 at 20:45
  • I don't think this works. Imagine script `t1` that sources another script `t2`. Checking `$0` in `t2` will reflect how `t1` was invoked as it is not changed while `t2` runs. – jhnc Aug 19 '19 at 21:16
  • @jhnc, my fault, I was too lazy to write the complete explanation I got in mailing list. I updated my answer. It's that if a script knows what it is called and we match it's name with `${0##*/}` and it doesn't matches, it would mean script has been sourced. Ofc it's not foolproof, but still the cases in which this would fail seem to be lesser compared to other methods. – Mihir Luthra Aug 20 '19 at 06:34
  • @YuriGinsburg, we can't know from what path script got executed. But still the cases that script name changes are less. So if the script name and the `${0##*/}` doesn't match, it means it has been sourced. As mentioned above, its not fully error free, but still has less chances of causing errors. – Mihir Luthra Aug 20 '19 at 06:38
  • @Mihir Sourced script has just "sh" in $0 and ${0##*/} and we compare result with this. But for executed script say /home/user/bin/sh, $0 will yield the full path and ${0##*/} just sh – the same as for sourced script. – Yuri Ginsburg Aug 20 '19 at 19:18
0

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

Yuri Ginsburg
  • 2,302
  • 2
  • 13
  • 16
  • I don't think this works if `t` is called from inside another script `t2`. `$0` retains the value from `t2` and will reflect how `t2` was invoked. – jhnc Aug 19 '19 at 21:12
  • @jhnc, is right but yea `man sh` on macos produces `man bash` – Mihir Luthra Aug 20 '19 at 06:40