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
:
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:
For clarity reasons I minimized the terminal window I ran the command from :)
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:
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:
and so on...
How to set up
The script needs both wmctrl
and xdotool
:
sudo apt-get install wmctrl xdotool
Copy the script below into an empty file, safe it as target_term
(no extension!) in ~/bin
(create the directory ~/bin
if necessary.
Make the script executable (don't forget) and either log out/in or run:
source ~/.profile
Now setup your terminal windows, with the number of required windows as an argument:
target_term -set <number_of_windows>
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.
The main problem is here:
read $input
In bash, usually, $foo
is the value of the variable foo
. Here, you don't want the value, but the name of the variable, so it should be just:
read input
Similarly, in the if
tests, $yes
and $no
should be just yes
and no
, since you just want the strings yes
and no
there.
You could use a case
statement here, which (IMHO) makes it easier to do multiple cases based on input:
case $input in
[Yy]es) # The first character can be Y or y, so both Yes and yes work
echo "Hello!"
echo "Hello!" | festival --tts
;;
[Nn]o) # no or No
echo "Are you sure?"
echo "Are you sure?" | festival --tts
;;
*) # Anything else
echo "Please answer yes or no."
echo "Please answer yes or no." | festival --tts
;;
esac
You could wrap the two echo
statements and the use of festival
in a function to avoid repeating yourself:
textAndSpeech ()
{
echo "$@"
echo "$@" | festival --tts
}
case $input in
[Yy]es) # The first character can be Y or y, so both Yes and yes work
textAndSpeech "Hello!"
;;
[Nn]o) # no or No
textAndSpeech "Are you sure?"
;;
*) # Anything else
textAndSpeech "Please answer yes or no."
;;
esac
With $input
, bash replaces this with its value, which is nothing initially, so the read
command run is:
read
And read
by default stores the input in the variable REPLY
. So you can, if you want, eliminate the input
variable altogether and use $REPLY
instead of $input
.
Also have a look at the select
statement in Bash.
Best Answer
The equivalent of
command1 | command2
iscommand2 < <(command1)
This can be extended to three (or more) commands too.
command3 < <(command2 < <(command1))
However, as Oli suggested, although this may produce the same output, it isn't technically the same as a pipe.