最近のほとんどのシェルでは、上下の矢印を押すと、プロンプトで、実行した前のコマンドが表示されます。私の質問は、これはどのように機能するのですか?!
シェルは、すでに書き込んだ内容を上書きするために、どういうわけかstdoutを操作しているように見えますか?
Wgetのようなプログラムもこれを行うことに気づきました。誰かがそれをどのように行うかについて何か考えがありますか?
Stdoutを操作するのではなく、端末によってすでに表示されている文字を上書きします。
これを試して:
#include <stdio.h>
#include <unistd.h>
static char bar[] = "======================================="
"======================================>";
int main() {
int i;
for (i = 77; i >= 0; i--) {
printf("[%s]\r", &bar[i]);
fflush(stdout);
sleep(1);
}
printf("\n");
return 0;
}
これはwget
の出力にかなり近いですよね? \r
はキャリッジリターンであり、端末は「カーソルを現在の行の先頭に戻す」と解釈します。
シェルは、bash
の場合、 GNU Readlineライブラリ を使用します。これは、端末タイプの検出、履歴管理、プログラム可能なキーバインディングなど、はるかに一般的な機能を提供します。
もう1つ、疑わしい場合は、wget、Shellなどのソースがすべて利用可能です。
現在の標準出力行(またはその一部)を上書きするには、_\r
_(または_\b
_)を使用します。特殊文字_\r
_(キャリッジリターン)は、キャレットを行の先頭に戻します。 、上書きできます。特殊文字_\b
_は、キャレットを1つの位置にのみ戻し、最後の文字を上書きできるようにします。
_#include <stdio.h>
#include <unistd.h>
int i;
const char progress[] = "|/-\\";
for (i = 0; i < 100; i += 10) {
printf("Processing: %3d%%\r",i); /* \r returns the caret to the line start */
fflush(stdout);
sleep(1);
}
printf("\n"); /* goes to the next line */
fflush(stdout);
printf("Processing: ");
for (i = 0; i < 100; i += 10) {
printf("%c\b", progress[(i/10)%sizeof(progress)]); /* \b goes one back */
fflush(stdout);
sleep(1);
}
printf("\n"); /* goes to the next line */
fflush(stdout);
_
標準出力は通常バッファリングされます であるため、fflush(stdout);
を使用します。そうしないと、情報が出力または端末にすぐに出力されない場合があります。
\ rと\ bに加えて、コンソール画面の内容を高度に制御するために ncurses を確認してください。 (列を含む、任意に移動するなど)。
テキスト端末/コンソールで実行されているプログラムは、コンソールに表示されるテキストをさまざまな方法で操作できます(テキストを太字にする、カーソルを移動する、画面をクリアするなど)。これは、 "エスケープシーケンス" と呼ばれる特別な文字シーケンスを出力することによって実現されます(通常、エスケープで始まるため、ASCII 27)。
Stdoutがこれらのエスケープシーケンスを理解する端末に移動すると、それに応じて端末の表示が変わります。
Stdoutをファイルにリダイレクトすると、エスケープシーケンスがファイルに表示されます(これは通常、必要なものではありません)。
エスケープシーケンスの完全な標準はありませんが、ほとんどの端末は VT1 で導入されたシーケンスを使用し、多くの拡張機能があります。これは、Unix/Linux(xterm、rxvt、konsole)およびPuTTYなどの他の端末で理解されていることです。
実際には、エスケープシーケンスをソフトウェアに直接ハードコーディングすることはありませんが(可能ですが)、上記の ncurses や GNU readline などのライブラリを使用してエスケープシーケンスを出力します。これにより、さまざまな端末タイプとの互換性が可能になります。
readline ライブラリで実行されます...舞台裏でどのように機能するかはわかりませんが、stdoutやストリームとは何の関係もないと思います。 readlineは、ある種の不可解な(少なくとも私にとっては)ターミナルコマンドを使用していると思います。つまり、実際にシェルセッションを表示するターミナルプログラムと連携します。出力を印刷するだけでreadlineのような動作が得られるかどうかはわかりません。
(これについて考えてみてください。stdoutはファイルにリダイレクトできますが、上/下矢印キーのトリックはファイルでは機能しません。)
キャリッジリターンを使用して、これをシミュレートできます。
#include <stdio.h>
int main(int argc, char* argv[])
{
while(1)
{
printf("***********");
fflush(stdout);
sleep(1);
printf("\r");
printf("...........");
sleep(1);
}
return 0;
}
プログラムは、端末が特別な方法で解釈する特殊文字を印刷することによってこれを行います。これの最も単純なバージョンは、(ほとんどのlinux/unix端末で)カーソル位置を現在の行の最初の文字にリセットする通常の標準出力に「\ r」(キャリッジリターン)を出力することです。したがって、次に書くものは、前に書いた行を上書きします。これは、たとえば、単純な進行状況インジケーターに使用できます。
int i = 0;
while (something) {
i++;
printf("\rprocessing line %i...", i);
...
}
ただし、さまざまな方法で解釈される、より複雑なエスケープ文字シーケンスがあります。これにより、画面上の特定の位置にカーソルを置いたり、テキストの色を設定したりするなど、さまざまなことができます。これらの文字シーケンスが解釈されるかどうか、またはどのように解釈されるかは端末によって異なりますが、ほとんどの端末でサポートされている一般的なクラスは ansiエスケープシーケンス です。したがって、赤いテキストが必要な場合は、次を試してください。
printf("Text in \033[1;31mred\033[0m\n");
最も簡単な方法は、キャリッジリターン文字( '\ r')を標準出力に印刷することです。
カーソルが行頭に移動し、内容を上書きできるようになります。