web-dev-qa-db-ja.com

コマンドをだまして、その出力がターミナルに送られると思わせる方法

出力がターミナルに送られるときに動作を変更するコマンド(たとえば、カラー出力を生成する)が与えられた場合、変更された動作を維持しながら、その出力をパイプラインにリダイレクトするにはどうすればよいですか?そのためのユーティリティがあるに違いないが、私は知らない。

grep --color=alwaysなどの一部のコマンドには、動作を強制するオプションフラグがありますが、問題は、出力ファイル記述子のテストのみに依存するプログラムを回避する方法です。

それが重要であれば、私のシェルはLinuxではbashです。

44
Amir

unbufferを使用すると、必要なものを取得できます。

unbuffertcl/expectスクリプトです。必要に応じてソースを確認してください。また、人の警告セクションにも注意してください。

また、次のようなエイリアスは実行しません。

alias ls='ls --color=auto'

stéphaneChazelasが指摘したトリックを追加しない限り:

alias unbuffer='unbuffer '(末尾のスペースに注意)、エイリアスはunbufferの後に展開されます。

22
Runium

ツールセットの歴史

あなたはそのようなツールを欲する最初の人ではありません。人々はそのようなツールを30年間求めてきました。そして、それらはほとんど同じくらい長く存在してきました。

この種の最も初期のツールは、ダニエルJ.バーンスタインの「pty」パッケージで、リッチサルツによって「ギンスナイフ」と記述されていました。バーンスタインは、1990年代の初めにnethack(sic!)をだますために書き戻しました。 「pty」パッケージのバージョン4は1992年にcomp.sources.unixに公開されました(25巻は127から135号)。ワールドワイドウェブ上でまだ検索可能です。ポール・ヴィクシーは当時それを説明しました:

何と言えばいい?スライスして、さいの目に切って、皿を洗って、犬の散歩をします。これは「うまくいく」ことを意味します。つまり、指示に従えば、髪を引っ張ったり、歯を噛んだり、その他の標準的な移植作業をしたりすることなく、機能するパッケージを入手できます。

バーンスタインは、1999年4月7日以前のいつか、これを「ptyget」パッケージで更新しました。

新しい疑似ttyアロケータであるptygetをまとめました。アルファ版はftp://koobera.math.uic.edu/pub/software/ptyget-0.50.tar.gzにあります。 Ptygetメーリングリストがあります。参加するには、空のメッセージを[email protected]に送信してください。私はptygetのインターフェースを一から設計しました。これはptyよりもはるかにモジュール化されています。基本的なptyインターフェースは3つの部分に分割されました:

  • ptyget:新しい疑似ttyを割り当て、選択したプログラムに渡す、小さな低レベルプログラム(パッケージ内の唯一のsetuidプログラム)
  • ptyspawn:疑似ttyの下で子プロセスを実行し、終了するのを待って停止を監視する別の小さなプログラム
  • ptyio:データを前後に移動するもう1つのわずかに大きなプログラム

古いギンスナイフptyのスペルはptybandageになりました。これはptyget ptyio -t ptyspawnの同義語です。ネットワークプログラムを疑似ttyに接続するためのpty -dは、ptyrunと綴られるようになりました。これは、ptyget ptyio ptyspawnの同義語です。また、nobufptyget ptyio -r ptyspawn -23xの同義語です。セッション管理機能を別のパッケージに分割しました。

その別のパッケージは「sess」パッケージでした。

ちなみに、「ptyget」は、非常に初期のバージョンと、公開されていないBerstein独自の「redo」ビルドシステムの数少ないインスタンスの1つを例示することで注目に値します。 dependonredo-ifchangeの前兆です。

使用法

ptybandage

ptybandageは、ログインセッションで通常必要なものです。その主な使用例は、標準の入力、出力、またはエラーが端末に接続されているかどうかに敏感なプログラムを、実際にはシェルパイプラインにあるか、標準のファイル記述子をファイルにリダイレクトしても、そのように動作させることです。

これは実行するコマンド(もちろん、適切な外部コマンドでなければなりません)を取り、その標準入力、出力、およびエラーがターミナルに接続されていると見なすように実行し、それらをptybandageのオリジナルに接続します標準入力、出力、およびエラー。

これは、ジョブ制御シェルの下で実行されるニュアンスを扱い、端末のSTOP文字がptybandageを停止するだけでなく、内部端末に接続されているプログラムの実行も停止することを保証します。

ptyrun

ptyrunは、人々が通常TCPネットワークサーバーで使用するものです。その主な使用例は、それ自体が端末をセットアップしていないリモート実行環境であり、端末がないと期待どおりに動作しないプログラムを実行します。

ジョブコントロールシェルの下で実行されることは想定されていません。実行中のコマンドが停止信号を受信した場合は、単に再起動されます。

利用可能なツールセット

Dru Nelsonは、「pty」バージョン4と「ptyget」の両方を公開しています。

Paul Jarcは、オペレーティングシステムが実際には提供していないオリジナルのオペレーティングシステム固有の疑似端末デバイスioctlを処理しようとする修正バージョンのptygetを公開しています。

Noshソースパッケージには、Laurent Bercotのptybandangeツールとnoshパッケージ独自の疑似端末管理コマンドを使用する、機能の似たptyrunおよびexeclineスクリプトが付属しています。 noshバージョン1.23以降、これらはnosh-terminal-extrasパッケージに事前パッケージされて利用可能です。 (以前のバージョンでは、ソースからビルドした人にのみ提供されていました。)

いくつかの使用例

