Bash入力リダイレクトを介して複数の読み取り(stdin)呼び出しに入力を提供する

Bash入力リダイレクトを介して複数の読み取り(stdin)呼び出しに入力を提供する

2回呼び出される次のプログラムがあるとしますread()

#include <stdio.h>
#include <unistd.h>

#define SIZE 0x100

int main(void)
{
    char buffer1[SIZE];
    char buffer2[SIZE];

    printf("Enter first input: \n");
    fflush(stdout);
    read(STDIN_FILENO, buffer1, SIZE);

    printf("Enter second input: \n");
    fflush(stdout);
    read(STDIN_FILENO, buffer2, SIZE);

    printf("\nFirst input:\n%s", buffer1);
    printf("\nSecond input:\n%s", buffer2);

    return 0;
}

直接呼び出すと、1最初の入力と22番目の入力を受け取り、印刷できます。

First input:
1

Second input:
2

入力リダイレクトを使用するときにこれを達成するには?

最初の方法は2つの入力を使用するため、次の方法は機能しませんread

パイプリダイレクト:

$ { echo "1"; echo "2"; } | ./main_read
Enter first input:
Enter second input:

First input:
1
2

Second input:

区切り文字リダイレクト:

$ ./main_read << EOF
1
2
EOF
Enter first input:
Enter second input:

First input:
1
2

Second input:

ソースコードを変更することはできませんし、入力が時々SIZE

2人目が残りの入力を使用できるread()ように、1人目に読書を停止するように指示する方法はありますか?read()

答え1

これは許容可能な解決策を提供しないかもしれませんが、次の点を考慮してください。

  • ソースコードは変更できません

  • シェルは、実行中のプログラムの開かれたファイル記述子が指す場所を変更できず、実行中のプログラムにファイル記述子の読み取りを停止させることもできません。

競争条件を利用することに加えて、残りの選択肢のいくつかは次のとおりです。

  • SIZEプログラムが常に一度にバイトを入力していることを確認してください。

    {
      echo foo | dd bs=256 conv=sync
      echo bar | dd bs=256 conv=sync
    } 2>/dev/null | ./main_read
    

    出力:

    Enter first input: 
    Enter second input: 
    
    First input:
    foo
    
    Second input:
    bar
    

    これは、少なくともSIZEパイプバッファサイズより小さいと仮定する。

  • expect(またはそれに対応する)スクリプトでプログラム呼び出しをラップします。

    expect <<'EOT'
    spawn ./main_read
    expect "Enter first input:"
    send "foo\n"
    expect "Enter second input:"
    send "bar\n"
    expect eof
    EOT
    

    または、別のコマンドの出力をパイプできるように別々に読み込みます(OSがプロセスのファイル記述子を提供すると仮定/dev/fd/n)。

    echo foo | {
      echo bar |
        expect 4<&0 <<'EOT'
        spawn ./main_read
        set chan [open "/dev/fd/3"]
        gets $chan line
        expect "Enter first input:"
        send "$line\r"
        close $chan
        set chan [open "/dev/fd/4"]
        gets $chan line
        expect "Enter second input:"
        send "$line\r"
        close $chan
        expect eof
    EOT
    } 3<&0
    

    どちらの場合も、出力は次のようになります。

    spawn ./main_read
    Enter first input: 
    foo
    Enter second input: 
    bar
    
    First input:
    foo
    
    Second input:
    bar
    
  • 非ブロック方式でパイプを開くことができるシステム(Linuxなど)では、FIFOを使用してシェルにプログラムを読み書きすることができます。たとえば、

    makefifo fifo
    {
      exec 3<>fifo
      ./main_read 0<&3
    } |
    sh -c '
      # read a line from the pipe
      # read from a file or a different pipe, write to fifo
      # repeat ...
      # echo the output from the pipe
    ' mysh
    

    しかし、expect可能であれば、これを再創造しなければならない説得力のある理由はありません。

しかし、他の人が指摘したように、約束はありません。すべてのプログラムはread実際にSIZEバイトを取得します。

答え2

ソースコードを変更できないと仮定

主にこの仮定の変更に焦点を当てる必要があります。

通常、呼び出しが返すバイト数は保証されませんread()。通常のファイルから読み取るときに通常要求されたバイト数(最大使用可能バイト数)を返しますが、すべての種類のファイル記述子に該当するわけではありません。システムで実行されるプロセス間の予約およびその他のタイミングの問題も、単一の呼び出しで使用可能なデータ量に影響を与える可能性があります。

read()読み取ったデータ量を確認せずに電話をかけると、ほとんど常にエラーが発生します。このような状況dd(呼び出し動作を明確に公開する必要があるread())とデータグラムソケットから読み取る(それぞれread()メッセージが表示されている)場合でも、プログラムはどのくらいのデータを取得したかを知る必要があります。

