web-dev-qa-db-ja.com

コマンドの出力が長すぎる場合(そしてその場合のみ)、ページャーを介してコマンドの出力をパイプする最良の方法は何ですか?

コマンドをラップして、その出力が端末に収まらない場合にページャーを介して自動的にパイプ処理されるようにしたいのですが。

現在、私は次のシェル関数を使用しています(zsh、Arch Linuxの下):

export LESS="-R"

RET="$($@)"
RET_LINES="$(echo "${RET}" | wc -l)"

if [[ $RET_LINES -ge $LINES ]]; then
  echo "${RET}" | ${PAGER:="less"}
else
  echo "${RET}"
fi

しかし、これは本当に私を納得させません。私が望むものを達成するためのより良い方法(堅牢性とオーバーヘッドの観点から)はありますか?それがうまく機能すれば、私もzsh固有のコードを受け入れます。


更新:この質問をしたので 回答 が見つかりました。出力をすべてキャッシュするのではなく、lessにパイプする前に最大で$LINES行。悲しいことに、どちらのソリューションも長く折り返された行を考慮に入れていないため、それも実際には満足できません。たとえば、上記のコードがpager_wrapという関数に格納されている場合、

pager_wrap echo {1..10000}

ページャーを介してパイプするのではなく、非常に長い行をstdoutに出力します。

10
A.P.

POSIX Shellに準拠するように作成されたソリューションがありますが、bashでのみテストしたため、移植可能かどうかはわかりません。そして、私はzshを知らないので、zshに適したものにする試みはしていません。コマンドをパイプします。コマンドを引数として別のコマンドに渡すことは悪い設計です*

もちろん、この問題を解決するには、端末の行と列の数を知る必要があります。以下のコードでは、LINESおよびCOLUMNS環境変数(lessが参照)に依存できると想定しています。より信頼性の高い方法は次のとおりです。

  • A.P。が推奨 として、rows="${LINES:=$(tput lines)}"およびcols="${COLUMNS:=$(tput cols)}"を使用する、または
  • stty sizeからの出力を見てください。このコマンドは標準入力として端末を持っている必要があることに注意してください。したがって、スクリプト内にあり、スクリプトにパイプしている場合は、stty size <&1(bash)またはstty size < /dev/ttyと言う必要があります。出力のキャプチャはさらに複雑です。

秘密の要素:foldコマンドは、画面と同じように長い行を分割するため、スクリプトは長い行を正しく処理できます。

#!/bin/sh
buffer=$(mktemp)
rows="$LINES"
cols="$COLUMNS"
while true
do
      IFS= read -r some_data
      e=$?        # 1 if EOF, 0 if normal, successful read.
      printf "%s" "$some_data" >> "$buffer"
      if [ "$e" = 0 ]
      then
            printf "\n" >> "$buffer"
      fi
      if [ $(fold -w"$cols" "$buffer" | wc -l) -lt "$rows" ]
      then
            if [ "$e" != 0 ]
            then
                  cat "$buffer"
            else
                  continue
            fi
      else
            if [ "$e" != 0 ]
            then
                  "${PAGER:="less"}" < "$buffer"
                  # The above is equivalent to
                  # cat "$buffer"   | "${PAGER:="less"}"
                  # … but that’s a UUOC.
            else
                  cat "$buffer" - | "${PAGER:="less"}"
            fi
      fi
      break
done
rm "$buffer"

これを使用するには:

  • 上記をファイルに入れます。それをmypagerと呼んでいるとしましょう。
  • (オプション)検索パスであるディレクトリに配置します。例:$HOME/bin
  • chmod +x mypagerと入力して実行可能にします。
  • ps ax | mypagerls -la | mypagerなどのコマンドで使用します。
    2番目の手順(スクリプトを検索パスであるディレクトリに置く)をスキップした場合は、ps ax | path_to_mypager/mypagerを実行する必要があります。ここで、path_to_mypagerは相対パスにすることができます「.」のようなパス。

* コマンドを引数として別のコマンドに渡すのはなぜ悪い設計なのですか?

I.美学/伝統への適合/ Unix哲学

nixには哲学がありますOne One Thing and Do It Well です。たとえば、プログラムが(ポケットベルのように)特定の方法でデータを表示する場合、データを生成するメカニズムを呼び出すこともできません。それがパイプの目的です。

ユーザー指定のコマンドやプログラムを実行するUnixプログラムは多くありません。機能するものをいくつか見てみましょう。

  • sh -c "command"のようなシェル
    ユーザー指定のコマンドを実行するのは、シェルの job です。シェルが行うのはOne Thingです。 (もちろん、シェルが単純なプログラムであると言っているのではありません。)
  • envNiceNohupsetsidsu、およびSudo。これらのプログラムには共通点があります。これらはすべて、変更された実行環境でプログラムを実行するために存在します1。 Unixは通常、別のプロセスの実行環境を変更することを許可していないため、彼らは彼らがするように働く必要があります。独自のプロセスを変更してから、forkおよび/またはexecを変更する必要があります。
    _______
    1私は広義の execution environment というフレーズを使用しており、環境変数だけでなく、「Nice」値、UID、GIDなどのプロセス属性も参照しています。 、プロセスグループ、セッションID、制御端末、開いているファイル、作業ディレクトリ、umask値、ulimits、信号処理、alarmタイマーなど。
  • 「シェルエスケープ」を可能にするプログラム。思い浮かぶ唯一の例はvi/vimですが、他にも確かにあります。これらは歴史的な遺物です。それらはウィンドウシステムやジョブ制御よりも前から存在しています。ファイルを編集していて、別のことをしたい場合(ディレクトリ一覧を見るなど)、シェルに戻るには、ファイルを保存してエディターを終了する必要がありました。最近では、別のウィンドウに切り替えたり、使用したりできます Ctrl+Z (または:suspendと入力して)、エディターを存続させながらシェルに戻るため、シェルエスケープはおそらく廃止されています。

