코 악마

코 악마

파일 입력이 있습니다.

$ cat input
1echo 12345

다음 프로그램이 있습니다

초판

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

int main() {
  system("/bin/bash -i");
  return 0;
}

이제 이것을 실행하면

$ gcc -o program program.c
$ ./program < input
bash: line 1: 1echo: command not found
$ exit

모든 것이 예상대로 작동합니다.

getchar()이제 파일 입력의 첫 번째 문자를 무시하고 싶기 때문에 system().

두번째 버전:

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

int main() {
  getchar();
  system("/bin/bash -i");
  return 0;
}

놀랍게도 bash는 입력이 없는 것처럼 즉시 종료됩니다.

$ gcc -o program program.c
$ ./program < input
$ exit

질문Bash가 입력을 받지 못하는 이유는 무엇입니까?

노트 몇 가지를 시도한 결과 기본 프로세스에서 새 하위 프로세스를 포크하면 문제가 해결되는 것으로 나타났습니다.

세 번째 버전

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {
  getchar();
  if (fork() > 0) {
    system("/bin/bash -i");
    wait(NULL);
  }
  return 0;
}

$ gcc -o program program.c
$ ./program < input
$ 12345
$ exit

운영 체제우분투 16.04 64비트, gcc 5.4

답변1

파일 스트림다음과 같이 정의됩니다.:

참조된 대화형 장치가 없는 것으로 확인될 수 있는 경우에만 완전히 버퍼링됩니다.

표준 입력으로 리디렉션하므로 stdin은 비대화형이므로 버퍼링됩니다.

getchar버퍼를 스트림에서 채우고 해당 바이트를 소비한 다음 바이트를 반환하는 스트림 함수입니다.system그냥 fork-exec를 실행하세요이므로 하위 프로세스는 열려 있는 모든 파일 설명자를 그대로 상속합니다. 표준 입력에서 읽으려고 하면 bash상위 프로세스에서 모든 내용을 읽었기 때문에 이미 파일 끝에 있다는 것을 알게 됩니다.


귀하의 경우 해당 단일 바이트를 하위 프로세스에 넘겨주기 전에만 소비하려고 하므로 다음과 같습니다.

setvbuf()함수는 스트림이 가리키는 스트림이 열린 파일과 연결된 후 스트림에서 다른 작업이 수행되기 전에 사용할 수 있습니다.

따라서 이전에 적절한 호출을 추가하십시오 getchar().

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

int main() {
  setvbuf(stdin, NULL, _IONBF, 0 );
  getchar();
  system("/bin/bash -i");
  return 0;
}

stdin버퍼링되지 않음( )으로 설정하여 _IONBF원하는 작업을 수행하세요 . getchar단일 바이트만 읽혀지고 나머지 입력은 하위 프로세스에서 사용할 수 있습니다. 사용하는 것이 더 좋을 수도 있습니다read대신 이 경우 전체 스트림 인터페이스를 사용하지 마십시오.


POSIX는 포크 후 두 프로세스 모두에서 핸들에 액세스할 수 있을 때 특정 동작을 강제합니다., 그러나 분명히 명시되어 있음

프로세스 중 하나에 의해 수행된 유일한 작업이 다음 중 하나인 경우exec함수 [...], 이 프로세스에서는 핸들에 액세스할 수 없습니다.

이는 system()특별한 조치를 취할 필요가 없음을 의미합니다. 왜냐하면이건 그냥 포크 실행(fork-exec)이에요.

해결 방법 에 문제가 있을 수 있습니다 fork. 손잡이를 양쪽에서 접근할 수 있는 경우,그럼 처음으로:

읽기를 허용하는 모드에서 스트림이 열리고 기본 열린 파일 설명이 찾을 수 있는 장치를 참조하는 경우 애플리케이션은 다음을 실행해야 합니다.fflush(), 그렇지 않으면 스트림이 닫힙니다.

부르다fflush()읽기 스트림에서는 다음을 의미합니다.

기본 열린 파일 설명의 파일 오프셋은 스트림의 파일 위치로 설정되어야 합니다.

따라서 설명자 위치는 스트림과 동일한 위치인 1바이트로 다시 재설정되어야 하며 후속 하위 프로세스는 해당 지점에서 시작하는 표준 입력을 받게 됩니다.

또한 두 번째(자식) 핸들의 경우:

위의 첫 번째 핸들에 대한 요구 사항 외에도 파일 오프셋을 명시적으로 변경하는 함수에서 이전에 활성 핸들을 사용한 경우 애플리케이션은 다음을 수행해야 합니다.lseek()또는fseek()(핸들 유형에 따라) 적절한 위치로 이동합니다.

나는 "적절한 위치"가 아마도 동일할 것이라고 생각합니다(더 이상 지정되지는 않았지만). getchar()"파일 오프셋을 명시적으로 변경했습니다" 라는 호출이 있으므로 이 경우가 적용되어야 합니다. 이 구절의 목적은 포크의 두 가지 모두에서 작업하는 것이 동일한 효과를 가져야 하므로 fork() > 0및 및 모두 fork() == 0동일하게 작동해야 한다는 것입니다. 그러나 해당 분기에서는 실제로 아무 일도 일어나지 않으므로 이러한 규칙을 부모나 자식에 전혀 사용해서는 안 된다고 말하는 것이 타당합니다.

