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

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.

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

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.

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

Related

How can I invoke both BASH and CSH shells in a same script

In the same script, I want to use some CSH commands and some BASH commands.
Invoking one after the other giving me problems despite I am following the different syntax for respective shells. I want to know where is mistake in my code
Your suggestions are appreciated!!
I am a beginner to shell, especially so for CSH. but the code I got has been written in CSH entirely. Since I have some familiarity with CSH, I wanted to tweak the existing CSH code by including BASH commands, which I am comfortable using it. When I tried BASH commands after CSH by invoking !#/bin/bash, it is giving some errors. I want to know if I am missing any options!!
#!/bin/csh
----
----
----
#!/bin/bash
dir2in="/nethome/achandra/NCEI/CCSM4_Historical/Forecasts"
filin2 ="ccsm4_0_cfsrr_Fcst.${ENS}.cam2.h1.${yyear[${iimonth}]}-${mmon[${iimonth}]}-${ssday}-00000.nc"
cp $dirin/$filin /nethome/achandra/NCEI/CCSM4_Historical_Forecasts/
ln -s /nethome/achandra/NCEI/CCSM4_Historical/Forecasts/$filin /nethome/achandra/NCEI/CCSM4_Historical_Forecasts/"${$filin%.nc.cdo}.nc"
#!/bin/csh
I am getting errors such as
"dirin: Undefined variable."
You are asking here for "embedding one language into another", which, as #Bayou already explained, is not supported directly. Maybe you were spoiled from the HTML-world, where you can squeeze CSS and Javascript in between and maybe use some server side PHP or Ruby stuff too.
The closest to this are HERE-documents. If you write inside your bash script a
csh <<CSH_END
your ...
csh ....
commands ...
go here ...
CSH_END
these commands are executed in a child process driven by csh. It works the other way around with bash in the same way. Make sure that the terminator symbol (CSH_END in my example) starts in column 1.
Whether this will work for your application, I can't say, because things which run in the same process in your original script, now run in different processes.
You can't mix them up like you're suggesting. It's like asking "can I use PHP code in a Python script". However, most of the shells have options to run commands (-c), just as csh does. For using Bash within a sh script:
#! /bin/sh
CONDITION=$(/bin/bash -c "[[ 1 > 2 ]] || echo no")
echo $CONDITION
exit 0
Otherwise you could create separate files and execute them.
#! /bin/sh
CONDITION=$(./bash-script.sh)
echo $CONDITION
exit 0
You, of course, should use csh instead of sh. Both of my scripts will output the following text.
$ ./test.sh
no

Mac OSx terminal : "not a valid identifier" on function definition

I have a bash script in which I define the below function,
function start-if-exists()
{
if [ "`docker container ls -a|grep $1`" ]; then
echo "Container $1 exists. Starting $1..."
return `docker start $1`
else
echo "Container $1 doesn't exists."
return ""
fi
}
While executing the above function in terminal(zsh) directly I am not getting any error. But when I execute it using sh command(sh my_script.sh), I am getting the below error.
my_script.sh: line 10: `start-if-exists': not a valid identifier
where my_script.sh is the name of file.
What am I missing that my script works with zsh but fails in sh?
/bin/sh is bash, but, when started as /bin/sh, it starts in POSIX mode. According to the bash man page, in POSIX mode:
Function names must be valid shell `name's. That is, they may not
contain characters other than letters, digits, and underscores, and
may not start with a digit. Declaring a function with an invalid
name causes a fatal syntax error in non-interactive shells.
A note about how to figure things like this out:
At the Terminal command line, I executed /bin/sh --version to see information about it. It printed “GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin17)”.
Then I referred to the bash man page, using the command man bash. Since that is long, you might prefer to save a copy to a file and view it in your preferred text editor. The raw man output includes archaic underscores and backspaces. You can get a copy without these by executing man bash | col -b > file.txt.
In the man page, I searched for ”sh” (as a complete word, not a raw search for those letters, since they appear as parts of many unrelated words). This quickly revealed discussion that bash behaves differently when started as “sh”; it starts in POSIX mode.
Searching further for “POSIX” revealed a list of things that are different in POSIX mode.

if not working with wildcard value