他の(ハードコードされた)プログラムを実行して、それらの機能を複製するのではなく活用するプログラムはカウントしていません。たとえば、一部のプログラムはdiffまたはsortを実行する場合があります。 (たとえば、spellの初期のバージョンがsort -uを使用してドキュメントで使用されている単語のリストを取得し、次にdiff —またはcomm —を使用したという話があります。そのリストを辞書の単語リストと比較し、ドキュメントのどの単語が辞書になかったかを特定します。)

II。タイミングの問題

スクリプトの記述方法、RET="$($@)"行は、呼び出されたコマンドが完了するまで完了しません。したがって、スクリプトを生成するコマンドが完了するまで、スクリプトはデータの表示を開始できません。おそらくそれを修正する最も簡単な方法は、データ生成コマンドをデータ表示プログラムから分離することです(他の方法もありますが)。

III。コマンド履歴

  1. 表示フィルターによって処理された出力を使用してコマンドを実行し、出力を確認して、その出力をファイルに保存することを決定したとします。入力した場合(架空の例として)

    ps ax | mypager
    

    次に入力できます

    !:1 > myfile
    

    または押す  行を適切に編集します。今、あなたがタイプしたなら

    mypager "ps ax"
    

    戻ってそのコマンドをps ax > myfileに編集することはできますが、それほど簡単ではありません。

  2. または、次にps uaxを実行することを決定したとします。 ps ax | mypagerと入力した場合は、

    !:0 u!:*
    

    繰り返しになりますが、mypager "ps ax"を使用すれば、それでも実行可能ですが、間違いなく困難です。

  3. また、2つのコマンド、ps ax | mypagermypager "ps ax"も確認してください。 1時間後にhistoryリストを実行するとします。実行されているコマンドが何であるかを確認するのが少し難しいmypager "ps ax"を調べる必要があるISTM。

IV。複雑なコマンド/引用

  1. echo {1..10000}は明らかに単なるサンプルコマンドです。 ps axはそれほど良くはありません。 ps ax | grep Oracleのように、少し少しだけ現実的なことをしたい場合はどうしますか?入力した場合

    mypager ps ax | grep Oracle
    

    mypager ps axを実行し、そこからの出力をgrep Oracleにパイプします。したがって、ps axからの出力が30行の場合でも、ps ax | grep Oracleからの出力が3行しかない場合でも、mypagerlessを呼び出します。おそらくもっと劇的な方法で失敗する例があるでしょう。

    だからあなたは私が以前に示していたことをしなければなりません:

    mypager "ps ax | grep Oracle"
    

    しかし、RET="$($@)"はそれを処理できません。もちろん、そのようなことを処理する方法はありますが、推奨されません。

  2. 出力をキャプチャするコマンドラインがさらに複雑な場合はどうなりますか。例えば。、

    コマンド1arg1"| コマンド2  'arg2'$'arg'

    どこの引数は、スペース、タブ、$|\<>*;&[]()`、多分'"の厄介な組み合わせを含んでいます。そのようなコマンドは、シェルに直接正しく入力するのに十分難しい場合があります。次に、 quote を引数としてmypagerに渡す必要があるという悪夢を想像してみてください。

これがless-Fオプションの目的ですが、-Xオプションも使用する必要があります。そうしないと、テキストが端末の代替画面に出力されます。 (つまり、lessが終了するとすぐに使用できなくなります)。これは将来変更される可能性があります 現在、テキストが-F(303) および RedHatシステムが持っていたように見える1つの画面に収まるときに-Xを暗示する拡張要求があります2008年以降のパッチ (まだアップストリームにはなりませんが(2017-09-14の時点で、それについて[email protected]にメールを送信しました))。

そう:

cmd | less -RXF

出力が長すぎる場合でも代替画面を使用したい場合は、ここで空想を得る必要があります(上記のRedHatパッチを適用していないシステムの場合)。

page() {
  L=${LINES:-$(tput lines)} C=${COLUMNS:-$(tput cols)} \
    Perl -Mopen=locale -MText::Tabs -MText::CharWidth=mbswidth -e '
      while(<STDIN>) {
        if ($pager) {
          print $pager $_;
        } else {
          chomp(my $line = $_);
          $line =~ s/\e\[[\d;]*m//g;
          $l += 1 + int(mbswidth(expand($line)) / $ENV{C});
          $buf .= $_;
          if ($l > $ENV{L}) {
            open $pager, "|-", "less", "-R", @ARGV or die "pager: $!";
            print $pager $buf;
          }
        }
      }
      print $buf unless $pager;' -- "$@"
}

として使用する:

cmd | page

または

page < file
page -S < file...

page fileではなく、stdinをページングすることだけを目的としています)。

カラーエスケープシーケンスを取り除き、タブを展開して幅を計算することにより、出力の長さを推測して、特定のテキスト行を表示するためのターミナル行の数を決定しようとしています。

出力に他のエスケープシーケンスや制御/正しくエンコードされていない文字がない限り、これは機能するはずです。

RedHatパッチとの1つの重要な違いにも注意してください。1画面出力の場合、出力はless後処理を通過しません(逆ビデオでの制御文字の^Xとしてのレンダリング、スクイーズなど) -s...)の空の行の。それはここで求められていることに近いですが、実際にはあまり望ましくないかもしれません。

標準モジュールの1つではないText :: CharWidthモジュール(Debianのlibtext-charwidth-Perlパッケージ)をインストールする必要がある場合があります。

3