리디렉션을 사용하는 방법을 배우고 있습니다. 일반적인 작업은 다음과 같습니다.
command > file 2>&1
APUE 3.10과 3.12를 참고하면 핵심 시스템 호출 순서는 다음과 같다고 생각합니다.
open(file) == 3
dup2(3,1)
dup2(1,2)
내 아이디어를 테스트하기 위해 쉘 스크립트를 생성하고 strace 명령을 사용하여 실행했습니다. 내 test.sh는 다음과 같습니다.
#!/bin/sh
echo 'hello'
whatfuckis # For get a stderr
그런 다음 strace 명령을 사용하여 시스템 호출과 신호를 추적합니다.
strace -y -o trace.log ./test.sh > tmp.txt 2>&1
tmp.txt는 예상대로 오류 메시지를 받습니다. Trace.log에서 읽기, 쓰기, 닫기, fcntl.. 등과 같은 많은 시스템 호출을 볼 수 있으며 파일 설명자 2(stderr)가 tmp.txt를 가리키는 결과도 볼 수 있습니다.
문제는 dup2와 같은 파일 설명자 복사에 대한 특정 작업이 표시되지 않는다는 것입니다. 이는 이상합니다. 파일 설명자 중복을 추적하는 방법은 무엇입니까? Trace.log의 주요 정보는 다음과 같습니다.
read(10</home/madhouse/Applications/Lab/test.sh>, "#!/bin/sh\n\necho 'hello'\nwhatfuck"..., 8192) = 54
write(1</home/madhouse/Applications/Lab/tmp.txt>, "hello\n", 6) = 6
stat("/home/MATLAB/R2016b/bin/whatfuckis", 0x7ffc61f2a380) = -1 ENOENT (No such file or directory)
stat("/home/madhouse/Qt5.14.2/5.14.2/gcc_64//whatfuckis", 0x7ffc61f2a380) = -1 ENOENT (No such file or directory)
stat("/home/madhouse/Qt5.14.2/Tools/QtCreator/bin/whatfuckis", 0x7ffc61f2a380) = -1 ENOENT (No such file or directory)
stat("/usr/local/lib/nodejs/node-v12.22.5-linux-x64/bin/whatfuckis", 0x7ffc61f2a380) = -1 ENOENT (No such file or directory)
stat("/home/madhouse/.local/bin/whatfuckis", 0x7ffc61f2a380) = -1 ENOENT (No such file or directory)
stat("/usr/local/sbin/whatfuckis", 0x7ffc61f2a380) = -1 ENOENT (No such file or directory)
stat("/usr/local/bin/whatfuckis", 0x7ffc61f2a380) = -1 ENOENT (No such file or directory)
stat("/usr/sbin/whatfuckis", 0x7ffc61f2a380) = -1 ENOENT (No such file or directory)
stat("/usr/bin/whatfuckis", 0x7ffc61f2a380) = -1 ENOENT (No such file or directory)
stat("/sbin/whatfuckis", 0x7ffc61f2a380) = -1 ENOENT (No such file or directory)
stat("/bin/whatfuckis", 0x7ffc61f2a380) = -1 ENOENT (No such file or directory)
stat("/usr/games/whatfuckis", 0x7ffc61f2a380) = -1 ENOENT (No such file or directory)
stat("/usr/local/games/whatfuckis", 0x7ffc61f2a380) = -1 ENOENT (No such file or directory)
stat("/snap/bin/whatfuckis", 0x7ffc61f2a380) = -1 ENOENT (No such file or directory)
write(2</home/madhouse/Applications/Lab/tmp.txt>, "./test.sh: 4: ", 14) = 14
write(2</home/madhouse/Applications/Lab/tmp.txt>, "whatfuckis: not found", 21) = 21
write(2</home/madhouse/Applications/Lab/tmp.txt>, "\n", 1) = 1
read(10</home/madhouse/Applications/Lab/test.sh>, "", 8192) = 0
exit_group(127) = ?
+++ exited with 127 +++
아비로의 로그
167936 execve("/usr/bin/sh", ["sh", "-c", "./test.sh > tmp.txt 2>&1"], 0x7ffc000ac898 /* 76 vars */) = 0
167936 dup2(3</home/madhouse/Applications/Lab/tmp.txt>, 1) = 1</home/madhouse/Applications/Lab/tmp.txt>
167936 close(3</home/madhouse/Applications/Lab/tmp.txt>) = 0
167936 dup2(1</home/madhouse/Applications/Lab/tmp.txt>, 2) = 2</home/madhouse/Applications/Lab/tmp.txt>
167936 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f67e121d850) = 167937
167937 execve("./test.sh", ["./test.sh"], 0x55855ac8c2d8 /* 76 vars */) = 0
167937 write(1</home/madhouse/Applications/Lab/tmp.txt>, "hello\n", 6) = 6
167937 write(2</home/madhouse/Applications/Lab/tmp.txt>, "./test.sh: 4: ", 14) = 14
167937 write(2</home/madhouse/Applications/Lab/tmp.txt>, "whatfuckis: not found", 21) = 21
167937 write(2</home/madhouse/Applications/Lab/tmp.txt>, "\n", 1) = 1
167936 dup2(10</dev/pts/2>, 1</home/madhouse/Applications/Lab/tmp.txt>) = 1</dev/pts/2>
167936 dup2(11</dev/pts/2>, 2</home/madhouse/Applications/Lab/tmp.txt>) = 2</dev/pts/2>
답변1
두 가지 질문이 있습니다.
- 명령에서 리디렉션을 수행 중이므로
strace
대화형 셸에서 리디렉션이 수행되고strace
관련 시스템 호출은 표시되지 않습니다. - 시스템
dup2
호출은 명령이 실행되기 전에 하위 프로세스 내에서 실행됩니다. 기본적으로strace
명령의 하위 항목은 따르지 않으므로 하위 항목 자체의 흔적은 볼 수 없습니다. 태그 가 하위 항목도 따라다니도록 하려면-f
마크업에 태그를 추가 해야 합니다 .strace
가장 중요한 것은 예상된 검사를 수행하려면 다음을 실행해야 한다는 것입니다.
strace -yf -o trace.log sh -c './test.sh > tmp.txt 2>&1'
그러면 예상한 순서가 표시됩니다.
$ grep -E 'dup2\(|clone\(|execve\(|(open|write|close)\(.*tmp.txt' trace.log
31769 execve("/usr/bin/sh", ["sh", "-c", "./test.sh > tmp.txt 2>&1"], [/* 124 vars */]) = 0
31769 clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7ffff7fd19d0) = 31770
31770 open("tmp.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3</tmp/tmp.txt>
31770 dup2(3</tmp/tmp.txt>, 1</dev/pts/445>) = 1</tmp/tmp.txt>
31770 close(3</tmp/tmp.txt>) = 0
31770 dup2(1</tmp/tmp.txt>, 2</dev/pts/445>) = 2</tmp/tmp.txt>
31770 execve("./test.sh", ["./test.sh"], [/* 123 vars */]) = 0
31770 dup2(3</tmp/test.sh>, 255) = 255</tmp/test.sh>
31770 write(1</tmp/tmp.txt>, "hello\n", 6) = 6
31770 clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7ffff7fd19d0) = 31771
31771 write(2</tmp/tmp.txt>, "./test.sh: line 3: whatfuckis: c"..., 49) = 49
및 시스템 호출은 실제 실행 전에 open
명령의 하위 명령 pid dup2
내에서 실행 됩니다.31770
sh -c...
test.sh
자식 대신 부모의 파일 설명자를 복사합니다.
(셸 구현에 따라) 실행이 가능합니다 clone
. 이 경우 부모는 원본을 저장해야 합니다.표준 출력그리고표준 오류 오류율파일 설명자를 삭제하고 하위 프로세스가 종료된 후 복원합니다.
$ grep -E 'dup2\(|clone\(|execve\(|(open|write|close|openat)\(.*tmp.txt|F_DUPFD|exit_group' trace.log
1094 execve("/usr/bin/sh", ["sh", "-c", "./test.sh > tmp.txt 2>&1"], 0x7fffd0324118 /* 20 vars */) = 0
1094 openat(AT_FDCWD, "tmp.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3</tmp/tmp.txt>
1094 fcntl(1</dev/pts/0>, F_DUPFD, 10) = 10</dev/pts/0>
1094 dup2(3</tmp/tmp.txt>, 1) = 1</tmp/tmp.txt>
1094 close(3</tmp/tmp.txt>) = 0
1094 fcntl(2</dev/pts/0>, F_DUPFD, 10) = 11</dev/pts/0>
1094 dup2(1</tmp/tmp.txt>, 2) = 2</tmp/tmp.txt>
1094 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f844aa71690) = 1095
1095 execve("./test.sh", ["./test.sh"], 0x7f844aa9dc08 /* 20 vars */) = 0
1095 fcntl(3</tmp/test.sh>, F_DUPFD, 10) = 10</tmp/test.sh>
1095 write(1</tmp/tmp.txt>, "hello\n", 6) = 6
1095 write(2</tmp/tmp.txt>, "./test.sh: 3: ", 14) = 14
1095 write(2</tmp/tmp.txt>, "whatfuckis: not found", 21) = 21
1095 write(2</tmp/tmp.txt>, "\n", 1) = 1
1095 exit_group(127) = ?
1094 dup2(10</dev/pts/0>, 1</tmp/tmp.txt>) = 1</dev/pts/0>
1094 dup2(11</dev/pts/0>, 2</tmp/tmp.txt>) = 2</dev/pts/0>
1094 exit_group(127)
dup2
각각의 실행을 시작하기 전에 1094
상위 프로세스(pid)가 어떻게 파일 설명자 1과 2를 파일 설명자 10과 11에 각각 복사하는지( fcntl
시스템 호출과 F_DUPFD
명령을 사용하여) 확인하세요 .
그런 다음 하위 프로세스(pid 1095
)가 완료된 후 상위 프로세스는 파일 설명자 10 및 11(원본 파일 설명자)을 복원합니다.표준 출력그리고표준 오류 오류율) 파일 설명자 1과 2로 돌아갑니다.