60

Let's assume I have 3 shell scripts:

script_1.sh

#!/bin/bash
./script_3.sh

script_2.sh

#!/bin/bash
./script_3.sh

the problem is that in script_3.sh I want to know the name of the caller script.

so that I can respond differently to each caller I support

please don't assume I'm asking about $0 cause $0 will echo script_3 every time no matter who is the caller

here is an example input with expected output

  • ./script_1.sh should echo script_1

  • ./script_2.sh should echo script_2

  • ./script_3.sh should echo user_name or root or anything to distinguish between the 3 cases?

Is that possible? and if possible, how can it be done?

this is going to be added to a rm modified script... so when I call rm it do something and when git or any other CLI tool use rm it is not affected by the modification

codeforester
  • 39,467
  • 16
  • 112
  • 140
a14m
  • 7,808
  • 8
  • 50
  • 67
  • 12
    Beware modifying the `rm` command (by giving using cover scripts called `rm`, or aliases, or functions). You'll come to rely on the functionality, and then one day you'll find yourself using the raw `rm` command without the protections, and you'll do serious damage because you've been lulled into a false sense of security. – Jonathan Leffler Dec 13 '13 at 18:27

9 Answers9

84

Based on @user3100381's answer, here's a much simpler command to get the same thing which I believe should be fairly portable:

PARENT_COMMAND=$(ps -o comm= $PPID)

Replace comm= with args= to get the full command line (command + arguments). The = alone is used to suppress the headers.

See: http://pubs.opengroup.org/onlinepubs/009604499/utilities/ps.html

Thomas Guyot-Sionnest
  • 2,251
  • 22
  • 17
  • 10
    Cygwin's `ps` does not support the `-o` option. – Koraktor Sep 02 '15 at 11:34
  • 1
    If the parent-name is longer than 15 chars, it gets truncated to 15 chars.... (tested on RHEL8). – Rop Aug 27 '22 at 15:30
  • 1
    @Rop This is a very standard limitation, the `comm` field is 15 char and generally just the executable name. On multi-threaded processes, it's also common for threads to change their `comm` to help differentiate between threads. The full command being invoked is usually the first argument of `args`, though using `ps` it could be difficult to separate names/paths that could contain a space form the rest of the arguments (on Linux, reading the null-terminated `/proc/self/cmdline` would be a safe way to get the full command and its arguments). – Thomas Guyot-Sionnest Sep 16 '22 at 02:46
23

In case you are sourceing instead of calling/executing the script there is no new process forked and thus the solutions with ps won't work reliably.

Use bash built-in caller in that case.

