0

It took me 10 hours of searching the net, and testing techniques to get the results that worked on any shell (#!/bin/sh).

In BASH this is relatively simple, because read can be told how many characters are to be grabbed, and if a delimiter is found it will not wait to exit.

stty -icanon -echo; echo -en "\033[6n"; read -d R -n 12 ESCPOS; stty "$x_TERM"; \
ESCPOS=`echo "$ESCPOS" | tail -c +3`; echo "$ESCPOS"

How to write a sh script version, compatible with any shell?

2 Answers2

4

Copied from https://unix.stackexchange.com/a/88327

Assuming your /bin/sh is a POSIX sh (on Solaris 10 and older which had a Bourne shell there instead, use /usr/xpg4/bin/sh instead):

if [ -t 0 ] && [ -t 1 ]; then
  old_settings=$(stty -g) || exit
  stty -icanon -echo min 0 time 3 || exit
  printf '\033[6n'
  pos=$(dd count=1 2> /dev/null)
  pos=${pos%R*}
  pos=${pos##*\[}
  x=${pos##*;} y=${pos%%;*}
  stty "$old_settings"
fi

That assumes the whole response will come in one go waiting for it for up to 0.3 seconds. That should be generally true for terminal emulators and pty devices, but not necessarily for terminals over serial. You could change it to min 8 time 3 to keep waiting (up to 0.3 second between each byte) until 8 bytes have been read, but with the drawback that it will take always at least 0.3 seconds if the answer is shorter than 8 bytes and would hang forever if there's no answer).

You could use awk -F'[^0-9]+' -v RS=R '{print $3, $2; exit}' with min 1 time 0. That would work with awk implementations other than mawk (which insists in accumulating a buffer full of data on input before starting to process it).

In the end, reading one byte at a time like you do in your own answer is the most reliable. You may want to add a timeout to account for terminals that don't send a response.

  • the whole point of script in OP and my answer is that they dont have to wait, and that they dont know how many bytes (chars) to get. – Paul Wratt Aug 30 '17 at 03:37
  • who keeps upvoting this. the copied answer is NOT the right answer, read ALL the comments – Paul Wratt Nov 24 '17 at 00:04
2

Note:

unlike the wrongly copied and continually upvoted answer provided
(points scoring?), the following script IS NON-BLOCKING, and does
not care what length returned input may be.  IE it will work with 
ANY screen size.

With SH it is more complex, and I was unable to find enhanced command line version of the built-in read, eventually I found a mention of dd on STDIN, here is the result. NOTE that the SH version of built-in echo does not permit the use echo -en although /bin/echo -en does work we use printf instead.

#!/bin/sh
x_TERM=`stty -g`
stty -icanon -echo
printf "\033[6n"
ESCPOS=""
X=""
I=0
while [ ! "$X" = "R" ]; do
  X=`dd bs=1 count=1 2>/dev/null`
  I=`expr $I + 1`
  if [ $I -gt 2 -a ! "$X" = "R" ]; then
    ESCPOS="$ESCPOS$X"
  fi
done
stty "$x_TERM"
#echo "$ESCPOS"
CSRLIN=`echo "$ESCPOS" | cut -d \; -s -f 1`
POS=`echo "$ESCPOS" | cut -d \; -s -f 2`
echo "$CSRLIN"
#exit 0 <= dont use inline

I used the same code in two differnt scripts, one outputs CSRLIN, the other POS.

EDIT: you need to inline this script to use it in another script (eg . CSRLIN, as the shell has to be in interactive mode.

Cheers

Paul

  • 1
    Note that POSIX states that implementations of echo shall not support any options, but results are implementation-defined if the first operand is -n or if any operand contains a backslash. A strictly conforming implementation could have echo -en "\033[6n" print the string -en \033[6n followed by a newline character (and FreeBSD's does exactly that). This is why one prefers printf to echo – Fox Aug 27 '17 at 04:54
  • -en \033[6n is what I got with BASH in SH mode. you can force in SH with /bin/echo -en "\033[6n" but what if the system does not have /bin. I believe its possible in SH mode to echo -ne "\033[6n" but again if its implementation dependent the script will fail, hence the use of printf here. – Paul Wratt Aug 27 '17 at 05:00
  • 1
    My comment was more to address the statement that "although/bin/echo -en does work" — the results are implementation defined in this case (because an operand contains backlash in your example). That is, it was mostly a warning to others describing why we prefer printf – Fox Aug 27 '17 at 05:07
  • 1
    For bash to be Unix compliant, you need to set the xpg_echo option in addition to the posix one (set when you run it as sh). On macOS (a Unix certified system), for /bin/sh, bash is built with the option enabled by default. A Unix echo on ASCII-based systems is required to output -en<SPC><ESC>[6n<LF>. It's unspecified per POSIX only (without XSI extension) because of the backslash as @Fox said. – Stéphane Chazelas Aug 27 '17 at 06:52
  • 1
    And for GNU echo (/bin/echo on GNU systems), the behaviour depends on whether POSIXLY_CORRECT is in the environment. See Why is printf better than echo? for more details. – Stéphane Chazelas Aug 27 '17 at 07:08
  • @StéphaneChazelas. You keep saying Unix this and Unix that. Unix is not UNIX and vise versa. One is a brand and one is class of operating systems. Neither are standards or specifications. You should refer to the appropriate standard or specification. – fpmurphy Nov 24 '17 at 03:56
  • @fpmurphy1, I can't say I cared much and I don't like the UNIX spelling as Unix is not an acronym (so it's like a "shouted" Unix, as opposed to a respectful "Unix", and I'm quite fond of Unix as you can probably tell), but you're right, I should probably use UNIX when I talk of conformance at least as that's the form chosen by the Open Group. I've updated some of my answers and will try to be careful about that in the future. The UNIX trademark is given to OSes that are certified to the Single UNIX Specification, that's what people refer to when they say UNIX compliance. – Stéphane Chazelas Nov 24 '17 at 09:13