Reputation: 22356
An interactive program running in an xterm can send control codes to the xterm, which cause the xterm to respond by sending back an answer. I would like to read the answer. There are several of these control codes, so for the sake of this discussion, let's stick with the control sequence ESC Z, which causes the xterm to send back its terminal id. For instance, when I type in my shell (I'm using Zsh, but as far I can see, this applies to bash as well)
echo -e '\eZ'
I see in my terminal buffer the string
63;1;2;4;6;9;15;22;29c
and hear a beep (because the answer sent from xterm also contains non-printable characters, in particular it also contains an ESC). My goal is to somehow read this string 63;1;2;4;6;9;15;22;29c into a shell variable. I mainly use Zsh, but a solution in bash would be welcome as well (or in any other scripting language, such as Ruby, Perl or Python).
My first attempt was pretty straightforward:
#!/bin/zsh
echo -e '\eZ'
read -rs -k 25
echo $REPLY | xxd
This works, because I found out (with a little bit of trial and error) that the answerback string in this particular example on my particular xterm has a length of 25 characters. In the general case, of course, I don't know the exact length in advance, so I wanted a more flexible solution. The idea would be to read one character at a time, until nothing is left, and I wrote the following program to test my idea:
#!/bin/zsh
str=''
echo -e '\eZ'
while :
do
read -rs -t -k
if [[ -z $REPLY ]]
then
echo '(all read)'
break
fi
str="${str}$REPLY"
done echo $str | xxd
However, this doesn't read anything. REPLY is always empty.
I also tried the variations read -rs -t -k 1
(same effect) and read -rs -k 1
(hangs forever). Even read -rs k 25
does not work anymore, so I guess the culprit is the while loop, not the read command. However, I do need a loop if I want to read the answerback string one character at a time.
Could someone explain, why my approach failed, and how I could solve my problem?
Upvotes: 3
Views: 829
Reputation: 4133
Putting the answers here together, here is the finished product:
#!/usr/bin/env bash
tty=/dev/tty
cat >$tty
read -rs -t 0.2 -d "" <$tty
echo $REPLY | xxd -r -p
$ printf '\eP+q544e\e\\' | escape_code_answer_read.bash
TNxterm-kitty
Upvotes: 0
Reputation: 22356
Since I hadn't got any new findings for this question here, I have crossposted the problem at Unix/Linux Forums and received an answer for Zsh, and an improved answer for bash, which I would like to summarize here:
Assuming that tty
contains the string denoting my terminal(following here the advice given by @ThomasDickey in his answer), the Zsh-command
read -rs -t 0.2 -k 1 <$tty # zsh
reads the next single character into the variable REPLY
. It is important to note that -t
must be given a timeout value, and the fact, that no character is available, can not be deduced from an empty REPLY
, but from the exit code of the read
command.
As for bash, there is an easier solution than the one I've posted in my comment: With
read -rs -t 0.2 -d "" <$tty # bash
the whole answerback string is read as once into REPLY
; no loop is necessary.
Upvotes: 2
Reputation: 54583
You could make the read-statement read directly from the terminal, e.g.,
read -rs -t -k < $(tty)
to work around redirection of the shell.
Upvotes: 2