67

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

1
  • 13
    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. Commented Dec 13, 2013 at 18:27

10 Answers 10

92

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

Sign up to request clarification or add additional context in comments.

3 Comments

Cygwin's ps does not support the -o option.
If the parent-name is longer than 15 chars, it gets truncated to 15 chars.... (tested on RHEL8).
@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).
29

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 meta_meta_server.sh
     1
     2  echo from meta_meta_server.sh
     3
     4  source ./meta_server.sh
$ cat -n meta_server.sh
     1
     2  echo from meta_server.sh
     3  source server.sh
$ cat -n server.sh
     1
     2  echo from server.sh
     3  source common.sh
     4
     5  [[ $# -ne 2 ]] && warn_me "Don't do that"
$ cat -n common.sh
     1
     2  echo from common.sh
     3
     4  function warn_me() {
     5      echo "WARNING: $@"
     6      echo "stack trace:"
     7      i=0; while caller $i; do ((i++)); done
     8  }
$
$ bash meta_meta_server.sh
from meta_meta_server.sh
from meta_server.sh
from server.sh
from common.sh
WARNING: Don't do that
stack trace:
5 source server.sh
3 source ./meta_server.sh
4 main meta_meta_server.sh
$ 

Source

4 Comments

@JerryGreen it's a bash built-in. What shell are you using? Available at least on GNU bash.
I use standard terminal which comes with macos Catalina (10.15.7), it comes with zsh/bash/sh
Consider meta_server.sh which sources server.sh. Is it possible somehow to get the "top level" caller's name (i.e. meta_server.sh) in the warn_me?
@pmor, yes it's possible. Updated post.
22

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}")

8 Comments

Inventive, and probably about as good as you're going to get. Not particularly nice, though.
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.
this prints /bin/bash
@artmees Try it with awk "{print \$6}"
using $6 make ./script_3 prints '\n' empty string and ./script_2 return '\n./script_3'
|
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

4 Comments

this doesn't run on MacOs
on linux this is the best answer
It's worth mentioning that cat /proc/<PID>/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/<PID>/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.
I'd recode \0 to spaces for better readability: cat "/proc/$PPID/cmdline" | tr "\0" " "
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"

7 Comments

this is not working sed: /proc/97727/cmdline: No such file or directory
@artmees What files do you have if you cd to /proc/97727? And if you don't have that, do you have any pid directories in /proc?
i don't have /proc directory
@artmees What platform are you running on?
mac osx but it needs to run on both osx and linux
|
1

If you have /proc:

$(cat /proc/$PPID/comm)

2 Comments

Can you explain a bit more?
This is a UUOC. You can do a simple $(< /proc/$PPID/comm)
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.

Comments

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

Comments

0

As others have mentioned, the output of ps -o comm= $PPID is truncated to 15 characters. In another question the answer was

cut -d $'\0' -f 2 /proc/$PPID/cmdline

Obviously you must have /proc on your system. Note that the output of ps -o args= $PPID does not have 0x00 as separator.

You can then use, e.g. basename or readlink -f, to maybe add more sense to the calling filename.

Comments

-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

3 Comments

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.
please clarify your original question then, because that was not clear from your examples.
I do not think it is possible to obtain the calling program from within a bash script the way you want, I'm sorry.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.