プログラムが行を読み取るには、rawの代わりにまたはをfgets()使用する必要があります。他の種類のブロックを読み取る必要がある場合は、これらのブロックを区別するための別の方法を実装する必要があります。これは、長さを先頭に追加したり、別のファイル記述子を使用したり、一部の区切り文字(改行文字と同じですがバイトより長い場合があります)を使用したりすることができます。getline()read()

つまり、stdinデータグラムソケットに接続する準備ができていない限り、これは非常に珍しい設定になり、実際にデータを提供するために通常の入力リダイレクトを使用することはできません。

また、バッファをに渡す前に、printf("%s")プログラムは文字列を終了するNULバイトが含まれていることを確認する必要があります。提供されたデータの1つにNULが含まれていない場合、プログラムはread()データがまったく返されない2番目のケースを含む未定義の動作を生成します。read()

答え3

以下のコメントで提案されているように、次のことができます。

{ echo a & sleep 0.1; echo b; } | ./main

睡眠スケジュールを調整する必要があります。このコマンドの要点は、read()への最初の呼び出しがこれが得られた完全な入力であるa と考えるようにすることです。これ仮説echo a &Cプログラムは、最初のread()によって完了して処理された後(注&- バックグラウンドで送信された)2番目のread()に達することです。しかし、Linuxは怠惰な仮想メモリ割り当てを実行する真のマルチユーザーマルチタスクオペレーティングシステムであるため、sleep 0.1 これらの仮定をすべての場合に適用するだけでは不十分です。

なぜ働くのか

{ echo a && echo b; } | ./main

唯一の違いは、read()が最初の読み取りで使用可能な標準入力全体(最大SIZE文字)を読み取り、2番目の読み取りでは後ろに文字を残さないことです。 read() が返した値を確認した場合:

#include <stdio.h>
#include <unistd.h>
#include <stdint.h>

#define SIZE 0x100

int main(void)
{
    char buffer1[SIZE];
    char buffer2[SIZE];

    printf("Enter first line of input: \n");

    ssize_t read_bytes = read(STDIN_FILENO, buffer1, SIZE);
    buffer1[read_bytes] = '\0';
    printf("First input - count of read bytes: %jd\n", (intmax_t) read_bytes);

    printf("Enter second line of input: \n");

    read_bytes = read(STDIN_FILENO, buffer2, SIZE);
    printf("Second input - count of read bytes: %jd\n", (intmax_t) read_bytes);
    buffer2[read_bytes] = '\0';

    printf("\nFirst input:\n%s", buffer1);
    printf("\nSecond input:\n%s", buffer2);

    return 0;
}

第二に、どの文字も読まないことがわかります。

$ ./main << EOF
1
2
EOF
Enter first line of input:
First input - count of read bytes: 4

First input:
1
2
Enter second line of input:
Second input - count of read bytes: 0

Second input:

First input:
1
2

Second input:

操作を完了するには、{ echo a && echo b; } | ./maingetline()に切り替えるか、両方の入力を単一のバッファに保存し、strtok()を使用してバッファを改行で解析する必要があります。 getline() バージョンは次のとおりです。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(void)
{
    size_t size = 0x100;
    char *buffer1 = malloc(size);
    if (buffer1 == NULL)
        {
            perror("malloc");
        }


    char *buffer2 = malloc(size);
    if (buffer2 == NULL)
        {
            perror("malloc");
        }

    printf("Enter first line of input: \n");
    getline(&buffer1, &size, stdin);

    printf("Enter second line of input: \n");
    getline(&buffer2, &size, stdin);

    printf("\nFirst input:\n%s", buffer1);
    printf("\nSecond input:\n%s", buffer2);

    free(buffer1);
    free(buffer2);

    return 0;
}

例:

$ ./main << EOF
1
2
EOF
Enter first line of input:
Enter second line of input:

First input:
1

Second input:
2

ここでは3つのことをさらに議論したいと思います。

  • fflush(stdout);標準出力は常に改行文字の後にフラッシュされるため、必要はありません。

  • マニュアルページはローカルなので、インターネット上でマンページを見つける必要はありません。man 2 read端末に入力するか、エディタで開きます(たとえば、Emacsはこれを行います)。

  • あなたの質問に投稿したコードにバグがあります。read() nul バイトは自動的に追加されないため、UB を防止するには直接これを行う必要があります。それが必要です:

      printf("Enter first line of input: \n");
      ssize_t read_bytes = read(STDIN_FILENO, buffer1, SIZE - 1);
      buffer1[read_bytes] = '\0';
    

関連情報