0

My goal is to pipe options from one command to a function that generates a menu using select.

Shell select loop reads from stdin, which by default is linked to keyboard inputs, when you are in a pipe however, stdin becomes the stdout of the previous command (I may be oversimplifying here).

The bash help says that select completes when EOF is read, which is unfortunate, because if you read all options from the pipe, then select will not be able to open it, and return immediately.

This is what I would like to do, it does not work:

pipe_select() {
        select foo in $(cat <&0)
        do
                echo ${foo};
                break;
        done
}

echo "ga bu zo me" | pipe_select
jraynal
  • 507
  • 3
  • 10

3 Answers3

1

From another answer

pipe_select() {
        opts=$(cat <&0);
        select foo in ${opts}
        do
                echo ${foo};
                break;
        done < /dev/tty
}

echo "ga bu zo me" | pipe_select`

That's actually whant I wanted to do all along! :p

jraynal
  • 507
  • 3
  • 10
1

A more general approach, which allows spaces in select items

#!/usr/bin/env bash

pipe_select() {
    readarray -t opts
    select foo in "${opts[@]}"
    do  
        echo ${foo};
        break;
    done < /dev/tty
}

printf "%s\n" ga bu zo me | pipe_select
printf "%s\n" "option 1" "option 2" | pipe_select
Philippe
  • 20,025
  • 2
  • 23
  • 32
  • Thanks, I had not thought about using [`readarray` / `mapfile`](https://www.gnu.org/software/bash/manual/html_node/Bash-Builtins.html#index-mapfile) in that way. In my real-world case, I get a JSON payload via `curl` then use `jq` to parse it inside my `select` function. – jraynal Feb 13 '20 at 22:15
  • 1
    readarray is simpler than cat/exec. – Philippe Feb 13 '20 at 22:19
  • Note that readarray is not available in the current default OSX version of bash (https://stackoverflow.com/q/61647118/411282) or in zsh (https://unix.stackexchange.com/a/710369/77539) – Joshua Goldberg Jan 04 '23 at 19:52
0

The solution I found is the following:

pipe_select() {
        opts=$(cat <&0);
        exec <&3 3<&-;
        select foo in ${opts}
        do
                echo ${foo};
                break;
        done
}

exec 3<&0
echo "ga bu zo me" | pipe_select

I have a command "generating" options, I use exec to "save" my stdin from the vile corruption of the pipes. When pipe_select is called, I parse the output of the previous command, then I "reset" the stdin for select to read. Everything else is just standard select behavior after that.

jraynal
  • 507
  • 3
  • 10