Ubuntu – How to send commands to specific terminal windows

bashcommand linegnome-terminalscripts

I'd like to write a script for opening multiple programs (servers) simultanously in separate terminals – doesn't matter which one – and assign different commands to different terminals with commands "landing" inside the correct terminal. Is this possible?
Maybe, something like this:

  1. open terminal1
  2. open terminal2 //simultanously with 1.
  3. command1 //execute in terminal1 without opening a new terminal window
  4. command2 //execute in terminal2 without opening a new terminal window

Can I somehow label terminal windows so commands are executed inside correct terminal?

I'd also like to watch all terminals while their programs are running – my programs have an argument for printing trace/debug to terminal. So I'd like to see what messages are exchanged between them.

NOTE: I'm less concerned about security of exchanged data since this script should serve as a "simulation". I've configured each server to run from an allocated port on localhost.

Best Answer

Since you mention you solved the problem for your specific situation, below a solution for general purpose. Thanks to xdotool's --sync option, it works pretty reliable in the tests I ran; I could "send" commands to specific terminal windows and it ran perfectly without an exception.

How it works in practice

The solution exists from a script, which can be run with two options -set and -run:

  1. To set up (open) an arbitrary number of terminal windows, in this example 3:

    target_term -set 3
    

    Three new terminals will open up, their window id is remembered in a hidden file:

    enter image description here

    For clarity reasons I minimized the terminal window I ran the command from :)

  2. Now that I created three windows, I can send commands to either one of them with the run command (e.g.):

    target_term -run 2 echo "Monkey eats banana since it ran out of peanuts"
    

    As shown below, the command ran in the second terminal:

    enter image description here

    Subsequently, I can send a command to the first terminal:

     target_term -run 1 sudo apt-get update
    

    making sudo apt-get update run in terminal 1:

    enter image description here

    and so on...

How to set up

  1. The script needs both wmctrl and xdotool:

    sudo apt-get install wmctrl xdotool
    
  2. Copy the script below into an empty file, safe it as target_term (no extension!) in ~/bin (create the directory ~/bin if necessary.

  3. Make the script executable (don't forget) and either log out/in or run:

    source ~/.profile
    
  4. Now setup your terminal windows, with the number of required windows as an argument:

    target_term -set <number_of_windows>
    
  5. Now you can "send" commands to either one of your terminals with the command:

    target_term -run <terminal_number> <command_to_run>
    

The script

#!/usr/bin/env python3
import subprocess
import os
import sys
import time
#--- set your terminal below
application = "gnome-terminal"
#---

option = sys.argv[1]
data = os.environ["HOME"]+"/.term_list"

def current_windows():
    w_list = subprocess.check_output(["wmctrl", "-lp"]).decode("utf-8")
    w_lines = [l for l in w_list.splitlines()]
    try:
        pid = subprocess.check_output(["pgrep", application]).decode("utf-8").strip()
        return [l for l in w_lines if str(pid) in l]
    except subprocess.CalledProcessError:
        return []

def arr_windows(n):
    w_count1 = current_windows()
    for requested in range(n):
        subprocess.Popen([application])
    called = []
    while len(called) < n:
        time.sleep(1)
        w_count2 = current_windows()
        add = [w for w in w_count2 if not w in w_count1]
        [called.append(w.split()[0]) for w in add if not w in called]
        w_count1 = w_count2

    return called

def run_intterm(w, command):
    subprocess.call(["xdotool", "windowfocus", "--sync", w])
    subprocess.call(["xdotool", "type", command+"\n"]) 

if option == "-set":
    open(data, "w").write("")
    n = int(sys.argv[2])
    new = arr_windows(n)
    for w in new:
        open(data, "a").write(w+"\n")
elif option == "-run":
    t_term = open(data).read().splitlines()[int(sys.argv[2])-1]
    command = (" ").join(sys.argv[3:])
    run_intterm(t_term, command)

Notes

  • The script is set for gnome-terminal, but can be used for any terminal (or other program as well) by changing the application in the head section of the script:

    #--- set your terminal below
    application = "gnome-terminal"
    #---
    
  • The commands above can (of course) be run from a script as well in case you'd lile to use it for some kind of a simulation.
  • The script waits until both the targeted window has focus and the command is done typing, so the command will always land in the right terminal window.
  • No need to say that the script only works with the terminal setup (windows) that was called by the command:

    target_term -set
    

    The terminal windows will then be "labelled" by the script, like you mention in your question.

  • In case you start a new target_term session, the hidden file, created by the script will simply be overwritten, so there is no need to remove it otherwise.