パイプでスクリプトを使用しているときにユーザー入力を読み取る方法


10

一般的な問題

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

具体例

具体的には、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し、grepped入力から選択を読み取ろうとします。

私のアプローチ

次のように、の-uフラグを使用しようとしましread

exec 4< /proc/$PPID/fd/0
exec 4> /proc/$PPID/fd/1
nl $INPUT >4
read -u4 -p"Select options: "

しかし、これは私が望んでいたことをしません。

Q:実際のユーザーインタラクションを取得するにはどうすればよいですか?


スクリプトを作成し、出力を変数に保存してから、ユーザーに表示したいですか?
ハッカホリック2014年

@ハッカホリック—どういう意味かわかりません。あらゆる種類のパイプラインシーケンス(つまり、Unixの方法)に配置できるスクリプトが必要です。上記の入念な例を挙げましたが、私が念頭に置いている唯一のユースケースではありません。
jmc 2014年

1
使用cmd | { some processing; read var </dev/tty; } | cmd
mikeserv 2014年

@mikeserv —興味深い!私は今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; }'どれがかなりうまくいくかを持っています。ただしgrep b foo | selector | wc -l、ここで中断します。それを修正する方法はありますか?ちなみに、rangeselect私が使用したものはpastebin.com/VAxTSSHsにあります。これは、指定された範囲の行番号に対応するファイルの行を出力する単純なAWKスクリプトです。(範囲は「3-10、12、14、16-20」のようなものにすることができます。)
jmc

1
ないaliasということを、selector() { all of that stuff...; }関数に。aliasesは単純なコマンドの名前を変更しますが、関数は複合コマンドを単一の単純なコマンドにパックします
mikeserv 2014年

回答:


8

使用/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: "

1
ありがとう、それで私の問題は解決しました。答えは少しミニマルですが。質問へのコメントにmikeservのアドバイスの一部を組み込むことは有益だと思います。
jmc

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た数ライン毎かつその出力通過teeの両方の出力を複製したstdoutとすると/dev/tty。つまり、パイプラインからの出力は、関数の引数の配列に出力されます。関数の引数配列は、それが\newlineで分割され、機能するようにターミナルに出力されます。

次に、前のアクションから少なくとも1つの結果が最大5回ある場合、_in()関数readは選択を試みます。選択は、スペースで区切られた数字のみ、またはで区切られた数字の範囲で構成できます-。それ以外の場合read (空白行を含む)は再試行しますが、以前と同様、最大5回までです。

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

@mikeservこれはスクリプト全体ではなく単なるアイデアでした。テストについて話しているのは、元のファイルはディスクのみにあるため、そこから取得します。だから私はそれをテストするのは問題でも余分な努力でもないと思います
Hackaholic

@mikeservはい、あなたは正しいです、私は不適切な入力などすべてのものを検証していません。あなたのポイントに感謝
ハッカホリック '

@mikeserv私はシェルプログラミングのすべての基本を知っています。上級者になる方法を教えてくれますか
Hackaholic

はい、私はそれを喜んで編集します
Hackaholic '
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.