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

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

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

$ echo hi > x
$ cat x >> x

stdout에 쓰기를 시작한 후에는 cat계속해서 읽는 것으로 추측할 수 있습니다. x그러나 혼란스럽게도 cat에 대한 내 테스트 구현은 다른 동작을 나타냅니다.

// 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

물론아니요반지 모양. 동작과 cat다시 호출하기 전에 새로 고침을 호출한다는 사실을 고려하면 이 C 코드가 루프에서 계속 읽고 쓰기를 원합니다.stdoutfread

이 두 가지 행동이 어떻게 일치합니까? cat루프가 발생하지만 위 코드는 발생하지 않는 이유를 설명하는 메커니즘은 무엇입니까 ?

답변1

내가 가지고 있는 이전 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"과 달리) 커널에서 발생하는 것입니다.

open(av[1])내 생각에는 파일 설명자 3( 의 결과 )이 파일의 오프셋 0에 있다는 것 입니다. 파일 설명자 1(stdout)은 오프셋 3에 있습니다. 왜냐하면 ">>"를 사용하면 파일 설명자에서 셸이 호출되어 하위 프로세스 lseek()로 전달되기 때문입니다.cat

read()stdio 버퍼에 들어가든 일반 작업을 수행하든 관계없이 모든 종류의 작업을 수행하면 char buf[]파일 설명자 3의 위치가 향상됩니다. 작업을 수행하면 write()파일 설명자 1의 위치가 올라갑니다. 이 두 오프셋은 서로 다른 숫자입니다. ">>" 때문에 파일 설명자 1의 오프셋은 항상 파일 설명자 3의 오프셋보다 크거나 같습니다. 따라서 "고양이 같은" 프로그램은 내부 버퍼링을 수행하지 않는 한 반복됩니다. a(코드의 FILE *기호 유형 stdout)의 stdio 구현에 자체 버퍼가 포함되어 있을 가능성도 있습니다 . 내부 버퍼를 채우기 위해 실제로 시스템 호출이 수행될 수 있습니다. 내부적으로는 아무것도 변경될 수도 있고 변경되지 않을 수도 있습니다. 호출은 내부적으로 아무것도 변경하지 않을 수도 있습니다. 따라서 stdio 기반 "cat"은 반복되지 않을 수 있습니다. 아니면 그럴 수도 있습니다. 보기 흉한 libc 코드를 많이 읽지 않고는 말하기가 어렵습니다.ffread()read()fstdoutfwrite()stdoutf

저는 straceRHEL에서 하나를 만들었습니다 cat. 이것은 일련의 시스템 read()호출 만 수행합니다 write(). 하지만 꼭 cat이런 식으로 작업할 필요는 없습니다. 파일이 mmap()입력된 다음 실행될 수 있습니다 write(1, mapped_address, input_file_size). 커널이 모든 작업을 수행합니다. 또는 sendfile()Linux 시스템에서 입력 및 출력 파일 설명자 간에 시스템 호출을 수행할 수 있습니다. 이전 SunOS 4.x 시스템에서는 메모리 매핑 트릭을 수행한다는 소문이 있지만, sendfile 기반 고양이를 사용하여 그런 작업을 수행한 사람이 있는지는 알 수 없습니다. 두 경우 모두 길이 매개변수를 전송 write()해야 하므로 "루프"가 발생하지 않습니다.sendfile()

답변2

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

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

답변3

쓰여진대로헤비히트 트랩,동일한 파이프에서 파일을 읽고 파일에 쓸 수 없습니다.

파이프가 수행하는 작업에 따라 파일이 손상될 수 있습니다(0바이트 또는 운영 체제의 파이프 버퍼 크기와 동일한 바이트 수). 사용 가능한 디스크 공간을 채울 때까지 증가하거나 해당 지점에 도달할 수 있습니다. 귀하의 운영 시스템 파일 크기 제한 또는 할당량 등

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

답변4

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

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

이것이 감지되지 않으면 동작은 분명히 구현에 따라 다릅니다(버퍼 크기 등).

clearerr(f);파일 끝 표시기가 설정된 경우 다음 코드에서 오류가 반환될 fflush경우를 대비하여 코드에서 그 뒤에 추가해 볼 수 있습니다.fread

편집하다:이런 행동은 결국오스틴 패널 토론, 실패를 허용합니다.

관련 정보