3

I want to use a program in the shebang, so I create a script named <myscript> with:

#!<mypgm>

I also want to be able to run <mypgm> directly from the command prompt.

<mypgm> args...

So far, no issue.

I want to be able to run <myscript> from the command prompt with arguments.

<myscript> blabla

In turn, the shebang makes <mypgm> being called with the following arguments:

<mypgm> <myscript> blabla

Now, I need to know when <mypgm> <myscript> blabla is called using the shebang, or not:

myscript blabla # uses the shebang
-or-
<mypgm> myscript blabla   # directly in the command prompt.

I looked at the environment variables (edit: <=== wrong assertion (¬,¬”) ), at the process table (parent process too) but didn't find any way to make a difference.

The only thing I found so far is:

grep nonvoluntary_ctxt_switches /proc/$$/status

When this line is just after the shebang, the value is often 2 (sometimes 3) when called through the shebang, and 1 (sometimes 2) with the direct call. Being unstable and dependent on process scheduling (the number of times the process was taken off from its CPUs), I am wondering if anybody here might have a better solution.

19
  • 8
    So more facts for us. But What is the question? Commented May 6, 2019 at 11:16
  • 9
    This ^^^ should be the main part of your question. Your question as it is now tries to solve Y while you want to solve X.
    – pLumo
    Commented May 6, 2019 at 11:32
  • 2
    I need to know if it was invoked using a shebang (i.e. from a file), or directly from the command prompt (without any file in the process. Ummm, why?!?! What difference does it make how your program is started? That's the real problem you need to solve. Commented May 6, 2019 at 12:36
  • 6
    So what practical difference is there between myscript blabla and mypgm myscript blabla for you? How do you distinguish between mypgm myscript blabla and mypgm otherargs? I’m trying to understand what you’d do with the information you’re asking for, once you have it. Commented May 6, 2019 at 13:08
  • 2
    Forget the shebang for a moment. If you want to allow calc myscript blah, how are you going to differenciate between calc PI where PI is a script, and calc PI + 1? (This is why most tools use an option for scripts, e.g. awk -f myscript.) Commented May 6, 2019 at 13:38

6 Answers 6

21

Instead of having myprg magically detect whether it is being used in a shebang, why not make that explicit by using a command-line flag (such as -f) to pass it a file as a script?

From your example in the comments:

In the calc theoretical example above. calc PI + 1 should return 4.14159... Now adding the support for the shebang (i.e. a filename as the first parameter) would return the calculation contained into the file.

Make calc take a script file through -f and then create scripts with:

#!/usr/local/bin/calc -f
$1 + 1

Let's say you call this file addone.calc and make it executable. Then you can call it with:

$ ./addone.calc PI
4.141592...

That call will translate into an invocation of /usr/local/bin/calc -f ./addone.calc PI, so it's pretty clear which argument is a script file and which is a parameter to the script.

This is similar to how awk and sed behaves.

A similar (but opposite) approach is to have calc take a script file argument by default (which simplifies its use with a shebang), but add a command-line flag to use it with an expression from an argument. This is similar to how sh -c '...' works.

5
  • I already thought to this solution. Unfortunately, I know it is not perfect a all but in my environment, I need a shebang a env: "#!/bin/env calc". In that situation, it ends up that adding an argument make the shebang combine try to call calc\ -f, and not calc with option -f.
    – Jacques
    Commented May 6, 2019 at 18:39
  • 1
    In many environments, shebang lines are quite limited in terms of the number of arguments they can take. If you're trying to use the environment (#!/usr/bin/env) then if you want your script to be portable you can't add command-line arguments. If you want something more complex than to simply specify one single interpreter, you're abusing the shebang and may or may not get the results you're looking for and will quite likely lose portability. Commented May 6, 2019 at 20:15
  • 1
    If you want to use the shebang with env, then turn it around and make the script file execution the default and use a command-line flag for passing an expression inline from the arguments. There is really no reliable way to find whether your program is being called from a shebang. IMO, that's by design, so we shouldn't be trying to go around that.
    – filbranden
    Commented May 6, 2019 at 22:11
  • @ScottSeverance , you'd have to be using a really old Unix to be limited in any practical way in the number of arguments you could pass to the interpreter on the shebang line. By (partial) coincidence, I was reading just this morning about the technical limitations of the shebang line on this site: #! magic ... 127 bytes for the shebang line seems to be the minimum these days. Commented Oct 6, 2021 at 14:27
  • 1
    @todd: You might be right, but I don't know for sure. What I do know is that my web hosting company runs FreeBSD and a whole lot of things I take for granted on Linux don't work there. I haven't tested the shebang limits specifically, but I've learned that in many cases what seems more modern to me turns out to be limited to Linux. Commented Oct 6, 2021 at 16:37
9

The real problem is the way you designed the commandline syntax of <mypgm>. Instead of trying to support two ways of interpreting its arguments, provide two ways of calling it instead.

Shebang commands are meant to be script engines that execute the content of your script; it might be bash, perl, or whatever, but the expectation is that it is called with the file name of a script to execute. How does bash do it? It does not guess. If it encounters any argument that does not look like an option (or an option's argument), it treats it as the script to execute; arguments after that are passed to the script. For example:

/bin/bash -x -e somename foo bar

Here, bash will look for the file somename and try to run it as a script with arguments foo and bar. You should do the same thing, because you might want to write <mypgm> <myscript> on the command line some day.

If you want the script-less use of <mypgm> to be the default, you can require a script to be passed with <mypgm> -f <myscript>. This is how sed does it. Then you'd use it in a shebang line like this:

#!<mypgm> -f

If you want the script case to be the default, like with bash and perl, create an option that says "there is no script this time". You could use -- for this, so that <mypgm> -- one two three does not try to run one (or anything else) as a script. In that case the shebang line would just read:

#!<mypgm>
5

Now, I need to know when blabla is called using the shebang, or not:

In C, you can obtain that info via getauxval(AT_EXECFN), which will tell you the name of the original executable (ie the first argument passed to execve(2)) [1].

But that string is placed in the memory immediately after the command line arguments and environment strings, at the end of the [stack] memory region, so it can be fetched directly from there.

For instance, the following perl script (name it foo.pl), if made executable with chmod 755 foo.pl, will print ./foo.pl when run directly and /usr/bin/perl when run as perl ./foo.pl:

#! /usr/bin/perl

open my $maps, "/proc/self/maps" or die "open /proc/self/maps: $!";
my $se;
while(<$maps>){ $se = hex($1), last if /^\w+-(\w+).*\[stack\]$/ }
open my $mem, "/proc/self/mem" or die "open /proc/self/mem: $!";
sysseek $mem, $se - 512, 0;
sysread $mem, $d, 512 or die "sysread: $!";
print $d =~ /([^\0]+)\0+$/, "\n";

On newer (>=3.5) linux kernels the end of the environment is also available in /proc/PID/stat (in the 51th field, as documented in the proc(5) manpage).

#! /usr/bin/perl

open my $sh, "/proc/self/stat" or die "open /proc/self/stat: $!";
my @s = <$sh> =~ /\(.*\)|\S+/g;
open my $mem, "/proc/self/mem" or die "open /proc/self/mem: $!";
seek $mem, $s[50], 0;
$/ = "\0";
my $pn = <$mem> or die "readline: $!"; chomp $pn; print "$pn\n";

[1] Linux kernels newer than 2.6.26 introduced the aux vector entry pointing to it (see the commit), but the executable name was available at the end of the stack long before that (since linux-2.0 from 1996).

10
  • Size of the array is 44 for me, not 51 or more. :-(
    – Jacques
    Commented May 6, 2019 at 12:49
  • what system are you running on (uname -a). The format of /proc/PID/stat on linux hasn't changed since a long time.
    – user313992
    Commented May 6, 2019 at 12:50
  • Linux xxxxxxxxxxx.com 2.6.32-573.el6.x86_64 #1 SMP Thu Jul 23 15:44:03 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux
    – Jacques
    Commented May 6, 2019 at 12:56
  • It's at the end of the stack, anyway. You can get the end of the stack from /proc/PID/maps. Very gross example: off="0x$(awk -F'[- ]' '/\[stack\]/{print$2}' /proc/$$/maps)"; dd bs=1 if=/proc/$$/mem skip=$((off - 64)) status=none count=64 | perl -nle 'print /.*\0([^\0]+)\0*$/'
    – user313992
    Commented May 6, 2019 at 13:16
  • 1
    @Jacques I've updated with a /proc/self/maps version (the 1st script) which should work with older kernels. This is not easily doable in the shell, because older kernels won't let you access the memory of another process, unless you've ptrace-d it first, and the shell has no builtin command which could be used to read /proc/self/mem (and running dd will have to fork a separate process).
    – user313992
    Commented May 7, 2019 at 4:37
2

You can have regular calc program with usage like calc PI + 3 (and as extension calc -f script_file_name).

For using in shebang create link (only hard links works if I recall correctly) named eg. calcf and then in calc program check executable name (for C/C++ look at argv[0] in function main). You have now #! /some/path/calcf in scripts.

That way you avoids using options like -c on command line (3 keystrokes saved) and you don't need options in shebang (that may be problematic as of Scott's comment shebang or not shebang).

1
  • I sense the kernel of an answer here, but it’s wrapped in confusion.  Can you please edit this to make it clearer how you are addressing the question? Commented May 7, 2019 at 2:01
0

Just realized that the following environment variable does it all: $_

When launched using <myscript>, its value is './<myscript>'

When launched using <mypgm> <myscript> its value is the full path to <mypgm>.

That simple, in my case:

#!/bin/bash

how_called=$_

if [[ "X$how_called" == X$0 || "X$how_called" ==X$BASH ]]; then
#                              ^in this case, if the login shell is not bash
   shebang=0
else
   shebang=1
fi

bn=$(basename $0)

A bit later (for my purpose):

if (( shebang == 1 )) || [[ ! -z $1 && "X$1" != X-* && "X$1" == X*\.${bn:0:3} && -x $1 ]]; then 
   # ^ shebang: first argument is the script file
   #                        ^ or not shebang: first argument **may** be a script file name
   #                                                    ^ ensure that this is a script by script extension
   #                                                       (otherwise just use the more verbose but standard --script=...)

   shebang_fn="$1"
   shift 1
   set -- --script="$shebang_fn" "$@" # fall back on standard way.
fi

(I know that I'm flipping the table a bit here, and that we still have to ensure that this is a portable solution).

-1

I may have found something closer to the solution:

cat /proc/$$/cmdline | tr '\0' '\n'

Called from command prompt, output is:

bash
<path to mypgm>
<myscript>

Called from shebang:

bash
<path to mypgm>
./<myscript>

Third line is different.

The sort of solution below is still heuristic, not a final one. Indeed, as several people mentioned here, playing with specific option either in shebang mode (-f ) or in command line mode (-c ...) would be 100% bullet proof (the hard link solution too).

However, this solution wouldn't be satisfying for the situation encountered. Moreover, both solutions are NOT mutually exclusive. You could either rely on the heuristic below in absence of -f and -c options, or on the bullet proof solution in case you're using one of them.

Heuristic equivalent in bash.

    #!/bin/bash

    # first statement for more accuracy
    shebang=$(</proc/$$/status)

    shebang=$(echo "${shebang// /\n}" \
       | awk '$1 ~ /^nonvoluntary_ctxt_switches:$/ {print $2}')

    maybe_file=$(awk -v RS="\0" 'NR==3' /proc/$$/cmdline)

    if    [[ "X$maybe_file" == X*/* ]] \
       && [[ maybe_file != X-* ]] \
       && (( shebang > 1 )) \
       && [[ -x "$maybe_file" ]]; then
        shebang=1
    else
        shebang=0
    fi

Lowering the probability to even a very low probability in not a nice option (I would understand people voting down for this) but mixing this heuristic with the bullet proof solution might be (or not) better than nothing until a more definitive solution is found.

I understand perfectly what's happening, the pros and the cons of each comment provided (thanks a lot to all of you!). I'm just a bit surprised that there is apparently no way to get the original command line after the shebang.

3
  • 1
    <mypgm> ./myscript is still a valid form, and would yield the same results as your "called from shebang" option. Commented May 6, 2019 at 17:24
  • I did not pretend that it was THE solution, but wrote "CLOSER to the solution". I know this is not yet perfect but it is already better than using the heuristic [[ -x &lt;myscript&gt; ]] and nonvoluntary_ctxt_switches in /proc/$$/status. I proposed this as a possible starting point to the solution, as I may still edit it ;-)
    – Jacques
    Commented May 6, 2019 at 18:47
  • "there is apparently no way to get the original command line" On linux (your Q is tagged "linux") there very much is a well defined and documented way to retrieve the original command, as I explained in my answer. As to that being hard to do in pure shell + standard utilities, everything is hard to impossible to do in those conditions, including trivial tasks like (reliably) sorting a list of files by size.
    – user313992
    Commented May 7, 2019 at 12:42

You must log in to answer this question.

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