2

I know that one can use the $- contains i or not or check if $PS1 is empty to tell if a shell is interactive.

However, those solutions only work for the current shell.

I have a bash script trying to find the immediate parent shell that is interactive.

For example:

  1. interactive shell: zsh
  2. bash script 1 which executes bash script 2
  3. bash script 2 contains the mechanism to find out the immediate interactive shell

So when we execute bash script 1 under our interactive shell zsh, we expects an output of zsh.

I cannot figure out how I can do it, when the script is running in the sub-shell.

Note

I want the script to be executed, not sourced.

Clarify

I have a bash script trying to find the first ancestor shell that is interactive.

By first ancestor, I meant that the first interactive shell we encounter during the bottom-up process-chain scanning process.

For example, in the case of: zsh(first interactive shell) -> bash(second interactive shell) -> bash(batch shell for script 1) -> bash(batch shell for script 2), we want to output bash(the second interactive shell).

  • The answer is here: unix.stackexchange.com/questions/26676/… – Spajus Jul 25 '15 at 3:45
  • @Spajus I already stated in my question that those(and similar) solutions only work for current shell. I'm looking for a solution which work from within a script. – GJ. Jul 25 '15 at 3:51
  • To clarify: you want a way to look up through the chain of parent processes and a) if the process is not (sh or bash or ksh or pdksh or mksh or ash or dash or zsh or fish or csh or tcsh), stop, else b) if shell is interactive, return its name, else c) go to parent and repeat. – Mark Plotnick Jul 25 '15 at 4:26
  • @MarkPlotnick Hi you are right. I have the loop scanning logics done(which is easy), I just lack of this core interactive-shell-identification mechanism for the script to work. – GJ. Jul 25 '15 at 4:29
  • @MarkPlotnick Does this work for this case: zsh(interactive) -> bash(interactive) -> bash(batch, for script1) -> bash(batch, for script2). The expected output should be bash(the second interactive)? – GJ. Jul 25 '15 at 4:46
3

This is a really weird requirement. Why would you care what interactive shell invoked your script, or invoked some other program that isn't an interactive shell which in turn invoked your script? This has a very strong smell of a XY problem.

IF you really need to know, you can try to figure it out, but I don't think there's a fully reliable method, just one that works in typical cases.

Starting from $PPID, trace your script's ancestor processes (ps -o ppid= -p $ancestor_pid) until you either find the one that you're looking for, or one that indicates you've gone too far.

A simple strategy is to look for a process in a different process group (ps -o pgid= -p $ancestor_pid). Under normal circumstances, if your script was invoked by (a script that was invoked by) an interactive shell, that process you've reached is a shell with job control, which ran your script('s parent script) in a separate process group.

Here are some examples of things that can go wrong with this strategy:

  • One of the processes on that chain is already dead.
  • Your script was not invoked through an interactive shell, but through a cron job, from an X11 program, etc.

You may or may not want check that this process's standard input, standard output and standard error (e.g. with lsof, or via /proc if you don't need portability beyond Linux) are the same terminal as your script. It depends how you want to handle cases such as

bash$ xterm -e your_script
  • I tried your approach, I think the pgid is really what I'm looking for. So what I do is that I get the pgid in the script, and start scanning the process chain. The first process with a different pgid encountered should be the process(the interactive shell) I'm looking for. Thanks! – GJ. Jul 25 '15 at 23:31
2

You check its options.

[ "$-" = "${-#*i}" ] ||
echo shell is interactive

You can also check its file descriptors. This is a little different. It won't necessarily tell you if the shell is interactive per se, but it will tell you if it's talking to a terminal.

for fd in 0 1 2
do     [ -t "$fd" ] && 
       break 
done|| echo shell fds 0 1 2 are not connected to a terminal.

We can hack at that a little bit in an attempt to discover all of the using processes of our terminal.

tty_users()
    for fd in 0 1 2 "$@"
    do     [ -t "$fd" ] && {
           fuser "$(tty)"
           break; } <&"$fd"
    done 

Which will run in the current shell and print a list of process ids for those processes running on the first detected terminal as associated with std(in|out|err) (by default - pass the function numeric arguments to test others) respectively. It sets the shell var $fd to the file descriptor number associated with that terminal, and returns false if none of the standard descriptors or any arguments are associated with a terminal.

The above is probably as close as you'll get unless you look for the session id of your terminal - if you have one.

ps -osid= -p"$$"

That will return to you the pid of the owner of your controlling terminal - if you have one.

To demonstrate:

echo "$$"; sh -c 'sh -c "ps -osid= -p\"\$$\""' 

6023
6023

But you cannot rely on any of this stuff. Not really at all. Look:

sh -acm 'IFS=\; i=0;eval "$0"'          \
        '[ "$i" -lt 5 ] && eval "$*"'   \
        'ps -opid -opgid -p"$$"'        \
        'sh -acm "$0" "$0" "$@" i=$((i+=1))'
  PID  PGID
28766 28766
  PID  PGID
28768 28768
  PID  PGID
28770 28770
  PID  PGID
28772 28772
  PID  PGID
28774 28774

You see? The interactive shell from which I run those processes is not even in that list. Each sh launches a child of its own until a depth of 5 is reached, and as each one does it calls ps to print its PID and its PGID. Every process gets a new PGID. Job control is just as related to an interactive shell as a terminal is, it's just that the terminal is the more direct way to test for it.

  • 1
    The OP want to check parent process of current process, not the current process itself. – cuonglm Jul 25 '15 at 4:46
  • @mikeserv Hi Mike, your approach won't work for this case: zsh(interactive) -> bash(interactive) -> bash(batch, for script1) -> bash(batch, for script2). The expected output should be bash(the second interactive) – GJ. Jul 25 '15 at 5:19
  • 1
    @Gj - It's like I said - you're not gonna find that value that way. And if you do it will be hacky. You're going the wrong way. Child processes are not supposed to get smart. They do what they're told. And so if your parent process tells you: (PPID_INTER=TRUE bash script2) then you'll know. If it doesn't then you probably won't, because it's not your business. – mikeserv Jul 25 '15 at 5:28
  • I think ps T will do the same thing as tty_users? – GJ. Jul 25 '15 at 22:57
  • @Gj - no, not exactly. tty_users doesn't open any outside processes unless it has to, and you can redirect its std descriptors and hand it arguments to test others. It also puts the device name on stderr and the pid list on stdout - and so they can be captured differently. And there's the return, and the setting of $fd. So - it's close, but not identical. Also, that's only about the controlling terminal. As @Gilles hints below, the session terminal and terminal on your fd don't necessarily have to be one and the same. – mikeserv Jul 25 '15 at 23:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.