AIXでptybandageを使用するJurjgen Oskam ヒアドキュメントからの入力を、明示的に開いてパスワードの制御端末を読み取るプログラムにフィードするプロンプト:

$ ptybandage dsmadmc << EOF> uit.txt 
 joskam 
 password 
 query session 
 query process 
 quit 
 EOF

OpenBSDでptyrunを使用するAndy Bradford daemontoolsとucspi-tcpの下で、bgplgshインタラクティブルーター制御プログラムをネットワーク経由でアクセス可能にし、端末と通信していると考えさせます。

#!/ bin/sh 
 exec 2>&1 
 exec envuidgid rviews tcpserver -vDRHl0 0 23 ptyrun/usr/bin/bgplgsh

参考文献

26
JdeBP

socat を使用して pty を接続してプロセスを開始し、socatを使用してptyのもう一方の端をファイルに接続できます。どのAFAIUは、あなたが尋ねたとおりのものです。

socat EXEC:"my-command",pty GOPEN:mylog.log

このメソッドは isattymy-commandから呼び出されてtrueを返すようにし、それにのみ依存するプロセスは制御コードを出力するためにだまされます。一部のプロセス(特にgrep)はTERM環境変数の値もチェックするため、"xterm"などの適切な値に設定する必要がある場合があることに注意してください。

21
Guss

ここではスーパーユーザーKarlC による素敵なソリューションも掲載されています:

小さな共有ライブラリをコンパイルします。

_echo "int isatty(int fd) { return 1; }" | gcc -O2 -fpic -shared -ldl -o isatty.so -xc -
_

次に、このisatty(3)オーバーライドを動的にロードするようにコマンドに指示します。

_LD_PRELOAD=./isatty.so mycommand
_

これは、すべてのコマンドで機能するわけではなく、予期しない方法で一部が壊れる可能性もありますが、ほとんどの場合は機能するでしょう。

14
Amir

script(1)を使用するのはどうですか?

例えば:

script -q -c 'ls -G' out_file

色コードを保持したままls出力をout_fileに保存します。

14
sagi

これまでに紹介したソリューションに満足できなかったため、Pythonをリリースしました。彼女は効果的でした。このソリューションでは、setuidアクセス許可や、共有ライブラリとLD_LIBRARY_PATHを使用した実際に異常なモンキーパッチは必要ありません。このスクリプトをPATHのどこかに保存します。それをchmod +xすることを忘れないでください。このスクリプトをptyとして保存するとします。

#!/usr/bin/env python

from sys import argv
import os
import signal

# I've had problems with python's File objects at this low a level, so
# we're going to use integers to specify all files in this script.
stdin = 0
stdout = 1
stderr = 2

if len(argv) < 2:
    os.write(stderr,
b"""A tragically beautiful piece of hackery, made to fool programs like ls,
grep, rg, and fd into thinking they're actually connected to a terminal.
Its usage:

pty command [arg1 arg2 ...]

Examples:
pty ls --color -R | less -r
git log -p | pty rg <search terms> | less -r
""")
    exit(255)

# We do not use forkpty here because it would block ^Cs from reaching the
# child process. And we don't need that.
ptm, pts = os.openpty()
pid = os.fork()
if pid == 0:
    # The child runs this.
    # To get the behaviour we want, we only need to replace the process's
    # stdout with pts. Everything else should remain in place, so that things
    # like `ps -eF | pty rg python | less -r` will still work as intended.
    os.dup2(pts, stdout)
    # This is not like a subprocess.call(). It replaces the entire child
    # process with argv[1:], meaning execvp will not return! Web search
    # "fork exec" for more.
    os.execvp(argv[1], argv[1:])


# The parent runs this.

# If the parent doesn't close the slave end, the script won't be able to
# exit. The next read on ptm after the child process terminates would hang
# forever because pts would technically still be open.
os.close(pts)

# The whole process group gets SIGINT, including the child, so we don't need
# to react to it. We'll know when to leave, judging by what the child does.
signal.signal(signal.SIGINT, signal.SIG_IGN)

while True:
    try:
        chunk = os.read(ptm, 4096)
    except OSError:
        break
    try:
        os.write(stdout, chunk)
    except BrokenPipeError:
        # This happens when the parent is piping output to another process in a
        # pipeline, like in `pty ls --color -R | less -r`, and the receiving
        # process is terminated before the child has exited. If the receiving
        # process is less, this can happen very easily. It happens every time
        # the user decides to quit less before it has displayed all output. So,
        # we need to stop the child process now. To do that, do what the child
        # expects the user to do if they want to stop looking at output, i.e.
        # send a SIGINT.
        os.kill(pid, signal.SIGINT)
        break
wait_pid, status = os.waitpid(pid, 0)
exit(status >> 8)
2

@ Amirの回答 に基づいて、実行時にライブラリを生成してインクルードするスクリプトを次に示します。

#!/bin/bash
set -euo pipefail

function clean_up {
  trap - EXIT # Restore default handler to avoid recursion
  [[ -e "${isatty_so:-}" ]] && rm "$isatty_so"
}
# shellcheck disable=2154 ## err is referenced but not assigned
trap 'err=$?; clean_up; exit $err' EXIT HUP INT TERM

isatty_so=$(mktemp --tmpdir "$(basename "$0")".XXXXX.isatty.so)
echo "int isatty(int fd) { return 1; }" \
  | gcc -O2 -fpic -shared -ldl -o "$isatty_so" -xc -
# Allow user to SH=/bin/zsh faketty mycommand
"${SH:-$Shell}" -c 'eval $@' - LD_PRELOAD="$isatty_so" "$@"
0
Tom Hale