5

I need to write a script that will launch a terminal emulator and run a particular command in it. Since I don't know which terminal emulators have been installed on the target system, how could I find which terminal emulators have been installed?

I've looked at this thread. However, I'm not really very inclined to make a hardcoded check, and I'm searching for a generic solution.

I've come up this very dirty hack, but I'm really not sure how this would fare:

termdetect:

#!/bin/bash

IFS=:
for i in $PATH; do
  find "$i" -type f -exec isterminal {} \;
done

isterminal:

#!/bin/bash

if [[ ! -z "$1" ]]; then
  if [[ "$(head -c 4 "$1")" == $'\x7fELF' ]]; then
    if ! grep editor "$1" > /dev/null; then
      if grep -E '(vte_terminal_|libutempter|rxvt_)' "$1" > /dev/null; then
        echo "$1"
      fi
    fi
  fi
fi

Is there an easy generic solution that I'm perhaps missing? Or do I have no other way than to scour binaries for hints or use a hardcoded list?

  • This looks like it would have plenty of false positives. – Gilles 'SO- stop being evil' Jun 18 '14 at 21:46
  • @Gilles, I'm aware of the possibility of false positives, that's why I asked for a better solution. Nevertheless, you (and other readers) are welcome to report on the effectiveness of the stated solution. –  Jun 19 '14 at 06:26

2 Answers2

3

Your heuristics are pretty bad. Here's what they find on a Debian wheezy machine of mine (I've manually marked correct hits with a +).

/usr/bin/gcm-calibrate
/usr/bin/gnome-terminal        +
/usr/bin/mosh-client   
/usr/bin/mosh-server
/usr/bin/mrxvt                 +
/usr/bin/mrxvt-full            +
/usr/bin/roxterm               +
/usr/bin/rxvt-unicode          +
/usr/bin/urxvt                 +
/usr/bin/urxvtd
/usr/bin/vinagre
/usr/bin/x-terminal-emulator   +
/usr/bin/xfce4-terminal        +
/usr/bin/xterm                 +

If you pick the first alphabetic match, it's a color calibrator.

Here's the list of alternatives for x-terminal-emulator, which is the correct answer (as far as distribution-managed programs is concerned) on this Debian machine:

/usr/bin/Eterm
/usr/bin/gnome-terminal.wrapper
/usr/bin/koi8rxterm
/usr/bin/konsole
/usr/bin/lxterm
/usr/bin/mlterm
/usr/bin/mrxvt-full
/usr/bin/roxterm
/usr/bin/rxvt-xpm
/usr/bin/rxvt-xterm
/usr/bin/urxvt
/usr/bin/uxterm
/usr/bin/xfce4-terminal.wrapper
/usr/bin/xterm
/usr/bin/xvt

You're finding 7 out of 15 (accounting for vagaries of names), which isn't a very good hit rate.

You could try to refine your heuristics, but it's a never-ending game. The minimum would be to retain only executables that both link against some X11 libraries and call some master terminal function or utility like getpt or grantpt, but that's bound to find a lot of spurious positives too, and to miss executables where the call is in a library (like konsole). And this will miss native code executables that are accessed through a shell wrapper (there are several in my list).

Detecting whether a program is an X terminal emulator from the executable content is hopeless.

Detecting programs by name is a far better heuristic.

Note also that different programs have a different command line syntax. Most of them accept -T TITLE and -e COMMAND, but they have different parsing rules for -e (going through a shell or not, taking multiple arguments or not) so you should pass a single parameter after -e, not containing any shell special characters.

On Debian and derivatives, x-terminal-emulator is guaranteed to be an X terminal emulator that supports -T and -e, such that -e uses all subsequent command line parameters as a command and its argument and doesn't do shell expansion on them. Because some emulators behave differently, x-terminal-emulator is sometimes a wrapper that takes a more xterm-like syntax (gnome-terminal.wrapper and xfce4-terminal.wrapper are two examples of this).

I don't know of anything like this on other unix variants. For anything like portability, a hard-coded list of program names is your best bet.

Furthermore, detecting installed terminal emulators won't give you any idea about the user's preferences. For example, if I use Gnome, I probably don't want your application to launch Konsole — unless I happen to prefer Konsole, but how would you know that?

The de facto standard for user preferences is via FreeDesktop.org specifications. Freedesktop publishes an xdg-utils package which contains programs such as xdg-open to open a document in the proper application, xdg-email to launch the user's preferred email composing application, and more to the point xdg-terminal to open the user's preferred terminal emulator.

This would make xdg-terminal the right thing to use, and you should use it if it's there. Unfortunately, xdg-terminal isn't available everywhere; for example, Debian doesn't ship it (it's a long-idle bug). Furthermore xdg-terminal allows a single command argument, and is not consistent about what kind of expansion this argument undergoes.

Whatever you do, make sure to provide an easy way for the caller to specify a preference. Even xdg-terminal doesn't always get it right.

Thus your best strategy is:

  1. if the user has specified a preference, use it
  2. else try xdg-terminal
  3. else try x-terminal-emulator
  4. else try a hard-coded list
2

On Debian and derivatives (Ubuntu, Mint, …), x-terminal-emulator will always launch the administrator's default graphical terminal.
You can even include something like this to determine if it's a graphical session:

if [ -z "$XAUTHORITY" ]
then 
 echo "Failed to launch terminal emulator! This is not a graphical session."
else
 x-terminal-emulator
fi
psimon
  • 1,212
  • 2
    x-terminal-emulator is absent on a large number of distros, like CentOS and Arch Linux. –  Jun 18 '14 at 10:24
  • As I know, this is the only way to go. Have you seen this question on the topic? Maybe including an if statement to determine the distribution and only go for your dirty hack if this method doesn't work on that distribution. – psimon Jun 18 '14 at 10:28