LoginSignup
9
5

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-11-05

経緯

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

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

mycmd1.gif

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

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

import os, strutils, strformat

for i in 1..60:
  # カーソルを1行上に移動し、行を削除するANSIエスケープシーケンス
  echo "\x1b[1A\x1b[K\x1b[1A"

  # 進捗の分数
  let prog = &"[ {i:>2}/60 ]"
  # 処理済みのバー
  let leftBar = "\x1b[44m" & " ".repeat(i).join() & "\x1b[m"
  # 空白のバー
  let rightBar = " ".repeat(60 - i).join()
  echo &"{prog} [ {leftBar}{rightBar} ]"
  sleep 25

# 処理結果を格納したファイルパス
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のソースを読んだところ、以下の部分がその理由でした。

/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でディレクトリツリーを表示するコマンドを作ってます。

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

dirsel.gif

以上です。

9
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
5