Finding the terminal your script is running in - Programming On Unix
vain
This is question that came up in a german board: https://forum.ubuntuusers.de/topic/pruef...mein-srip/

I suspect the original author will be satisfied with a pragmatic solution. I'm forwarding the question to this forum, though, because I hope we can come up with something nice and clever. :) Just for fun.

The goal is to find the PID of the terminal your script is running in. Turns out, this is not as easy as it sounds. The naïve approach is to traverse the process tree. The parent process of your script is ... yeah, what is it? Most likely an interactive shell. But it could also be the terminal you're looking for if you started your script using something like "xterm -e foo.sh". On the other hand, if you launched the script from inside of Vim, there's an additional intermediate process.

Then there's SSH.

Then there's screen and tmux.

Then there's screen over SSH.

Then there's the Linux VT where the terminal emulator is not an actual process.

And so on. You get the idea.

That's what I got so far:

Code:
#!/bin/bash

find_parent_terminal()
{
    pid=$1
    comm=$(ps --no-headers -p $pid -o comm)

    echo Checking process $pid, "'$comm'"

    case $comm in
        sshd)
            echo Looks like I\'m running via SSH, PID $pid, "'$comm'"
            return
            ;;
        login)
            echo Looks like I\'m running on a Linux VT, PID $pid, "'$comm'"
            return
            ;;
    esac

    for fd in /proc/$pid/fd/*
    do
        if [[ "$(readlink -e -- "$fd" 2>/dev/null)" == /dev/ptmx ]]
        then
            echo Determined PID $pid as my terminal, "'$comm'"
            return
        fi
    done

    ppid=$(ps --no-headers -p $pid -o ppid)
    if (( ppid == 1 ))
    then
        echo Reached PID 1, could not find a terminal candidate
    else
        find_parent_terminal $ppid
    fi
}

find_parent_terminal $BASHPID

The idea is to traverse the process tree and look for clues. Terminal emulators usually hold an open file descriptor to /dev/ptmx, so that's what I try to find.

I used a Bash script, but any common language is fine.

Ideally, the solution runs on *BSD as well, but I'd be happy to settle for just Linux.
venam
This problem is captivating.

The more I think about it the more it drives me close to your approach: Recursively checking if the parent writes to the pseudo terminal master.

Maybe you can swap the readlink part with an `lsof /dev/ptmx | grep " $pid "`, instead of going through every processes opened file descriptors you go through the file descriptors of ptmx and check if it's there.
Or then you could also do `lsof -p $pid | grep '/dev/ptmx'`
Then continue the recursion.

I'm still sceptical of the edge cases, namely the sshd, login, etc..
There ought to be more than that.

sshd holds a file descriptor to /dev/ptmx, it's the endpoint in that case.
Let's say you have something else that is the "endpoint" and isn't a terminal but with our way it'll be listed as a terminal.
We need other features specific to a terminal.
If we remove the console, maybe we could presuppose the terminal is running in a graphical session.
But then again, maybe ssh could be thought of as a terminal.

I tried with a terminal multiplexer, they lead back to the right terminal, so no issues on that part.
vain
(03-07-2016, 01:56 AM)venam Wrote: lsof

Good point, that would also make it a little more portable.

(03-07-2016, 01:56 AM)venam Wrote: I tried with a terminal multiplexer, they lead back to the right terminal, so no issues on that part.

Actually, that got me thinking. Is that correct? GNU screen does indeed hold an open fd to /dev/ptmx, but my method above can't find it:
Code:
$ ls -al /proc/14731/fd
ls: cannot open directory '/proc/14731/fd': Permission denied
$ sudo !!
sudo ls -al /proc/14731/fd
total 0
dr-x------ 2 root root   0 Jul  3 16:58 .
dr-xr-xr-x 9 root users  0 Jul  3 16:58 ..
lr-x------ 1 root root  64 Jul  3 17:00 0 -> /dev/null
l-wx------ 1 root root  64 Jul  3 17:00 1 -> /dev/null
l-wx------ 1 root root  64 Jul  3 17:00 2 -> /dev/null
lrwx------ 1 root root  64 Jul  3 16:58 3 -> /dev/pts/3
lrwx------ 1 root root  64 Jul  3 17:00 4 -> 'socket:[1072739]'
lrwx------ 1 root root  64 Jul  3 17:00 5 -> /run/utmp
lrwx------ 1 root root  64 Jul  3 17:00 6 -> /dev/ptmx

Maybe the original problem is not defined very well. What is "the" terminal? Is it supposed to be screen or the xterm that runs screen?

Yes, let's indeed ignore the Linux console for now. It only complicates things. :)

From what I understand, terminal emulators call `openpty()` which gets them a pair of connected file descriptors. The "slave" end is something like `/dev/pts/4` and this is what the shell and other programs see. So, what I'm really looking for would be the "master" end. Meaning, if you run a multiplexer, then that's "the" terminal because this process originally called `openpty()`. (So my script gives the wrong answer, due to missing permissions.)

It's simple to find the slave end because that's STDIN of my script. But how to find the process which holds the corresponding (!) master end? Is that even possible? :/ Even worse: In theory, there could be multiple matching processes because that master file descriptor could be inherited or passed on to other processes (even though that's very unlikely). Traversing the process tree may be a pretty good guess, but it's not necessarily correct.

Whew, this turned out to be way more complicated than I thought.
venam
(03-07-2016, 12:41 PM)vain Wrote:
Quote:I tried with a terminal multiplexer, they lead back to the right terminal, so no issues on that part.
Actually, that got me thinking. Is that correct? GNU screen does indeed hold an open fd to /dev/ptmx, but my method above can't find it:
Code:
$ ls -al /proc/14731/fd
ls: cannot open directory '/proc/14731/fd': Permission denied
$ sudo !!
sudo ls -al /proc/14731/fd
total 0
dr-x------ 2 root root   0 Jul  3 16:58 .
dr-xr-xr-x 9 root users  0 Jul  3 16:58 ..
lr-x------ 1 root root  64 Jul  3 17:00 0 -> /dev/null
l-wx------ 1 root root  64 Jul  3 17:00 1 -> /dev/null
l-wx------ 1 root root  64 Jul  3 17:00 2 -> /dev/null
lrwx------ 1 root root  64 Jul  3 16:58 3 -> /dev/pts/3
lrwx------ 1 root root  64 Jul  3 17:00 4 -> 'socket:[1072739]'
lrwx------ 1 root root  64 Jul  3 17:00 5 -> /run/utmp
lrwx------ 1 root root  64 Jul  3 17:00 6 -> /dev/ptmx

Oh, I also had to use sudo.
So the permission is an issue too.

(03-07-2016, 12:41 PM)vain Wrote: From what I understand, terminal emulators call `openpty()` which gets them a pair of connected file descriptors. The "slave" end is something like `/dev/pts/4` and this is what the shell and other programs see. So, what I'm really looking for would be the "master" end. Meaning, if you run a multiplexer, then that's "the" terminal because this process originally called `openpty()`. (So my script gives the wrong answer, due to missing permissions.)

It's simple to find the slave end because that's STDIN of my script. But how to find the process which holds the corresponding (!) master end? Is that even possible? :/ Even worse: In theory, there could be multiple matching processes because that master file descriptor could be inherited or passed on to other processes (even though that's very unlikely). Traversing the process tree may be a pretty good guess, but it's not necessarily correct.


It also got me thinking into the definition of what is a terminal.

If you write a terminal that works using two parts, client and server, which one do you consider to be the terminal?
Probably the server but there's no direct interaction with the server.

Quote:The role of the terminal emulator process is:

to interact with the user.
to feed text input to the master pseudo-device for use by the shell (such as bash), which is connected to the slave pseudo-device).
To read text output from the master pseudo-device and show it to the user.




Members  |  Stats  |  Night Mode  |  Help