コマンドをラップして、その出力が端末に収まらない場合にページャーを介して自動的にパイプ処理されるようにしたいのですが。
現在、私は次のシェル関数を使用しています(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に出力します。
POSIX Shellに準拠するように作成されたソリューションがありますが、bashでのみテストしたため、移植可能かどうかはわかりません。そして、私はzshを知らないので、zshに適したものにする試みはしていません。コマンドをパイプします。コマンドを引数として別のコマンドに渡すことは悪い設計です*。
もちろん、この問題を解決するには、端末の行と列の数を知る必要があります。以下のコードでは、LINES
およびCOLUMNS
環境変数(less
が参照)に依存できると想定しています。より信頼性の高い方法は次のとおりです。
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 | mypager
やls -la | mypager
などのコマンドで使用します。ps ax | path_to_mypager/mypager
を実行する必要があります。ここで、path_to_mypager
は相対パスにすることができます「.
」のようなパス。nixには哲学がありますOne One Thing and Do It Well です。たとえば、プログラムが(ポケットベルのように)特定の方法でデータを表示する場合、データを生成するメカニズムを呼び出すこともできません。それがパイプの目的です。
ユーザー指定のコマンドやプログラムを実行するUnixプログラムは多くありません。機能するものをいくつか見てみましょう。
sh -c "command"
のようなシェルenv
、Nice
、Nohup
、setsid
、su
、およびSudo
。これらのプログラムには共通点があります。これらはすべて、変更された実行環境でプログラムを実行するために存在します1。 Unixは通常、別のプロセスの実行環境を変更することを許可していないため、彼らは彼らがするように働く必要があります。独自のプロセスを変更してから、fork
および/またはexec
を変更する必要があります。Nice
」値、UID、GIDなどのプロセス属性も参照しています。 、プロセスグループ、セッションID、制御端末、開いているファイル、作業ディレクトリ、umask
値、ulimit
s、信号処理、alarm
タイマーなど。vi
/vim
ですが、他にも確かにあります。これらは歴史的な遺物です。それらはウィンドウシステムやジョブ制御よりも前から存在しています。ファイルを編集していて、別のことをしたい場合(ディレクトリ一覧を見るなど)、シェルに戻るには、ファイルを保存してエディターを終了する必要がありました。最近では、別のウィンドウに切り替えたり、使用したりできます Ctrl+Z (または:suspend
と入力して)、エディターを存続させながらシェルに戻るため、シェルエスケープはおそらく廃止されています。他の(ハードコードされた)プログラムを実行して、それらの機能を複製するのではなく活用するプログラムはカウントしていません。たとえば、一部のプログラムはdiff
またはsort
を実行する場合があります。 (たとえば、spell
の初期のバージョンがsort -u
を使用してドキュメントで使用されている単語のリストを取得し、次にdiff
—またはcomm
—を使用したという話があります。そのリストを辞書の単語リストと比較し、ドキュメントのどの単語が辞書になかったかを特定します。)
スクリプトの記述方法、RET="$($@)"
行は、呼び出されたコマンドが完了するまで完了しません。したがって、スクリプトを生成するコマンドが完了するまで、スクリプトはデータの表示を開始できません。おそらくそれを修正する最も簡単な方法は、データ生成コマンドをデータ表示プログラムから分離することです(他の方法もありますが)。
表示フィルターによって処理された出力を使用してコマンドを実行し、出力を確認して、その出力をファイルに保存することを決定したとします。入力した場合(架空の例として)
ps ax | mypager
次に入力できます
!:1 > myfile
または押す ↑ 行を適切に編集します。今、あなたがタイプしたなら
mypager "ps ax"
戻ってそのコマンドをps ax > myfile
に編集することはできますが、それほど簡単ではありません。
または、次にps uax
を実行することを決定したとします。 ps ax | mypager
と入力した場合は、
!:0 u!:*
繰り返しになりますが、mypager "ps ax"
を使用すれば、それでも実行可能ですが、間違いなく困難です。
また、2つのコマンド、ps ax | mypager
とmypager "ps ax"
も確認してください。 1時間後にhistory
リストを実行するとします。実行されているコマンドが何であるかを確認するのが少し難しいmypager "ps ax"
を調べる必要があるISTM。
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行しかない場合でも、mypager
はless
を呼び出します。おそらくもっと劇的な方法で失敗する例があるでしょう。
だからあなたは私が以前に示していたことをしなければなりません:
mypager "ps ax | grep Oracle"
しかし、RET="$($@)"
はそれを処理できません。もちろん、そのようなことを処理する方法はありますが、推奨されません。
出力をキャプチャするコマンドラインがさらに複雑な場合はどうなりますか。例えば。、
コマンド1 」arg1"| コマンド2 'arg2'$'arg3'
どこの引数は、スペース、タブ、$
、|
、\
、<
、>
、*
、;
、&
、[
、]
、(
、)
、`
、多分'
と"
の厄介な組み合わせを含んでいます。そのようなコマンドは、シェルに直接正しく入力するのに十分難しい場合があります。次に、 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
パッケージ)をインストールする必要がある場合があります。