一般的な質問

一般的な質問

一般的な質問

パイプラインチェーンの途中にあっても、ユーザーと対話するスクリプトを作成したいと思います。

具体例

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

私が望むように行動してください。ただし、上記の例のようにパイプを接続すると、表示されたオプションが印刷され、selectorgrepmyfile.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出力をnlwhichに渡し、各行に番号を付け、その出力をteewhichにコピーしてtostdoutおよびに渡します/dev/tty。これは、パイプの出力が作業中に関数の引数配列(行に分割)と\n端末の両方に印刷されることを意味します。

次に、関数は前の操作で最大5回までの結果が1つ以上あるかどうか_in()を選択しようとします。read選択には、スペースで区切られた数字またはスペースで区切られた数字の範囲のみを含めることができます-。他のものがあればread (空行を含む)もう一度やり直しますが、以前と同様に最大5回しか試すことはできません。

最後に、関数_out()はユーザーの選択を解析し、その中の範囲を拡張します。結果はそれぞれの形式で印刷されるため、"${[num]}"arg配列に格納されている行の値と一致します。inf()この出力はevalargsで編集されるため、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

関連情報