내부에서 파일을 수정하는 방법이 있습니까?


54

상당히 큰 파일 (35Gb)이 있고이 파일을 제자리에서 필터링하고 싶습니다 (예 : 다른 파일을위한 충분한 디스크 공간이 없음). 특히 grep하고 일부 패턴을 무시하고 싶습니다. 다른 파일을 사용하지 않고이 작업을 수행합니까?

foo:예를 들어 포함하는 모든 줄을 필터링하고 싶다고 가정 해 봅시다 .


3
@ Tshepang : 나는 그가 같은 파일에 다시 쓰고 싶다고 생각합니다.
Faheem Mitha

5
"in situ"는 "in placeu"를 의미하는 라틴어 문구입니다. 말 그대로 "위치에"있습니다.
Faheem Mitha

3
이 경우 파일을 제자리에서 수정하는 방법이 있습니까?
tshepang

5
@Tshepang, "in situ"는 영어로 정확하게 설명하기 위해 사용되는 상당히 일반적인 문구입니다. 제목이 꽤 자명하다고 생각했습니다 ... @Gilles, 더 많은 디스크 공간을 기다리는 것이 더 쉬웠습니다! ;)
Nim

2
@Nim : 글쎄, 나는 in- situ 보다 in -place 가 더 일반적 이라고 생각 합니다.
tshepang

답변:


41

시스템 호출 레벨에서 가능해야합니다. 프로그램은 대상 파일을 자르지 않고 쓰기 위해 열고 stdin에서 읽은 내용을 쓰기 시작할 수 있습니다. EOF를 읽을 때 출력 파일이 잘릴 수 있습니다.

입력에서 행을 필터링하므로 출력 파일 쓰기 위치는 항상 읽기 위치보다 작아야합니다. 이것은 새로운 출력으로 입력을 손상시키지 않아야한다는 것을 의미합니다.

그러나이를 수행하는 프로그램을 찾는 것이 문제입니다. 출력 파일을 열 때 자르지 않는 dd(1)옵션 conv=notrunc이 있지만 마지막 부분도 자르지 않고 grep 내용 뒤에 원래 파일 내용을 남겨 둡니다 (와 같은 명령 사용 grep pattern bigfile | dd of=bigfile conv=notrunc)

시스템 호출 관점에서 매우 간단하기 때문에 작은 프로그램을 작성하여 작은 (1MiB) 전체 루프백 파일 시스템에서 테스트했습니다. 원하는 것을 수행했지만 실제로 다른 파일로 먼저 테스트하려고합니다. 항상 파일을 덮어 쓸 위험이 있습니다.

overwrite.c

/* This code is placed in the public domain by camh */

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

int main(int argc, char **argv)
{
        int outfd;
        char buf[1024];
        int nread;
        off_t file_length;

        if (argc != 2) {
                fprintf(stderr, "usage: %s <output_file>\n", argv[0]);
                exit(1);
        }
        if ((outfd = open(argv[1], O_WRONLY)) == -1) {
                perror("Could not open output file");
                exit(2);
        }
        while ((nread = read(0, buf, sizeof(buf))) > 0) {
                if (write(outfd, buf, nread) == -1) {
                        perror("Could not write to output file");
                        exit(4);
                }
        }
        if (nread == -1) {
                perror("Could not read from stdin");
                exit(3);
        }
        if ((file_length = lseek(outfd, 0, SEEK_CUR)) == (off_t)-1) {
                perror("Could not get file position");
                exit(5);
        }
        if (ftruncate(outfd, file_length) == -1) {
                perror("Could not truncate file");
                exit(6);
        }
        close(outfd);
        exit(0);
}

당신은 그것을 다음과 같이 사용할 것입니다 :

grep pattern bigfile | overwrite bigfile

나는 주로 당신이 그것을 시도하기 전에 다른 사람들이 의견을 남길 수 있도록 이것을 게시하고 있습니다. 아마도 다른 누군가가 더 많은 테스트를 거친 비슷한 프로그램을 알고있을 것입니다.


나는 무언가를 쓰지 않고 도망 갈 수 있는지보고 싶었다! :) 이것이 트릭을 할 것 같아요! 감사!
Nim