정확한 결과는 아마도 플랫폼에 따라 다를 것입니다. 적어도 "액세스 가능"으로 간주되는 항목이나 첫 번째 핸들과 두 번째 핸들에 대한 직접적인 지정은 없습니다. 상위 프로세스에 대한 이전의 가장 중요한 사례가 있습니다.

이 열린 파일 설명자에 대한 핸들에 대해 수행되는 유일한 추가 작업이 핸들을 닫는 것이라면 작업이 필요하지 않습니다.

이는 프로그램이 종료되기 때문에 아마도 프로그램에 적용됩니다. 그렇다면 을 포함한 나머지 모든 사례를 fflush()건너뛰어야 하며 표시되는 동작은 사양에서 벗어나게 됩니다. 틀림없이 호출은 fork()핸들에 대한 작업을 수행하는 것으로 간주되지만 명확하지도 명확하지도 않으므로 그렇게 믿지 않습니다. 요구사항에도 "둘 중 하나"와 "또는"이 충분히 있으므로 많은 변형이 허용되는 것 같습니다.

많은 이유들로 인해내 생각에 당신이 보고 있는 행동은 버그일지도 모릅니다., 또는 적어도 규범에 대한 관대 한 해석. 내 전반적인 해석은 각 경우에 하나의 분기가 아무 작업도 하지 않기 때문에 fork이러한 규칙을 적용해서는 안 되며 설명자 위치는 무시되어야 한다는 것입니다. 나는 이것에 대해 확신할 수 없지만 가장 간단한 읽기인 것 같습니다.


나는 fork일하기 위해 기술에 의존하지 않을 것입니다. 귀하의 세 번째 버전은 여기서는 작동하지 않습니다.사용 setbuf/setvbuf대신에. 가능하다면 나는 심지어 사용할 것이다popen또는 스트림 및 파일 설명자 상호 작용의 모호함에 의존하기보다는 필요한 필터링을 사용하여 프로세스를 명시적으로 설정하는 것과 같습니다.

답변2

나는 박수를 보낸다마이클 호머의 답변, POSIX 참조를 찾으세요. 하지만 저는 이 내용을 적어도 70%는 이해했다고 생각하고, 그의 답변을 완전히 이해하지는 못해서 이렇게 준비했습니다.긴 이야기 짧게나는 그의 버전을 믿습니다.

  • getchar(일명 getc), 파일에서 읽을 때 실제로 파일에서 하나의 블록(또는 전체 파일 중 더 작은 것)을 읽습니다. 데이터를 버퍼로 읽어옵니다. 그런 다음 버퍼의 첫 번째 문자를 제공합니다.

    • 후속 호출은 버퍼가 소진될 때까지 버퍼에서 후속 문자를 반환합니다. 그런 다음 파일에서 다른 블록을 읽으려고 시도합니다. 이 "버퍼링된 I/O"는 프로그램이 파일을 읽고 한 번에 조금씩 처리하는 것을 더 효율적으로 만듭니다.


    버퍼링된 파일 스트림의 논리적 I/O 포인터가 파일의 문자이더라도 이로 인해 파일 설명의 I/O 포인터가 파일의 블록으로 이동됩니다(또는 작은 파일의 경우 끝 부분). 파일의). 분기할 때 system하위 프로세스는 파일 설명을 상속하지만 파일 스트림은 상속하지 않으므로 bash는 파일 끝에 있는 파일 I/O 포인터를 상속합니다. 따라서 파일을 읽을 때 EOF를 얻습니다.

  • 프로그램의 세 번째 버전에서는 직접 호출 main됩니다 (단순히 호출 되는 fork것이 아니라 ). 물론, 프로그램 의 두 번째 버전과 마찬가지로 .  systemforkmaingetcharMichael이 찾은 POSIX 문서ISO C 표준에 대한 확장을 설명하고 있음을 분명히 하십시오. 그 비밀스러운 언어에서 알 수 있는 한, main위의 문제를 알아차린 데는 C 컴파일러가 책임이 있다고 합니다. fork함수를 호출하는 루틴에 의해 직접 호출되는 경우 stdio, 그리고 그것을 해결하십시오. 따라서 분명히 fork(또는 다른 메커니즘?) stdio패밀리와 상호 작용하여 파일 설명의 물리적 I/O 포인터가 다시 이동하여 파일 포인터의 논리적 I/O 포인터와 동기화됩니다. 즉, 문자를 파일에 넣습니다. . 두 번째 것 fork(숨겨진 것) system은 쉘이 두 번째 바이트부터 시작하는 파일을 읽을 수 있게 하기 때문에 우연히 이것으로부터 이익을 얻습니다.

관련 매뉴얼 페이지를 대략적으로 검색했지만 이 동작을 설명하는 정보를 찾을 수 없었습니다. 나도 당신의 결과를 재현할 수 없습니다. 따라서 이는 POSIX에 지정된 동작일 수 있지만 아직 보편적으로 구현되지는 않은 것 같습니다.

답변3

코 악마

man 3 getchar설명하다:

The getchar() function shall be equivalent to getc(stdin).

그리고man 3 getc설명하다:

It is not advisable to mix calls to  input  functions  from  the  stdio
library  with  low-level  calls  to  read(2)  for  the  file descriptor
associated with the input stream; the results  will  be  undefined  and
very probably not what you want.

대화형 모드의 Bash는 read(readline을 통해) 등을 사용하여 입력에 직접 액세스할 수 있습니다. 그래서 우리는코 악마.

관련 정보