0

I am developing a zsh script that uses read -k. If I execute my script like this (echo a | myscript), it fails to get input. Apparently it is due to the fact that -k uses /dev/tty as stdin invariably, and you must tell read to use stdin as in read -u0.

But then if I change it to -u0 (which makes previous case work) and execute my script without redirecting tty, it breaks the script, it simply does not behave as executing it without -u0.

EDIT: After debugging, it seems the issue is simply that after using -u0, the -k1 option does not read a single char and stops anymore. read works in this case as without -k, simply buffering all input and saving it as soon as an EOL arrives

EDIT2: After more debugging I know it's something related to the raw mode not working with -u0. If I add stty raw/cooked before my read then it works (except enter keystroke is now handled with \r not \n), but then when I execute it with non-tty stdin it breaks.

Is there any way to make both modes compatible?

Indeed I would like to understand why the script behaves different at all, if either I read with -u0 or not, fd0 is by default the same as /dev/tty

4
  • I will try to find a minimum sample, although the script does zillion things Commented Mar 11, 2024 at 9:32
  • I think I could refine the issue to the point of the real cause, my apologies for vague question Commented Mar 11, 2024 at 9:46
  • Withdrew previous comment, as I can't keep up with the editing. I usually test each addition to a script as I write it, not a zillion things at once. You should be able to detect whether stdin is a tty, file, or pipe, and choose a corresponding version of the read command within the script. Commented Mar 11, 2024 at 9:54
  • I do not have zsh installed, but both bash and zsh have a variable called - which contains "Flags supplied to the shell on invocation". In Bash, echo "$-" gives himBHs, and you can test if the shell is interactive with case "$-" in (*i*). Also, the prompt PS1 is empty in non-interactive Bash shells. You could test if either of these in zsh helps you discriminate between keyboard and piped input. Commented Mar 11, 2024 at 10:16

1 Answer 1

2

read -k (read N characters) and read -q (read y or n) have two modes of operation:

  • By default, they read from the terminal. They put the terminal in raw mode to read byte by byte (as many times as necessary to read the requested number of characters) rather than line by line.
  • They can be instructed to read from an existing file descriptor (-u with a number, or -p to read from the pipe used to communicate with the current coprocess). In this case, they just read from the file descriptor.

There's no option to tell zsh to read from a specific source, but change the terminal mode if reading from a terminal. You can arrange it yourself, though: check if standard input is a terminal, and don't pass -u0 if it is.

if [[ -t 0 ]]; then read -k1 … else read -k1 -u0 … fi 
4
  • Thanks! This is exactly how I am circunventing it at the moment, but I would like to understand why -k does not work with stdin. There must be a fundamental reason Commented Mar 11, 2024 at 17:19
  • @Whimusical -k without -u works on the terminal. -k with -u0 works on stdin and doesn't assume that it's a terminal. Separating the terminal actions from the choice of file descriptor is a nontrivial feature (read the code to see what's involved — look for OPT_ISSET(ops,'k') and then conditions on keys) that nobody has taken the pains of implementing. Commented Mar 11, 2024 at 18:16
  • I think what is happening is a mix of 2 of your insights: "They put the terminal in raw mode to read byte by byte (as many times as necessary to read the requested number of characters) rather than line by line." and "Separating the terminal actions from the choice of file descriptor is a nontrivial feature". When inputed as stdin (u0), read is assuming cooked mode or non-supporting raw mode, probably because it is not smart enough to identify whether tty is behind stdin. Therefore I conclude your solution is the only feasible nice solution. Commented Mar 11, 2024 at 21:03
  • The other one is using always read -k1 -u0 and using test -t 0 to conditionally enable stty raw + {read code logic} + stty cooked and changing all conditions on \n char to \r char Commented Mar 11, 2024 at 21:03

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.