一般的な質問
パイプラインチェーンの途中にあっても、ユーザーと対話するスクリプトを作成したいと思います。
具体例
file
具体的には、ORを使用して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
grepmyfile.tmp
入力から選択を読み取ろうとします。
私の方法
私は次のような-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
。シェル・グロップを使用して読み取るファイルを指定する場合は、グロブ・シーケンスの最初の項目から始まり、最後の一致で終わるすべてのファイルのすべての一致を返します。
grep
出力をnl
whichに渡し、各行に番号を付け、その出力をtee
whichにコピーしてtostdout
およびに渡します/dev/tty
。これは、パイプの出力が作業中に関数の引数配列(行に分割)と\n
端末の両方に印刷されることを意味します。
次に、関数は前の操作で最大5回までの結果が1つ以上あるかどうか_in()
を選択しようとします。read
選択には、スペースで区切られた数字またはスペースで区切られた数字の範囲のみを含めることができます-
。他のものがあればread
(空行を含む)もう一度やり直しますが、以前と同様に最大5回しか試すことはできません。
最後に、関数_out()
はユーザーの選択を解析し、その中の範囲を拡張します。結果はそれぞれの形式で印刷されるため、"${[num]}"
arg配列に格納されている行の値と一致します。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
ただし、提供するすべてのオプションと提供できるファイル名を好きなだけ使用できます。つまり、1つを除いて何でも使用できます。入力を解析する副作用で空行を$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