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).

iconoclast
  • 9,198
  • 13
  • 57
  • 97
GJ.
  • 153
  • The answer is here: http://unix.stackexchange.com/questions/26676/how-to-check-if-a-shell-is-login-interactive-batch – Spajus Jul 25 '15 at 03: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 03: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 04: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 04: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 04:46
  • @MarkPlotnick - how is that affected by, for example, sh -cm 'bash script' ? – mikeserv Jul 25 '15 at 05:47
  • Never mind, I was wrong, off by one. You'd actually want the parent of the pgrp leader, as described in the answers, not the pgrp leader that I suggested. – Mark Plotnick Jul 26 '15 at 10:34

2 Answers2

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.

mikeserv
  • 58,310
  • 1
    The OP want to check parent process of current process, not the current process itself. – cuonglm Jul 25 '15 at 04: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 05: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 05: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
  • @Gj - please see the edit here regarding pgid. – mikeserv Jul 26 '15 at 00:02
  • @mikeserv Hi Mike, I'm not sure if I understand what you tried to say in your edit. But when I tried this command: ps -p $$ -opgid=;sh -cm 'ps -p $$ -opgid=;sh -c "echo $PPID $$ \"\$$\";ps -p $$ -opgid="', it output: 2692\n7731\n2692 7731 7733\n7731. This is working as expected as the pgid for the two sh are the same: 7731; while the pgid for the interactive shell is 2692. Thus, the find-first-process-with-different-pgid approach works. – GJ. Jul 26 '15 at 01:16
  • @Gj - no, that's not what's happening. You're seeing 2692 as output from the echo $PPID - and that is not the $PPID of the inner sh - that's the $PPID of the outer one, because it is evaluated in the sh -cm shell. The pgid for the outer shell is 2692 and the pgid for the inner is 7731. No wait - the pgid for both is 7731 - and that is pid of the sh -cm shell not the interactive shell which launched them. – mikeserv Jul 26 '15 at 01:25
  • @mikeserv yes, the pgid for sh -cm(the outer non-interactive shell) and sh -c(the inner non-interactive shell) are both 7731, which is the pid of the outer non-interactive shell. The interactive shell launching the outer non-interactive shell has the pgid of 2692. 2692 != 7731. I only need to confirm that they have different pgid. And yes, they do! – GJ. Jul 26 '15 at 02:07
  • @Gj - Will you please look again? – mikeserv Jul 26 '15 at 04:57
  • @mikeserv Thanks for your explanation! Hmm, so pgid is not reliable. – GJ. Jul 26 '15 at 14:43