Is there a way for shell script to know which program has executed it?

There's often confusion between process forking and execution.

When you do at the prompt of a bash shell.

$ sh -c 'exec env ps'

The process P1 issuing that $ prompt is currently running bash code. That bash code forks a new process P2 that executes /bin/sh which then executes /usr/bin/env, which then executes /bin/ps.

So P2 has in turn executed code of bash, sh, env and ps.

ps (or any other command like a script we would use instead here) has no way to know that it has been executed by the env command.

All it can do is find out what its parent process id is, which in this case would be either P1 or 1 if P1 has died in the interval or on Linux another process that has been designated as a subreaper instead of 1.

It can then query the system for what command that process is currently running (like with readlink /proc/<pid>/exe on Linux) or what arguments where passed to the last command it executed (like with ps -o args= -p <pid>).

If you want your script to know what invoked it, a reliable way would be to have the invoker tell it. That could be done for instance via an environment variable. For instance script1 could be written as:

#! /bin/sh -
INVOKER=$0 script2 &

And script2:

#! /bin/sh -
printf '%s\n' "I was invoked by $INVOKER"
# and in this case, we'll probably find the parent process is 1
# (if not now, at least one second later) as script1 exited just after
# invoking script2:
ps -fp "$$"
sleep 1
ps -fp "$$"
exit

$INVOKER will (generally) contain a path to script1. In some cases, it may be a relative path though, and the path will be relative to the current working directory at the time script1 started. So if script1 changes the current working directory before calling script2, script2 will get wrong information as to what called it. So it may be preferable to make sure $INVOKER contains an absolute path (preferably keeping the basename) like by writing script1 as:

#! /bin/sh -
mypath=$(
  mydir=$(dirname -- "$0") &&
  cd -P -- "$mydir" &&
  pwd -P) && mypath=$mypath/$(basename -- "$0") || mypath=$0

... some code possibly changing the current working directory
INVOKER=$mypath script2

In POSIX shells, $PPID will contain the pid of the parent of the process that executed the shell at the time of that shell initialisation. After that, as seen above, the parent process may change if the process of id $PPID dies.

zsh in the zsh/system module, can query the current parent pid of the current (sub-)shell with $sysparams[ppid]. In POSIX shells, you can get the current ppid of the process that executed the interpreter (assuming it's still running) with ps -o ppid= -p "$$". With bash, you can get the ppid of the current (sub-)shell with ps -o ppid= -p "$BASHPID".


Yes, a program can know who its parent is.

To illustrate, let's create two bash scripts. The first one reports its PID and starts the second script:

$ cat s1.sh
#!/bin/bash
echo s1=$$
bash s2.sh

The second script reports its process ID, the PID of its parent, and the command line used to run the parent:

$ cat s2.sh
#!/bin/bash
echo s2=$$ PPID=$PPID
echo "Parent command: $(ps -o cmd= -q $PPID)"

Now, let's run it:

$ bash s1.sh
s1=17955
s2=17956 PPID=17955
Parent command: bash s1.sh

As you can see the second script does, in fact, know the PID of its parent. Using ps, that PID reveals the command line used to invoke the parent.

For a discussion of PPID in more depth, see Stéphane Chazelas's answer.