cat x >> x가 반복되는 이유는 무엇입니까? > x $ cat x

다음 bash 명령은 무한 루프로 들어갑니다.

$ echo hi > x
$ cat x >> x

나는 그 추측 할 수 cat읽기 계속 x이 표준 출력에 쓰기 시작 후. 그러나 혼란스러운 것은 고양이에 대한 테스트 구현이 다른 동작을한다는 것입니다.

// mycat.c
#include <stdio.h>

int main(int argc, char **argv) {
  FILE *f = fopen(argv[1], "rb");
  char buf[4096];
  int num_read;
  while ((num_read = fread(buf, 1, 4096, f))) {
    fwrite(buf, 1, num_read, stdout);
    fflush(stdout);
  }

  return 0;
}

내가 달리면 :

$ make mycat
$ echo hi > x
$ ./mycat x >> x

반복 되지 않습니다 . 동작 catstdout이전에 플러시하고 있다는 사실을 fread다시 호출하면이 C 코드가 계속해서 읽고 쓰기를 기대합니다.

이 두 행동은 어떻게 일치합니까? cat위의 코드가 아닌 루프가 반복되는 이유는 무엇입니까 ?



답변

내가 가지고있는 오래된 RHEL 시스템 에서는 루프 /bin/cat하지 않습니다cat x >> x . cat“cat : x : 입력 파일이 출력 파일입니다”라는 오류 메시지가 표시됩니다. 이렇게하면 바보 /bin/cat가 될 수 있습니다 cat < x >> x. 위의 코드를 시도하면 설명하는 “루핑”이 나타납니다. 또한 “cat”기반의 시스템 호출을 작성했습니다.

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int
main(int ac, char **av)
{
        char buf[4906];
        int fd, cc;
        fd = open(av[1], O_RDONLY);
        while ((cc = read(fd, buf, sizeof(buf))) > 0)
                if (cc > 0) write(1, buf, cc);
        close(fd);
        return 0;
}

이것도 반복됩니다. stdio 기반 “mycat”과 달리 여기에서 버퍼링은 커널에서 진행되는 것입니다.

무슨 일이 일어나고 있다고 생각하는 것은 파일 디스크립터 3 (의 결과 open(av[1]))에 0 파일에 대한 오프셋이 있다는 것입니다. “>>”로 인해 호출하는 쉘 lseek()cat하위 프로세스 로 전달하기 전에 파일 디스크립터 .

수행 read()여부는 stdio 버퍼에, 어떤 종류의를, 또는 일반 char buf[]이렇게 파일 기술자 (3)의 위치 발전 write()의 발전이 두 오프셋이 다른 숫자 파일 기술자 (1)의 위치를. “>>”때문에 파일 디스크립터 1은 항상 파일 디스크립터 3의 오프셋보다 크거나 같은 오프셋을 갖습니다. 따라서 “cat-like”프로그램은 내부 버퍼링을 수행하지 않는 한 루프됩니다. 아마도 자체 버퍼를 포함 FILE *하는 (기호 stdoutf코드 의 유형 인) stdio 구현이 가능할 수도 있습니다. fread()실제로 read()내부 버퍼 fo를 채우기 위해 시스템 호출 을 수행 할 수 있습니다 f. 내부의 내용이 변경되거나 변경되지 않을 수 있습니다 stdout. 호출 fwrite()stdout내부의 내용을 변경하거나 변경하지 않을 수 있습니다 f. 따라서 stdio 기반 “cat”은 반복되지 않을 수 있습니다. 아니면 그렇습니다. 못생긴, 못생긴 libc 코드를 많이 읽지 않으면 말하기가 어렵습니다.

내가했던 straceRHEL은에 cat– 그것은 단지의 연속 수행 read()write()시스템 호출. 그러나 cat이런 식으로 작동하지 않아도됩니다. mmap()입력 파일에 가능할 수 있습니다 write(1, mapped_address, input_file_size). 커널은 모든 작업을 수행합니다. 또는 sendfile()Linux 시스템에서 입력 및 출력 파일 디스크립터간에 시스템 호출을 수행 할 수 있습니다. 오래된 SunOS 4.x 시스템은 메모리 매핑 트릭을 수행한다는 소문이 있었지만 어느 누구도 sendfile 기반 고양이를 한 적이 있는지 모르겠습니다. 두 경우 모두에서 “루프”모두 같이 일어나지 않을 것입니다 write()sendfile()길이-에 전송 매개 변수를 필요로한다.


답변

최신 cat 구현 (sunos-4.0 1988)은 mmap ()을 사용하여 전체 파일을 매핑 한 다음이 공간에 대해 1x write ()를 호출합니다. 가상 메모리가 전체 파일을 매핑 할 수있는 한 이러한 구현은 반복되지 않습니다.

다른 구현의 경우 파일이 I / O 버퍼보다 ​​큰지 여부에 따라 다릅니다.


답변

에 기록 된대로 배쉬 함정 , 동일한 파이프 라인의 그것에 파일 및 쓰기에서 읽을 수 없습니다.

파이프 라인의 기능에 따라 파일이 클로버되거나 (0 바이트 또는 운영 체제의 파이프 라인 버퍼 크기와 동일한 바이트 수) 사용 가능한 디스크 공간을 채우거나 도달 할 때까지 커질 수 있습니다. 운영 체제의 파일 크기 제한 또는 할당량 등

해결책은 텍스트 편집기 또는 임시 변수를 사용하는 것입니다.


답변

둘 사이에 일종의 경쟁 조건이 있습니다 x. 일부 구현 cat(예 : coreutils 8.23)은 다음을 금지합니다.

$ cat x >> x
cat: x: input file is output file

이것이 감지되지 않으면, 동작 (버퍼 크기 등)에 따라 동작이 분명히 달라집니다.

당신의 코드에서, 당신은을 추가 할 수 clearerr(f);fflush, 경우에 다음이 fread파일 끝 표시가 설정되어있는 경우 오류를 반환합니다.