I have a script which basically has a function and then I am running the an if to run the function if the kernel version is the version I need. The script in short looks like this
#!/bin/bash
postinstall() {
run some postinstall commands in here
}
UNAME=`uname -r`
if [[ $UNAME == 3.* ]]
then
postinstall
else
echo "Kernel version is not correct"
fi
When I run the commands on CLI everything it works but when I trigger the script as sh <scriptname> I get the following result:
: 313: <scriptname>.sh: [[: not found
Kernel version is not correct
Line 313 is the one with the "if".
As I noted in a comment, the shell that is installed as /bin/sh is not necessarily Bash (for example, on Ubuntu, it is often dash), and even when it is Bash, it behaves differently when run as sh instead of bash.
However, as chepner pointed out in a comment, even when run as sh (in Bash's POSIX mode), Bash recognizes the [[ command — though it doesn't recognize some other extensions to POSIX shell syntax, such as process substitution.
Given the error message you are seeing, it is clear that on your machine, sh does not recognize [[ as a valid command, which in turn means it is probably not Bash. You should therefore not run it with sh but with bash. Or run it without specifying the shell on the command line, and let the shebang (#!/bin/bash) ensure that the kernel runs the correct shell.

Strange script behaviour when shebang references different shell

I've recently switched to the ksh93 shell. I did this by adding the following two lines to my .profile file
export SHELL=/usr/local/bin/ksh93
exec $SHELL
Since I did that some simple scripts have started misbehaving in a way I don't understand. I narrowed it down to the following simple script called say test.sh
#!/bin/ksh
echo $0 $1
If I type the command test.sh fred I would expect to see the same output test.sh fred. Instead I see test.sh noglob. If I remove the shebang or if I change it to read #!/usr/local/bin/ksh93 then the script works as expected.
Can anyone explain what's going on, or what to do about it? I'm stumped.
I'm using Solaris 5.9 if it makes any difference.
I notice from the comments that your .kshrc has a set noglob. The set command with no options will set the command-line parameters, which is why $1 is "noglob", it should be set -o noglob.
By the way, setting noglob is weird, are you sure you want that?
I suspect (as others have mentioned) that /bin/ksh is Korn shell 88.
There is an important difference between ksh88 and ksh93 with regards to .kshrc. On ksh88 .kshrc is executed for every korn shell process, even non-interactive ones (scripts). In ksh93 .kshrc is not executed for shell scripts, only for interactive login shells.
When you do exec $SHELL that is not a login shell, it is better to change your entry in /etc/passwd. By the way, using variable SHELL is a bad idea, since that is set by the login shell.
There's probably an alias on ksh in your system with noglob set as an option, or noglob is being passed as a default parameter by default in your old shell. You should also check what ksh you're really calling (check if there's a link to another shell in from /bin/ksh). ksh --version should give some insight as well.
As a last point, instead of calling the shell directly i'd recommend to use
#!/usr/bin/env ksh

bash script: started with $! instead of #! and got mysterious behavior. What happened?

I accidentally started a bash script with $! instead of #! and got some very weird behavior. I'm trying to figure out what happened.
If you try this script:
$!/bin/bash
echo Hello world!
you will get the following behavior:
$ chmod +x hello
$ ./hello
[nothing happens, get prompt back]
$ exit
exit
Hello world!
$
So it looks like this happened:
A new bash shell spawned.
Upon exit, the rest of the script executed.
What's up? How is anything at all happening? Without #!, how does the shell know to use bash to interpret the script?
Obviously this is a "satisfy my curiosity" rather than "solve my problem" question. Googling does not yield much, probably because #! and $! in queries don't make the Google-bot happy.
$something is a parameter ("variable") expansion, but $! in particular returns a value that isn't set in your script, so it expands as a zero length string.
Therefore your script is, you are correct, the equivalent of:
/bin/bash
echo Hello world!
The shebang magic number is an old feature of Unix, but the shell is even older. A text file with the execute bit set that the kernel cannot exec (because it's not actually compiled) is executed by a subshell. That is, the shell deliberately runs another shell and passes the pathname as the parameter. This is how commands written as shell scripts were executed before shebang was invented, and it's still there in the code.
dollar-bang gets the PID of the last backgrounded process.
http://tldp.org/LDP/abs/html/internalvariables.html (Search for '$!')
Expanding a bit on #DigitalRoss's answer:.
An executable script with no #! on the first line is executed by /bin/sh -- even if you execute it from bash (or tcsh). This isn't shell functionality, it's in the kernel.
So when you executed your script, it was executed by /bin/sh (which means, on most systems, that it won't be able to use bash-specific features), $! expanded to nothing (because that shell hasn't launched any background processes), and the first line invokes an interactive /bin/bash shell. Then you exit from that interactive shell, and your script execute the echo Hello world! line and terminates, putting you back in your original shell.
For example, if you change echo Hello world! to echo $BASH_VERSION, you'll find that it doesn't print anything -- and if you type history from the invoked interactive bash shell, it won't show anything.

Resources