$ cat -n server.sh 
  1  #!/bin/bash
  2  
  3  source common.sh
  4  
  5  [[ $# -ne 2 ]] && warn_me "Error: You messed up"
  6
$ cat -n common.sh 
  1  #!/bin/bash
  2  
  3  function warn_me() {
  4      echo `caller | awk '{print $2":"$1}'` says: "$@"
  5  }
  6
$ ./server.sh 
./server.sh:5 says: Error: You messed up
$ 

Source

Kashyap
  • 15,354
  • 13
  • 64
  • 103
21

The $PPID variable holds the parent process ID. So you could parse the output from ps to get the command.

#!/bin/bash
PARENT_COMMAND=$(ps $PPID | tail -n 1 | awk "{print \$5}")
user3100381
  • 464
  • 2
  • 5
  • Inventive, and probably about as good as you're going to get. Not particularly nice, though. – Jonathan Leffler Dec 13 '13 at 18:26
  • On some platforms you just need the right options to `ps`, so the tail and Awk (which could easily be refactored to just Awk, btw) can be avoided. – tripleee Dec 13 '13 at 18:32
  • 3
    this prints `/bin/bash` – a14m Dec 13 '13 at 18:33
  • 2
    @artmees Try it with `awk "{print \$6}"` – Reinstate Monica Please Dec 13 '13 at 19:03
  • using `$6` make `./script_3` prints `'\n'` empty string and `./script_2` return `'\n./script_3'` – a14m Dec 13 '13 at 19:31
  • instead could you explain what this `ps $PPID | tail -n 1 | awk "{print \$6}"` should output and i might figure it out – a14m Dec 13 '13 at 19:32
  • @artmees I think I was looking at a modified ps, $5 is correct. You can just split the commands up to see what they do. First execute `ps $PPID`, then `ps $PPID | tail -n 1` , then add the awk. You could also add the F flag to`ps`to get command arguments, though sed of the file ps reads still feels slightly simpler. – Reinstate Monica Please Dec 13 '13 at 19:48
  • Found another site that was recommending ps -ocommand= -p $PPID, which then removes the need for a tail command. – Iain Hallam Apr 28 '14 at 16:09
6

Based on @J.L.answer, with more in depth explanations, that works for :

cat /proc/$PPID/comm

gives you the name of the command of the parent

If you prefer the command with all options, then :

cat /proc/$PPID/cmdline

explanations :

  • $PPID is defined by the shell, it's the of the parent processes
  • in /proc/, you have some dirs with the of each process (). Then, if you cat /proc/$PPID/comm, you echo the command name of the PID

Check man proc

Gilles Quénot
  • 173,512
  • 41
  • 224
  • 223
  • this doesn't run on MacOs – a14m Mar 06 '18 at 09:12
  • on linux this is the best answer – Scott Stensland Jan 18 '23 at 13:29
  • It's worth mentioning that `cat /proc//comm` truncates the program's name to the first 15 characters. A least, that's the result I'm getting on the various Linux hosts I'm using. To obtain a program name that exceeds 15 characters, I use `cat /proc//cmdline` with additional steps to (a) strip the leading directory components from the program path string (e.g., basename(1)) and (b) strip command line arguments that follow the program name. – Jim Fischer Jul 26 '23 at 17:27
2

Couple of useful files things kept in /proc/$PPID here

  • /proc/*some_process_id*/exe A symlink to the last executed command under *some_process_id*
  • /proc/*some_process_id*/cmdline A file containing the last executed command under *some_process_id* and null-byte separated arguments

So a slight simplification.

sed 's/\x0/ /g' "/proc/$PPID/cmdline"
Reinstate Monica Please
  • 11,123
  • 3
  • 27
  • 48
1

If you have /proc:

$(cat /proc/$PPID/comm)
Gilles Quénot
  • 173,512
  • 41
  • 224
  • 223
J.L.
  • 19
  • 1
0

You can simply use the command below to avoid calling cut/awk/sed:

ps --no-headers -o command $PPID

If you only want the parent and none of the subsequent processes, you can use:

ps --no-headers -o command $PPID | cut -d' ' -f1
HoldOffHunger
  • 18,769
  • 10
  • 104
  • 133
Six
  • 5,122
  • 3
  • 29
  • 38
0

Declare this:

PARENT_NAME=`ps -ocomm --no-header $PPID`

Thus you'll get a nice variable $PARENT_NAME that holds the parent's name.

-4

You could pass in a variable to script_3.sh to determine how to respond...

script_1.sh

#!/bin/bash
./script_3.sh script1

script_2.sh

#!/bin/bash
./script_3.sh script2

script_3.sh

#!/bin/bash
if [ $1 == 'script1' ] ; then
  echo "we were called from script1!"
elsif [ $1 == 'script2' ] ; then
  echo "we were called from script2!"
fi
Donovan
  • 15,917
  • 4
  • 22
  • 34
  • this is not possible as i'm trying add a script that handles special case of `rm` and i want it to be called if i called `rm` not if `git` used `rm` to remove some info. – a14m Dec 13 '13 at 18:00
  • please clarify your original question then, because that was not clear from your examples. – Donovan Dec 13 '13 at 18:01
  • I do not think it is possible to obtain the calling program from within a bash script the way you want, I'm sorry. – Donovan Dec 13 '13 at 18:02