Bash 입력 리디렉션을 통해 여러 읽기(stdin) 호출에 입력 제공

Bash 입력 리디렉션을 통해 여러 읽기(stdin) 호출에 입력 제공

두 번 호출되는 다음 프로그램이 있다고 가정합니다 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첫 번째 입력과 2두 번째 입력을 받아 인쇄할 수 있습니다.

First input:
1

Second input:
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.

두 번째 사람이 나머지 입력을 사용할 수 read()있도록 첫 번째 사람에게 읽기를 중지하도록 알리는 방법이 있습니까 ?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 바이트가 포함되어 있는지 확인해야 합니다. 이제 제공된 데이터 중 하나에 NUL이 포함되어 있지 않으면 프로그램은 read()데이터가 전혀 반환되지 않는 두 번째 경우를 포함하여 정의되지 않은 동작을 생성합니다.read()

답변3

아래 의견에서 제안한 대로 다음을 수행할 수 있습니다.

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

수면 일정을 조정해야 할 수도 있습니다. 이 명령의 요점은 read()에 대한 첫 번째 호출이 이것이 a 얻은 전체 입력이라고 생각하도록 하는 것입니다. 이것가설echo a &C 프로그램은 첫 번째 read()에 의해 완료되고 처리된 후 (참고 &- 백그라운드로 전송됨) 두 번째 read()에 도달한다는 것입니다 . 그러나 Linux는 게으른 가상 메모리 할당도 수행하는 진정한 다중 사용자 다중 작업 운영 체제이므로 sleep 0.1 이러한 가정을 모든 경우에 적용하는 것만으로는 충분하지 않을 수 있습니다.

왜 작동하는지 그리고

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

유일한 차이점은 read()가 첫 번째 읽기에서 사용 가능한 전체 표준 입력(최대 SIZE 문자)을 읽고 두 번째 읽기에서는 뒤에 문자를 남기지 않는다는 것입니다. 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';
    

관련 정보