関数またはラッパーからすべてのbashコマンドを実行します。

関数またはラッパーからすべてのbashコマンドを実行します。

だから私が実行しているすべてのコマンドが別のコマンドにパイプされることを望み、.bashrc関数を使ってみました。

* () {
    ${@} | [my command]
}

たとえば、私が本当に達成したいのは、最後に実行したコマンドの標準出力を別のコマンドで呼び出すことができる一時ファイルに保存する方法です。

* () {
    ${@} | tee /tmp/last_command
}

私が達成したいことを説明する別の方法は次のとおりです。ALIAS [any_command]='[any_command] | tee /tmp/last_command'

以下は、この例で達成した実際の例ですtee

$ tail -5 /etc/passwd
somethingsomethingsomethingsomething
somethingsomethingsomethingsomething
somethingsomethingsomethingsomething
somethingsomethingsomethingsomething
somethingsomethingsomethingsomething

コマンドを実行すると実際に何が起こりますか(ただし、毎回作成する必要はありません):

$ tail -5 /etc/passwd | tee /tmp/last_command

完全な例は次のとおりです。

$ tail -5 /etc/passwd
somethingsomethingsomethingsomething
somethingsomethingsomethingsomething
somethingsomethingsomethingsomething
somethingsomethingsomethingsomething
somethingsomethingsomethingsomething
$ cat /tmp/last_command
somethingsomethingsomethingsomething
somethingsomethingsomethingsomething
somethingsomethingsomethingsomething
somethingsomethingsomethingsomething
somethingsomethingsomethingsomething
$ date
Tue Jan 14 13:26:04 EET 2020
$ cat /etc/last_command
Tue Jan 14 13:26:04 EET 2020

ベストアンサー1

Bashは柔軟性が低いため、各コマンドラインの前後にコマンド/関数を実行できます。しかし、Bashで出力をキャプチャするだけでは簡単ではありません。

