I have written a small function: it will not answer about what you have asked chaining of pipe but will solve your problem.
inf() ( [ -n "$ZSH_VERSION" ] && emulate sh
unset n i c; set -f; tab=' ' IFS='
'; _in() until [ "$((i+=1))" -gt 5 ] && exit 1
printf '\nSelect: '
read -r c && [ -n "${c##*[!- 0-9]*}" ]
do echo "Invalid selection."
done
_out() for n do i=; [ "$n" = . ] &&
printf '"${%d#*$tab}" ' $c ||
until c="${c#*.} ${i:=${n%%-*}}"
[ "$((i+=1))" -gt "${n#*-}" ]
do :; done; done
set -- $(grep "$@"|nl -w1 -s "$tab"|tee /dev/tty)
i=$((($#<1)*5)); _in </dev/tty >/dev/tty
eval "printf '%s\n' $(c=$c\ . IFS=\ ;_out $c)"
)
The function turns over all arguments you give it immediately to grep
. If you use a shell glob to specify the files it should read from it will return all of the matches in all files, starting with the first in the glob order and ending with the last match.
grep
passes its output to nl
which numbers each line and which passes its output to tee
which duplicates its output both to stdout
and to /dev/tty
. This means that the output from the pipeline is simultaneously printed both to the function's argument array where it is split on \n
ewlines and to the terminal as it works.
Next the _in()
function attempts to read
in a selection if there is at least 1 result from the previous action a maximum of five times. The selection can consist of only numbers separated by spaces, or else number ranges separated by -
. If anything else is read
(including a blank line) it will try again - but only, as before, a maximum of five times.
Last the _out()
function parses the user's selection and expands any ranges therein. It prints its results in the form "${[num]}"
for each - thereby matching the value of the lines stored in inf()
's arg array. This output is eval
ed as args to printf
which therefore prints only the lines the user has selected.
It explicitly read
's from the terminal and only prints the Select:
menu to stderr
and so it is plenty pipeline friendly. For example, the following works:
seq 100 |inf 3|grep 8
1 3
2 13
3 23
4 30
5 31
6 32
7 33
8 34
9 35
10 36
11 37
12 38
13 39
14 43
15 53
16 63
17 73
18 83
19 93
Select: 6 9 12-18
38
83
But you can use any options you would give grep
and any number of filenames you might hand it as well. That is, you can use any but one kind - as a side-effect of its parsing input with $IFS
it will not work if you are searching for blank lines. But who would want to select from a numbered list of blank lines?
Last note that because this works by directly translating numeric user input into the numeric positional parameters stored in the function's argument array, then the output will be whatever the user selects, as many times as the user selects it, and in whatever order the user selects it.
For example:
seq 1000 | inf 00\$
1 100
2 200
3 300
4 400
5 500
6 600
7 700
8 800
9 900
10 1000
Select: 4-8 1 1 3-6
400
500
600
700
800
100
100
300
400
500
600
cmd | { some processing; read var </dev/tty; } | cmd
alias selector='{ TMPFILE=$(mktemp); cat > $TMPFILE; nl -s") " $TMPFILE | column -c $(tput cols); read -e -p"Select options: " < /dev/tty; rangeselect -v range="$REPLY" $TMPFILE; rm $TMPFILE; }'
which works pretty good. Howevergrep b foo | selector | wc -l
breaks over here. Any ideas how to fix that? By the way, therangeselect
that I used can be found at pastebin.com/VAxTSSHs. It is a simple AWK script that prints the lines of a file corresponding to a given range of linenumbers. (Ranges can be things like "3-10, 12,14,16-20".)alias
that, ratherselector() { all of that stuff...; }
into a function.alias
es rename simple commands whereas functions pack a compound command into a single simple command.