2
C의 경우 +1; 작동하는 것처럼 보이지만 잠재적 인 문제가 있습니다. 오른쪽이 동일한 파일에 쓰는 동안 파일을 왼쪽에서 읽는 중이며 두 프로세스를 조정하지 않으면 잠재적으로 동일한 파일에서 문제를 덮어 씁니다. 블록. 대부분의 핵심 도구는 8192를 사용하기 때문에 파일 무결성이 더 작은 블록 크기를 사용하는 것이 좋습니다. 이렇게하면 충돌을 피할 수있을만큼 프로그램 속도가 느려질 수 있지만 보장 할 수는 없습니다. 더 큰 부분을 메모리로 읽고 (모두는 아님) 더 작은 블록으로 쓸 수 있습니다. nanosleep (2) / usleep (3)을 추가 할 수도 있습니다.
Arcege

4
@Arcege : 블록 단위로 쓰기가 수행되지 않습니다. 읽기 프로세스에서 2 바이트를 읽었고 쓰기 프로세스에서 1 바이트를 쓰면 첫 번째 바이트 만 변경되며 해당 시점의 원래 내용을 변경하지 않고 읽기 프로세스는 바이트 3에서 계속 읽을 수 있습니다. 보낸 사람 grep이 읽고 출력 할 수 없습니다 더 많은 데이터를보다 쓰기 위치는 항상 읽기 위치 뒤에해야합니다. 독서와 같은 속도로 글을 쓰더라도 여전히 괜찮습니다. grep 대신 이것을 사용하여 rot13을 시도한 다음 다시 시도하십시오. 이전과 이후에 md5sum을 보시면 똑같이 보입니다.
camh

6
좋은. 이것은 Joey Hess의 moreutils에 귀중한 추가 사항 일 수 있습니다 . 당신은 사용할 수 있습니다dd ,하지만 성가신입니다.
Gilles

'그렙 패턴 빅 파일 | 큰 파일 덮어 쓰기 '-오류없이 작동하지만 이해할 수없는 것은 패턴의 내용을 다른 텍스트로 바꿔야 할 필요가 있습니까? 'grep pattern bigfile | 덮어 쓰기 / replace-text / bigfile '
Alexander Mills

20

sed파일을 편집 하는 데 사용할 수 있습니다 (그러나 중간 임시 파일이 생성됨).

다음을 포함하는 모든 줄을 제거하려면 foo:

sed -i '/foo/d' myfile

다음을 포함하는 모든 줄을 유지하려면 foo:

sed -i '/foo/!d' myfile

흥미롭게도이 임시 파일은 원본과 같은 크기 여야합니까?
Nim

3
예, 아마 좋지 않을 것입니다.
pjc50

17
OP는 두 번째 파일을 생성하기 때문에 요구하는 것이 아닙니다.
Arcege

