web-dev-qa-db-ja.com

Cでstdoutを上書きする方法

最近のほとんどのシェルでは、上下の矢印を押すと、プロンプトで、実行した前のコマンドが表示されます。私の質問は、これはどのように機能するのですか?!

シェルは、すでに書き込んだ内容を上書きするために、どういうわけかstdoutを操作しているように見えますか?

Wgetのようなプログラムもこれを行うことに気づきました。誰かがそれをどのように行うかについて何か考えがありますか?

32
Scott

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などのソースがすべて利用可能です。

44
ephemient

現在の標準出力行(またはその一部)を上書きするには、_\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);を使用します。そうしないと、情報が出力または端末にすぐに出力されない場合があります。

22
vladr

\ rと\ bに加えて、コンソール画面の内容を高度に制御するために ncurses を確認してください。 (列を含む、任意に移動するなど)。

12
SoapBox

テキスト端末/コンソールで実行されているプログラムは、コンソールに表示されるテキストをさまざまな方法で操作できます(テキストを太字にする、カーソルを移動する、画面をクリアするなど)。これは、 "エスケープシーケンス" と呼ばれる特別な文字シーケンスを出力することによって実現されます(通常、エスケープで始まるため、ASCII 27)。

Stdoutがこれらのエスケープシーケンスを理解する端末に移動すると、それに応じて端末の表示が変わります。

Stdoutをファイルにリダイレクトすると、エスケープシーケンスがファイルに表示されます(これは通常、必要なものではありません)。

エスケープシーケンスの完全な標準はありませんが、ほとんどの端末は VT1 で導入されたシーケンスを使用し、多くの拡張機能があります。これは、Unix/Linux(xterm、rxvt、konsole)およびPuTTYなどの他の端末で理解されていることです。

実際には、エスケープシーケンスをソフトウェアに直接ハードコーディングすることはありませんが(可能ですが)、上記の ncursesGNU readline などのライブラリを使用してエスケープシーケンスを出力します。これにより、さまざまな端末タイプとの互換性が可能になります。

5
sleske

readline ライブラリで実行されます...舞台裏でどのように機能するかはわかりませんが、stdoutやストリームとは何の関係もないと思います。 readlineは、ある種の不可解な(少なくとも私にとっては)ターミナルコマンドを使用していると思います。つまり、実際にシェルセッションを表示するターミナルプログラムと連携します。出力を印刷するだけでreadlineのような動作が得られるかどうかはわかりません。

(これについて考えてみてください。stdoutはファイルにリダイレクトできますが、上/下矢印キーのトリックはファイルでは機能しません。)

2
David Z

キャリッジリターンを使用して、これをシミュレートできます。

#include <stdio.h>

int main(int argc, char* argv[])
{
    while(1)
    {
        printf("***********");
        fflush(stdout);
        sleep(1);
        printf("\r");
        printf("...........");
        sleep(1);
    }

    return 0;
}
1
Kknd

プログラムは、端末が特別な方法で解釈する特殊文字を印刷することによってこれを行います。これの最も単純なバージョンは、(ほとんどのlinux/unix端末で)カーソル位置を現在の行の最初の文字にリセットする通常の標準出力に「\ r」(キャリッジリターン)を出力することです。したがって、次に書くものは、前に書いた行を上書きします。これは、たとえば、単純な進行状況インジケーターに使用できます。

int i = 0;
while (something) {
  i++;
  printf("\rprocessing line %i...", i);
  ...
}

ただし、さまざまな方法で解釈される、より複雑なエスケープ文字シーケンスがあります。これにより、画面上の特定の位置にカーソルを置いたり、テキストの色を設定したりするなど、さまざまなことができます。これらの文字シーケンスが解釈されるかどうか、またはどのように解釈されるかは端末によって異なりますが、ほとんどの端末でサポートされている一般的なクラスは ansiエスケープシーケンス です。したがって、赤いテキストが必要な場合は、次を試してください。

printf("Text in \033[1;31mred\033[0m\n");
1
sth

最も簡単な方法は、キャリッジリターン文字( '\ r')を標準出力に印刷することです。

カーソルが行頭に移動し、内容を上書きできるようになります。

0
morais