いくつかの質問は次のとおりです。

  • パイプへの書き込みは、端末やファイル(たとえば、、)に書き込むのと同じではなく、多くのプログラムがioctl()これを検出します(たとえば)。インタラクティブエディタで使用したくありません。fseek()fstat()manlsvi
  • timeパイプ、シェルループなどの特別なコマンドはソリューションを複雑にし、パイプは戻りコードを変更することができます。
  • aliasエイリアスを使用して可能なすべてのコマンドを簡単に「ハイジャック」することはできません。これらのエイリアスはコマンドラインオプション/引数を破壊する可能性があります。語彙的置換コマンド
  • 単一のファイルに(上書き)書き込むことはできません。つまり、最後の出力を確認するためにコマンドを実行したときに競合がありますか?
  • キャプチャしstdoutたいがstderrインターリーブの問題がある可能性がある場合
  • パイプを使用している場合は、パイプや他の特定のリダイレクトがあるときにbashがサブシェルを生成し、直接的な副作用は変数の割り当てであることを覚えておいてください。これらの変数はサブシェルに割り当てられ、現在のシェルから削除され、変更されていません。 " ."(sourceexitも影響を受けます。 (以下のオプションでreadlineを使用すると、Ctrl-をj使用してそのコマンドを実行できます。)

これは同様の質問、呼び出された関数などに(おおよそ)使用されたアプローチな+ので++、キャプチャ/処理したい出力のコマンド(X選択やクリップボードなど)にプレフィックスを付けることができます。

function +() (
  set -f  # no double expand
  eval "$@" | tee >( tr -s "\n" " " | xclip )
  return ${PIPESTATUS[0]}  # real rc
)  

完璧ではありませんが、+ find /tmp -name "*.pdf" -mtime -30 基本的に必要な作業を行います。

上記では、@mosvyはこれを行うscriptと、プログラムを実行するための新しいtty(ターミナル)が作成され、これらすべての問題を回避できることが示唆されました。すべての出力を親ttyに記録し、ファイルにも記録します。セッション全体または単一のコマンドを記録するため、自動的に呼び出す方法に関する問題が引き続き発生します。

代わりに(実行中のインスタンスと対話して制御できるためscreen)とbashを使用してください。scriptPROMPT_COMMAND

if [[ "$TERM" -eq "screen" && -n "$STY" ]]; then
    PROMPT_COMMAND=screenlog
    SCREENLOG="${HOME}/screenlog"
fi

function screenlog() {
    [[ -n "$STY" ]] && {
        screen -S "$STY" -X log off
        [[ -f "${SCREENLOG:=$HOME/screenlog}" ]] && mv "$SCREENLOG" /tmp/last_command
        screen -S "$STY" -X logfile "$SCREENLOG"
        screen -S "$STY" -X log on
    }
    return 0
}

上記の要件を満たしている場合は、.bashrc問題はありませんscreen。上記の関数は、bashが新しいコマンドプロンプトを表示するたびに呼び出されます。まず、screenで実行されていることを確認してから、screenと会話してロギングを停止するように指示し、最後の出力を書き、screenに/tmp/last_commandロギングを開始するように指示します。

プロンプトが表示される前にsが呼び出されるため、プロンプトPROMPT_COMMANDとコマンド(バックスペースなどの編集内容を含む)の両方がログファイルの先頭に表示されます。この問題を解決する1つの方法は、DEBUGトラップを使用して「事前実行」をシミュレートして上記のコードを実行することです。たとえば、次のようになります。 実行前にすべてのbashコマンドをプログラムで変更する。 「対話型」または端末認識プログラム(manまたはtop)を実行している場合は、端末制御シーケンスも記録します。これは、出力を再生したい場合に便利です(script/scriptreplayが優れていますが)。


内部的には、bashはreadlineを使います。これにより、キーをreadline関数、他のキーシーケンス、またはシェル関数にバインドすることができます(ただし、これら3つのタイプの組み合わせではありません)。これは比較的簡単です。

bind '"\C-e": end-of-line'
bind '"\C-j": accept-line'
bind '"\C-m": "\C-e | tee /tmp/last_command\C-j"'

最初の2行は、Ctrl-ECtrl-がJ期待どおりにバインドされていることを確認するための編集証です(バインドされていない端末がある場合はaccept-lineもう端末がありません...)。最後の行は通常、にバインドされている - Ctrl(「Return」とも呼ばれます)を次のシーケンスに置き換えます。Maccept-line

  1. Ctrl- E(行末)
  2. | tee /tmp/last_command(単語)
  3. Ctrl- J(ライン受付)

発行されたコマンド自体が変更されると、画面と履歴に表示されます。欠点は、この単純なアプローチが「自己認識」ではなく、すべてのコマンドを盲目的に変更することです。歴史的に変更が適用されたコマンドも同様です。

以下は、送信されたコマンドを変更する関数を使用するより複雑なバージョンです。

bind '"\C-e": end-of-line'
bind '"\C-j": accept-line'
bind -x '"\e\C-a": _recorder'

function _record() {
    tee /tmp/this_command
    [[ -f /tmp/this_command ]] && mv /tmp/this_command /tmp/last_command
}

function _recorder() {
    local text="| _record"
    [[ -z "${READLINE_LINE}" ]] && return 0
    [[ "$READLINE_LINE" =~ "${text}"$ ]] || READLINE_LINE+=" $text"
    return 0
}
bind '"\C-m": "\C-e\e\C-a\C-j"'

ステップ2は、 -_recorderにバインドされたbash関数に置き換えられます(マクロにはキー入力のみを含めることができるので、入力する必要はなく、キー入力にのみバインドするだけです)。EscCtrla

この関数は任意にスマートにできます。ここでは、出力ファイルの競合を処理し、入力行を変更する前にパイプがすでに存在することを確認します。他のリダイレクトがある場合は、コマンドライン全体をサブシェルにラップすることもできます(ただし、bashでbashコマンドラインを解析するのは複雑であることを知っていますが)。

すぐに履歴を変更して、ハッキングをさらに複雑にすることができます。

function myprompt() {
    local _seq _cmdline
    local text="| _record"
    read _seq _cmdline < <(HISTTIMEFORMAT= history 1)  # previous command
    [[ "${_cmdline}" =~ (.*)" ${text}"$ ]] && {
        _cmdline="${BASH_REMATCH[1]}"
        history -d $_seq        # delete entry
        history -s "$_cmdline"  # restore original
    }
}
PROMPT_COMMAND=myprompt

readlineメソッドにも重要な欠陥があります。通常、複数行に入力できる単純でないコマンド(例:(whilefor))は、この方法で破損します。

おすすめ記事