1
"읽기 전용"당신이 있음을 의미 곳이 솔루션은 읽기 전용 파일 시스템에 실패 $HOME 할 것이다 쓸 수 있지만 /tmp됩니다 읽기 전용 (기본적으로). 예를 들어, Ubuntu가 있고 복구 콘솔로 부팅 한 경우가 일반적입니다. 또한, 여기-문서 운영자 <<<가 필요로 하나가 작동하지 않습니다 /tmpR / W 그것뿐만 아니라 거기에 임시 파일을 쓰기 때문입니다. (cf. 이 질문strace'd 출력을 포함합니다. )
syntaxerror

그래, 이것은 나에게도 효과가 없다. 내가 시도한 모든 sed 명령은 현재 파일을 새로운 파일로 대체 할 것이다 (--in-place 플래그에도 불구하고).
Alexander Mills

19

필자는 필터 명령이 접두사 축소 필터 라고 부르는 것으로 가정합니다.이 필터 는 출력의 바이트 N이 N 바이트 이상의 입력을 읽기 전에 절대 쓰지 않는 속성을 갖습니다. grep이 속성을 가지고 있습니다 (만 필터링하고 일치하는 줄 번호 추가와 같은 다른 일을하지 않는 한). 이러한 필터를 사용하면 입력을 덮어 쓸 수 있습니다. 물론 파일의 시작 부분에서 덮어 쓴 부분이 영구적으로 손실되므로 실수를하지 않아야합니다.

대부분의 유닉스 도구는 파일을 덮어 쓰지 않고 파일에 추가하거나자를 수 있습니다. 표준 도구 상자의 한 가지 예외 dd는 출력 파일을 자르지 않도록 지시 할 수 있습니다. 따라서 계획은로 명령을 필터링하는 것 dd conv=notrunc입니다. 파일 크기는 변경되지 않으므로 새 내용의 길이를 잡고 파일을 해당 길이로 자릅니다 (와 함께 dd). 이 작업은 본질적으로 강력하지 않습니다. 오류가 발생하면 사용자가 스스로해야합니다.

export LC_ALL=C
n=$({ grep -v foo <big_file |
      tee /dev/fd/3 |
      dd of=big_file conv=notrunc; } 3>&1 | wc -c)
dd if=/dev/null of=big_file bs=1 seek=$n

견고하게 동등한 Perl을 작성할 수 있습니다. 효율적이지 않은 빠른 구현은 다음과 같습니다. 물론 해당 언어로 직접 초기 필터링을 수행 할 수도 있습니다.

grep -v foo <big_file | perl -e '
  close STDOUT;
  open STDOUT, "+<", $ARGV[0] or die;
  while (<STDIN>) {print}
  truncate STDOUT, tell STDOUT or die
' big_file

16

Bourne과 같은 쉘 :

{
  cat < bigfile | grep -v to-exclude
  perl -e 'truncate STDOUT, tell STDOUT'
} 1<> bigfile

어떤 이유로, 사람들은 40 살 ¹과 표준 읽기 + 쓰기 리디렉션 연산자 를 잊는 경향이 있습니다.

우리는 열 bigfile읽기 + 쓰기 모드와에 잘림없이 (어떤 일이 대부분 여기에 중요한) stdout동안은 bigfile에 공개 (별도)이다 catstdin. grep종료 된 후 일부 라인이 제거 된 경우 stdout이제 내부 어딘가를 bigfile가리켜 서이 시점 이후의 항목을 제거해야합니다. 따라서 현재 위치에서 perl파일 ( truncate STDOUT) 을 자르는 명령 (에서 반환 tell STDOUT)

( stdin과 stdout이 동일한 파일을 가리키는 경우 그렇지 않으면 catGNU에 대한 것 grep입니다).


¹ 음, <>70 년대 후반부터 Bourne 쉘에 있었지만 처음에는 문서화되지 않았으며 제대로 구현되지 않았습니다 . 그것은 ash1989 년부터 원래 구현되지 않았으며 POSIX sh리디렉션 연산자 (POSIX shksh88항상 가지고 있었던 90 년대 초 이후 ) 였지만 sh2000 년까지 FreeBSD 에 추가되지 않았기 때문에 15 년이되었습니다. 오래된 것이 더 정확할 것입니다. 또한 지정되지 않은 경우 기본 파일 디스크립터 는 2010 년 ksh93t +에서 0에서 1로 변경 <>되었다는 점을 제외하고는 모든 쉘에 ksh93있습니다 (이전 호환성 및 POSIX 준수 중단).


2
당신은 설명 할 수 있습니까 perl -e 'truncate STDOUT, tell STDOUT'? 그것은 그것을 포함하지 않고 저에게 효과적입니다. Perl을 사용하지 않고 동일한 것을 달성 할 수있는 방법이 있습니까?
Aaron Blenkush

1
@AaronBlenkush, 편집 참조.
Stéphane Chazelas

1
절대적으로 훌륭합니다-감사합니다. 나는 그때 거기에 있었지만 이것을 기억하지 못한다 .... en.wikipedia.org/wiki/Bourne_shell 에서 언급되지 않았기 때문에“36 세”표준에 대한 참조는 재미있을 것이다 . 그리고 그것은 무엇을 위해 사용 되었습니까? SunOS 5.6의 버그 수정에 대한 참조를 보았습니다 redirection "<>" fixed and documented (used in /etc/inittab f.i.). . 하나의 힌트입니다.
nealmcb

2
@nealmcb, 편집을 참조하십시오.
Stéphane Chazelas

@ StéphaneChazelas 솔루션 이이 답변 과 어떻게 비교 됩니까? 분명히 같은 일을하지만 더 단순 해 보입니다.
akhan

9

이것은 오래된 질문이지만, 그것은 영원한 질문이며, 지금까지 제안 된 것보다 더 일반적이고 명확한 해결책을 사용할 수 있습니다. 신용이 필요한 신용 : Stéphane Chazelas의 <>업데이트 연산자에 대한 언급을 고려하지 않고 신용 카드를 사용했을 것으로 확신하지 않습니다 .

Bourne 쉘에서 업데이트 할 파일 여는 것은 유틸리티가 제한적입니다. 쉘은 파일을 탐색 할 수있는 방법과 새로운 길이를 설정하는 방법을 제공하지 않습니다 (이전 길이보다 짧은 경우). 그러나 그것은 쉽게 해결되었으므로의 표준 유틸리티가 아니라는 것에 놀랐습니다 /usr/bin.

이것은 작동합니다 :

$ grep -n foo T
8:foo
$ (exec 4<>T; grep foo T >&4 && ftruncate 4) && nl T; 
     1  foo

이와 마찬가지로 (Stéphane에게 팁) :

$ { grep foo T && ftruncate; } 1<>T  && nl T; 
     1  foo

(GNU grep을 사용하고 있습니다. 그가 답변을 작성한 이후에 변경된 것이있을 수 있습니다.)

단, / usr / bin / ftruncate 가 없습니다 . 수십 줄의 C에 대해서는 아래를 참조하십시오. 이 ftruncate 유틸리티는 임의의 파일 설명자를 임의의 길이로 자르며 기본값은 표준 출력과 현재 위치입니다.

위의 명령 (제 1 예)

  • T업데이트 를 위해 파일 설명자 4를 엽니 다 . open (2)과 마찬가지로이 방법으로 파일을 열면 현재 오프셋이 0에 배치됩니다.
  • 그런 다음 grep이T 정상적으로 처리 되고 셸은 출력을 T설명자 4 를 통해 리디렉션합니다 .
  • ftruncate 는 설명자 4에서 ftruncate (2)를 호출하여 길이를 현재 오프셋의 값 (정확히 grep이 남긴 위치)으로 설정합니다.

서브 쉘이 종료되고 설명자 4가 닫힙니다 . 다음은 ftruncate입니다 .

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

int
main( int argc, char *argv[] ) {
  off_t i, fd=1, len=0;
  off_t *addrs[2] = { &fd, &len };

  for( i=0; i < argc-1; i++ ) {
    if( sscanf(argv[i+1], "%lu", addrs[i]) < 1 ) {
      err(EXIT_FAILURE, "could not parse %s as number", argv[i+1]);
    }
  }

  if( argc < 3 && (len = lseek(fd, 0, SEEK_CUR)) == -1 ) {
    err(EXIT_FAILURE, "could not ftell fd %d as number", (int)fd);
  }


  if( 0 != ftruncate((int)fd, len) ) {
    err(EXIT_FAILURE, argc > 1? argv[1] : "stdout");
  }

  return EXIT_SUCCESS;
}

NB, ftruncate (2)는 이런 식으로 사용하면 이식 할 수 없습니다. 절대적인 일반성을 위해 마지막으로 쓴 바이트를 읽고 O_WRONLY 파일을 다시 열고 찾은 다음 바이트를 쓰고 닫습니다.

질문이 5 살이라는 점을 감안할 때이 솔루션은 명백하지 않습니다. 이는 활용 간부 새로운 디스크립터 및 개방하는 <>비전있는 둘 연산자. 파일 디스크립터로 inode를 조작하는 표준 유틸리티를 생각할 수 없습니다. (구문은 일 수 ftruncate >&4있지만 개선이 확실하지 않습니다.) 그것은 camh의 유능하고 탐구적인 답변보다 상당히 짧습니다. 내가 Perl을 더 좋아하지 않는다면 Stéphane 's, IMO보다 조금 더 명확합니다. 누군가가 유용하다고 생각합니다.

동일한 작업을 수행하는 다른 방법은 현재 오프셋을보고하는 lseek (2)의 실행 버전입니다. 출력은 일부 Linuxi가 제공하는 / usr / bin / truncate에 사용될 수 있습니다 .


5

ed 적절한 위치에서 파일을 편집 할 수있는 올바른 선택 일 수 있습니다.

ed my_big_file << END_OF_ED_COMMANDS
g/foo:/d
w
q 
END_OF_ED_COMMANDS

나는 생각이 마음에 들지만, 다른 ed버전이 다르게 행동 하지 않는다면 man ed... (GNU Ed 1.4)에서 온 것입니다.If invoked with a file argument, then a copy of file is read into the editor's buffer. Changes are made to this copy and not directly to file itself.
Peter.O

@fred, 변경 사항을 저장해도 명명 된 파일에 영향을 미치지 않는다는 것을 의미하는 경우에는 잘못된 것입니다. 변경 사항 을 저장할 때까지 변경 사항이 반영되지 않는다고 말하는 인용문을 해석 합니다. 나는 그것이 인정 않는 ed파일을 버퍼로 읽어되기 때문에 35기가바이트 파일을 편집하기위한 GOOL 솔루션이 아닙니다.
glenn jackman

2
나는 그것이 전체 파일이 버퍼에로드 될 것을 의미한다고 생각하고 있었다 . 그러나 아마도 그 파일이 버퍼에로드되어있을 것이다. 나는 한동안 ed에 대해 궁금했다. 나는 그것을 생각했다. 현장 편집을 할 수 있습니다 ... 파일 을 시도해야 합니다 ... 그것이 작동하면 합리적인 해결책이지만, 글을 쓸 때 이것이 sed에서 영감을 얻은 것으로 생각하기 시작합니다 ( 대용량 데이터 청크 작업에서 해방 ... 사실 (로 시작 스크립트에서 스트리밍 입력을받을 수있는 '에드'것으로 나타났습니다 !), 그래서 그것의 소매를 몇 가지 더 재미있는 트릭을 할 수 있습니다.
Peter.O

ed파일 의 쓰기 작업 이 파일을 자르고 다시 작성한다고 확신합니다. 따라서 OP가 원하는대로 디스크의 데이터를 제자리에서 변경하지 않습니다. 또한 파일이 너무 커서 메모리에로드 할 수없는 경우 작동하지 않습니다.
Nick Matteo

5

당신은 (에 현장을 덮어 쓰기합니다), 다음 파일을 엽니 다 읽기 / 쓰기 파일 설명 떠들썩한 파티를 사용 sed하고 truncate...하지만 물론, 지금까지 데이터가 지금까지 읽을의 변경 사항이 금액보다 큰 것을 허용하지 않습니다 .

다음은 스크립트입니다 (bash 변수 $ BASHPID 사용).

# Create a test file
  echo "going abc"  >junk
  echo "going def" >>junk
  echo "# ORIGINAL file";cat junk |tee >( wc=($(wc)); echo "# ${wc[0]} lines, ${wc[2]} bytes" ;echo )
#
# Assign file to fd 3, and open it r/w
  exec 3<> junk  
#
# Choose a unique filename to hold the new file size  and the pid 
# of the semi-asynchrounous process to which 'tee' streams the new file..  
  [[ ! -d "/tmp/$USER" ]] && mkdir "/tmp/$USER" 
  f_pid_size="/tmp/$USER/pid_size.$(date '+%N')" # %N is a GNU extension: nanoseconds
  [[ -f "$f_pid_size" ]] && { echo "ERROR: Work file already exists: '$f_pid_size'" ;exit 1 ; }
#
# run 'sed' output to 'tee' ... 
#  to modify the file in-situ, and to count the bytes  
  <junk sed -e "s/going //" |tee >(echo -n "$BASHPID " >"$f_pid_size" ;wc -c >>"$f_pid_size") >&3
#
#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
# The byte-counting process is not a child-process, 
# so 'wait' doesn't work... but wait we must...  
  pid_size=($(cat "$f_pid_size")) ;pid=${pid_size[0]}  
  # $f_pid_size may initially contain only the pid... 
  # get the size when pid termination is assured
  while [[ "$pid" != "" ]] ; do
    if ! kill -0 "$pid" 2>/dev/null; then
       pid=""  # pid has terminated. get the byte count
       pid_size=($(cat "$f_pid_size")) ;size=${pid_size[1]}
    fi
  done
  rm "$f_pid_size"
#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
#
  exec 3>&- # close fd 3.
  newsize=$(cat newsize)
  echo "# MODIFIED file (before truncating)";cat junk |tee >( wc=($(wc)); echo "# ${wc[0]} lines, ${wc[2]} bytes" ;echo )  cat junk
#
 truncate -s $newsize junk
 echo "# NEW (truncated) file";cat junk |tee >( wc=($(wc)); echo "# ${wc[0]} lines, ${wc[2]} bytes" ;echo )  cat junk
#
exit

테스트 출력은 다음과 같습니다

# ORIGINAL file
going abc
going def
# 2 lines, 20 bytes

# MODIFIED file (before truncating)
abc
def
c
going def
# 4 lines, 20 bytes

# NEW (truncated) file
abc
def
# 2 lines, 8 bytes

3

파일을 메모리 매핑하고 char * 포인터를 사용하여 네이 키드 메모리를 사용하여 모든 작업을 수행 한 다음 파일을 매핑 해제하고 자릅니다.


3
+1이지만 64 비트 CPU 및 OS의 광범위한 가용성으로 인해 35GB 파일로이를 수행 할 수 있습니다. 아직도 32 비트 시스템을 사용하는 사람들은 (이 사이트의 청중 대부분은이 솔루션을 사용할 수 없다고 생각합니다.)
워렌 영

2

현장에서 정확하게 아니지만 유사한 상황에서 사용될 수 있습니다.
디스크 공간이 문제인 경우 파일을 먼저 압축 한 다음 (텍스트이므로 크게 줄어 듭니다) 압축 해제 / 압축 파이프 라인 중간에 일반적인 방식으로 sed (또는 grep 등)를 사용하십시오.

# Reduce size from ~35Gb to ~6Gb
$ gzip MyFile

# Edit file, creating another ~6Gb file
$ gzip -dc <MyFile.gz | sed -e '/foo/d' | gzip -c >MyEditedFile.gz

2
그러나 반드시 gzip은 압축 된 버전으로 압축하기 전에 압축 된 버전을 디스크에 기록하므로 다른 옵션과 달리 최소한의 추가 공간이 필요합니다. 그러나 만약 당신이 공간을 가지고 있다면 더 안전합니다. (...)
nealmcb

이것은 두 가지가 아닌 하나의 압축 만 수행하도록 더욱 최적화 할 수있는 영리한 솔루션입니다.sed -e '/foo/d' MyFile | gzip -c >MyEditedFile.gz && gzip -dc MyEditedFile.gz >MyFile
Todd Owen

0

이 질문에 인터넷 검색 사람의 이익을 위해, 정답은 중단 이 패턴의 어떤 변화를 사용하는 대신 무시할 성능 향상을위한 파일을 손상 위험이 모호한 쉘 기능을 찾고, 그리고 :

grep "foo" file > file.new && mv file.new file

매우 드문 경우이지만 이것이 불가능한 이유 일 경우 에만 이 페이지의 다른 답변을 진지하게 고려해야합니다 (확실히 읽는 것이 흥미롭지 만). 두 번째 파일을 만들 디스크 공간이 없다는 OP의 수수께끼가 바로 그런 상황이라는 것을 인정합니다. 그럼에도 불구하고 @Ed Randall 및 @Basile Starynkevitch와 같은 다른 옵션도 있습니다.


1
이해할 수는 없지만 OP 원본이 요청한 내용과 관련이 없습니다. 임시 파일을위한 충분한 디스크 공간이 없어도 큰 파일을 인라인 편집 할 수 있습니다.
Kiwy

@Kiwy이 질문의 다른 시청자 (현재까지 15,000 명 가량)를 겨냥한 답변입니다. "제자리에서 파일을 수정하는 방법이 있습니까?" OP의 특정 사용 사례보다 더 넓은 관련성이 있습니다.
Todd Owen

-3

echo -e "$(grep pattern bigfile)" >bigfile


3
파일이 크고 grepped데이터가 명령 줄이 허용하는 길이를 초과하는 경우에는 작동하지 않습니다 . 그런 다음 데이터가
손상됨
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.