一般问题
我想编写一个与用户交互的脚本,即使它位于管道链的中间。
具体例子
具体来说,它需要一个file
或stdin
,显示行(带有行号),要求用户输入选择或行号,然后将相应的行打印到stdout
。我们称这个脚本为selector
。基本上,我希望能够做到
grep abc foo | selector > myfile.tmp
如果foo
包含
blabcbla
foo abc bar
quux
xyzzy abc
然后selector
向我提供选项(在终端上,而不是在myfile.tmp
!)
1) blabcbla
2) foo abc bar
3) xyzzy abc
Select options:
之后我输入
2-3
并最终得到
foo abc bar
xyzzy abc
作为 的内容myfile.tmp
。
我已经启动并运行了一个选择器脚本,如果我不重定向输入和输出,它基本上可以正常工作。所以
selector foo
表现得像我想要的。但是,当像上面的示例一样将事物通过管道连接在一起时,selector
将显示的选项打印到myfile.tmp
并尝试从 grep 输入中读取选择。
我的方法
我尝试使用-u
的标志read
,如
exec 4< /proc/$PPID/fd/0
exec 4> /proc/$PPID/fd/1
nl $INPUT >4
read -u4 -p"Select options: "
但这并没有达到我希望的效果。
问:如何获得实际的用户交互?
答案1
使用/proc/$PPID/fd/0
是不可靠的:进程的父selector
进程可能没有终端作为其输入。
有一个标准路径它总是指当前进程的终端:/dev/tty
。
nl "$INPUT" >/dev/tty
read -p"Select options: " </dev/tty
或者
exec </dev/tty >/dev/tty
nl "$INPUT"
read -p"Select options: "
答案2
我编写了一个小函数:它不会回答您所询问的管道链接问题,但会解决您的问题。
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)"
)
该函数将立即将所有参数转交给grep
。如果您使用 shell glob 来指定应从中读取的文件,它将返回所有文件中的所有匹配项,从 glob 顺序中的第一个开始,到最后一个匹配结束。
grep
将其输出传递给nl
which,对每行进行编号,并将其输出传递给tee
which,将其输出复制到tostdout
和to /dev/tty
。这意味着管道的输出会同时打印到函数的参数数组(按行分割)\n
和工作时的终端。
接下来,如果前一个操作最多五次至少有 1 个结果,则该_in()
函数会尝试进行选择。read
选择只能包含由空格分隔的数字,或者由 分隔的数字范围-
。如果还有什么的话read
(包括空行)它会再试一次 - 但和以前一样,最多只能尝试五次。
最后,该_out()
函数解析用户的选择并扩展其中的任何范围。它以每个的形式打印结果- 从而匹配存储在arg 数组"${[num]}"
中的行的值。inf()
此输出被eval
作为 args 编辑,printf
因此仅打印用户选择的行。
它明确地read
来自终端,并且仅打印菜单Select:
,stderr
因此它对管道非常友好。例如,以下作品:
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
但是您可以使用您提供的任何选项grep
以及您可能提供的任意数量的文件名。也就是说,您可以使用除一种之外的任何一种 - 作为其解析输入的副作用,$IFS
如果您正在搜索空行,它将不起作用。但谁愿意从空白行的编号列表中进行选择呢?
最后请注意,因为这是通过直接将数字用户输入转换为存储在函数参数数组中的数字位置参数来工作的,所以输出将是用户选择的任何内容,用户选择它的次数与用户选择它的顺序无关。
例如:
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