Quantcast
Channel: Bashタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 2722

端末のパイプ先に特定の出力だけ渡す方法

$
0
0

経緯

CLIツールを作っていたときに、コマンドの標準出力のうち、
パイプ先のコマンドに渡したい出力と渡したくない出力があるケースがでてきました。

以下のようなツールです。

mycmd1.gif

何らかの処理を行い、プログレスバー風のアニメーションをプロンプトに表示し、
処理結果のファイル名を最後に出力するコマンドです。

コードは以下のような感じです。
Nimでの実装です。

importos,strutils,strformatforiin1..60:# カーソルを1行上に移動し、行を削除するANSIエスケープシーケンスecho"\x1b[1A\x1b[K\x1b[1A"# 進捗の分数letprog=&"[ {i:>2}/60 ]"# 処理済みのバーletleftBar="\x1b[44m"&" ".repeat(i).join()&"\x1b[m"# 空白のバーletrightBar=" ".repeat(60-i).join()echo&"{prog} [ {leftBar}{rightBar} ]"sleep25# 処理結果を格納したファイルパスecho"/var/tmp/result.txt"

渡したくないのはアニメーションをしているプログレスバーの部分です。
最後のファイル名だけパイプ先に渡して、catなりvimなりで開きたかったんです。

これをそのままlessするとどうなるか?
以下のようになります。

2019-11-05-231752_939x507_scrot.png

こうなります。悲惨です。
プログレスバーの出力と、カーソル移動、カーソル行の削除のANSIエスケープシーケンスまでlessが補足しています。

これを回避する方法を調べて、解決したことを書きます。

類似ツールの調査

必要な出力と不要な出力を分けてパイプ先に渡しているツールとしてpecoが思い浮かびました。
なので、pecoのソースを調べることにしました。

pecoのソースを読んだ所、pecoのUI部分はtermboxが全部引き受けていることがわかりました。
termboxで軽くUIを作ってlessしてみたところ、termboxのUI出力はパイプ先に渡さないことがわかりました。
termboxのソースを読んだところ、以下の部分がその理由でした。

https://github.com/nsf/termbox-go/blob/master/api.go#L27-L42

/dev/ttyを開いています。
ttyについて全然把握していなかったので、ttyについて調べました。
以下のQiitaの記事がとてもわかりやすかったです。

Qiita - ttyとかptsとかについて確認してみる

実装

前述の実装を参考に以下のように修正しました。

--- mycmd.nim   2019-11-05 21:16:47.623075601 +0900
+++ mycmd2.nim  2019-11-05 21:20:30.929732374 +0900
@@ -1,5 +1,15 @@
 import os, strutils, strformat

+var
+  tty = open("/dev/tty", fmReadWrite)
+  oldStdin = stdin
+  oldStdout = stdout
+  oldStderr = stderr
+
+stdin = tty
+stdout = tty
+stderr = tty
+
 for i in 1..60:
   echo "\x1b[1A\x1b[K\x1b[1A"

@@ -9,4 +19,9 @@
   echo &"{prog} [ {leftBar}{rightBar} ]"
   sleep 25

+tty.close()
+stdin = oldStdin
+stdout = oldStdout
+stderr = oldStderr
+
 echo "/var/tmp/result.txt"

ttyで仮想端末を開き、stdin,stdout,stderrで上書きします。
echoは上書きされたstdoutのほうに出力します。
一連の処理が終わったらttyを閉じてしまい、上書き前のstdin,stdout,stderrで元に戻します。

このように変更を加えたプログラムmycmd2を実行してみます。
結果がわかりやすいようにnlを使います。

mycmd2.gif

最後の出力結果のみ、パイプ先のnlが処理するようにできました。
これで特定の出力だけパイプ先に渡せそうです。

実装例

まだ作りかけなのですが、今回得た知見を利用して
Nimでディレクトリツリーを表示するコマンドを作ってます。

https://github.com/jiro4989/dirsel

選択したファイルを最後に出力するので、あとはパイプ先でよしなに使ってくれ、というツールにしようかと。

dirsel.gif

以上です。


Viewing all articles
Browse latest Browse all 2722

Trending Articles