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


17

다음 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위의 코드가 아닌 루프가 반복되는 이유는 무엇입니까 ?


그것은 나를 위해 루프 않습니다. strace / truss에서 실행 해 보셨습니까? 어떤 시스템을 사용하고 있습니까?
Stéphane Chazelas

BSD cat에 이런 동작이있는 것 같고 GNU cat은 이와 같은 것을 시도 할 때 오류를보고합니다. 답변은 똑같이 설명하며 GNU 고양이가 있고 테스트 할 때 오류가 발생했기 때문에 BSD 고양이를 사용하고 있다고 생각합니다.
Ramesh

다윈을 사용하고 있습니다. 나는 cat x >> x오류 를 일으키는 아이디어를 좋아한다 . 그러나이 명령은 Kernighan 및 Pike의 Unix 책에서 연습으로 제안됩니다.
Tyler

3
cat대부분 stdio 대신 시스템 호출을 사용합니다. stdio를 사용하면 프로그램이 EOFness를 캐싱 할 수 있습니다. 4096 바이트보다 큰 파일로 시작하면 무한 루프가 발생합니까?
Mark Plotnick

@MarkPlotnick, 그렇습니다! 파일이 4k를 초과하면 C 코드가 반복됩니다. 고마워, 아마도 그것이 바로 전체의 차이입니다.
Tyler

답변:


12

내가 가지고있는 오래된 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()길이-에 전송 매개 변수를 필요로한다.


감사. Darwin에서는 freadMark Plotnick이 제안한대로 통화가 EOF 플래그를 캐시 한 것처럼 보입니다 . 증거 : [1] 다윈 고양이는 읽지 않고 읽습니다. 그리고 [2] 다윈의 확산은 __srefill을 호출 fp->_flags |= __SEOF;하여 어떤 경우에는 설정합니다 . [1] src.gnu-darwin.org/src/bin/cat/cat.c [2] opensource.apple.com/source/Libc/Libc-167/stdio.subproj/…
Tyler

1
이것은 굉장하다-나는 그것을 어제 그것을 처음으로 찬성했다. 그것은 언급 할 가치가있을 만을 위한 POSIX 정의 스위치가 cat있습니다 cat -u- 에 대한 버퍼링 .
mikeserv

실제로, 플래그 >>와 함께 open ()을 호출하여 구현해야합니다. 이렇게 하면 파일 디스크립터의 위치가 읽기 이전의 위치에 관계없이 모든 쓰기 작업이 파일의 현재 끝에 (원자 적으로) 쓰기됩니다 . 이 동작은 예를 들어 올바르게 작동 하기 위해 필요 합니다. 자신의 마지막 쓰기가 끝난 후의 위치가 여전히 파일의 끝이라고 가정 할 수는 없습니다. O_APPENDfoo >> logfile & bar >> logfile
hmakholm 님이 Monica

1

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

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


많은 cat구현이 출력을 버퍼링하지 않습니다 ( -u암시 적). 그것들은 항상 반복됩니다.
Stéphane Chazelas

Solaris 11 (SunOS-5.11)은 작은 파일에 mmap ()을 사용하지 않는 것 같습니다 (32769 바이트 이상의 파일에만 사용함).
Stéphane Chazelas

올바른 -u가 일반적으로 기본값입니다. 구현이 전체 파일 크기를 읽고 해당 buf로 한 번만 쓸 수 있으므로 루프를 의미하지는 않습니다.
schily

파일 크기가> 최대 맵 크기이거나 초기 파일 오프셋이! = 0 인 경우에만 Solaris cat이 루프합니다.
schily

Solaris 11에서 관찰 한 사항입니다. 초기 오프셋이! = 0이거나 파일 크기가 0과 32768 사이 인 경우 read () 루프를 수행합니다. PiB 파일 (희소 파일에서 테스트 됨)조차도 read () 루프로 되 돌리는 것 같습니다.
Stéphane Chazelas

0

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

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

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


-1

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

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

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

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


좋은 OS는 동일한 읽기 / 쓰기 명령을 실행하는 단일 스레드를 사용하여 단일 프로세스에 대해 결정적인 동작을 갖는 것으로 보입니다. 어쨌든, 그 행동은 나에게 결정적이며, 나는 주로 불일치에 대해 묻습니다.
Tyler

@Tyler IMHO,이 경우에 명확한 지정이 없으면 위의 명령은 의미가 없으며 결정론은 실제로 중요하지 않습니다 (여기서 가장 좋은 동작 인 오류는 제외). 이것은 C의 i = i++;정의되지 않은 동작 과 비슷 하므로 불일치입니다.
vinc17

1
아니요, 여기에는 경쟁 조건이 없으며 동작이 잘 정의되어 있습니다. 그러나 파일의 상대 크기와에서 사용하는 버퍼에 따라 구현에 따라 정의됩니다 cat.
Gilles 'SO- 악마 그만'

@Gilles 행동이 잘 정의 / 구현 정의 된 곳은 어디입니까? 당신은 몇 가지 참조를 줄 수 있습니까? POSIX cat 사양 은 "-u 옵션이 지정되지 않은 경우 cat 유틸리티가 출력을 버퍼링하는지 여부는 구현 정의입니다"라고 말합니다. 그러나 버퍼를 사용하는 경우 구현시 버퍼 사용 방법을 정의 할 필요는 없습니다. 예를 들어 임의의 시간에 버퍼가 플러시되는 비 결정적 일 수 있습니다.
vinc17

@ vinc17 이전 코멘트에 "실제로"삽입하십시오. 그렇습니다. 이론적으로 가능하고 POSIX를 준수하지만 아무도 그렇게하지 않습니다.
Gilles 'SO- 악마 그만'
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.