![엣지 케이스 - Perl의 STDIN 입력 감지](https://linux55.com/image/194172/%EC%97%A3%EC%A7%80%20%EC%BC%80%EC%9D%B4%EC%8A%A4%20-%20Perl%EC%9D%98%20STDIN%20%EC%9E%85%EB%A0%A5%20%EA%B0%90%EC%A7%80.png)
이런 질문을 어떻게 해야 할지 잘 모르겠고, 여기가 그런 질문을 할 만한 곳인지도 모르겠습니다. 이것은 매우 복잡해 보이며 무슨 일이 일어나고 있는지 완전히 이해하지 못합니다. 솔직히 말해서, 이것이 바로 제가 이 문제를 해결하는 데 도움을 받기 위해 글을 올리는 이유입니다. 나의 궁극적인 목표는 배우는 것이지 전반적인 문제를 해결하는 것이 아닙니다. 제가 설명하려는 상황이 발생했을 때, 그리고 왜 그런 일이 발생했는지 이해하고 싶습니다.
제가 개발해온 Perl 모듈이 있습니다. 이것이 수행하는 작업 중 하나는 표준 입력(파이프 또는 리디렉션(예: <
) 을 통해)에 입력이 있는지 감지하는 것입니다 .
리디렉션을 포착하기 위해 상황에 따라 몇 가지 다른 검사를 사용합니다. 그 중 하나는 출력에서 파일 설명자를 찾는 것입니다 0r
. lsof
꽤 잘 작동합니다. 많은 스크립트에서 문제 없이 모듈을 사용하지만 스크립트가 STDIN에서 입력을 받고 있다고 생각하지만 그렇지 않은 사용 사례가 1개 있습니다. 이는 내가 lsof 출력에 넣은 것과 관련이 있습니다. . 다음은 제가 사건의 범위를 좁힌 조건입니다. 그러나 이것이 요구 사항의 전부는 아닙니다. 뭔가 빠졌습니다. 어쨌든 이러한 조건이 필요한 것 같지만, 장난감 예제에서 구현하는 방법을 잘 모르기 때문에 제 직관을 이해해 주시기 바랍니다. 시도해 봤습니다. 그래서 제가 빠졌다는 것을 압니다. 무엇:
- 백틱을 통해 Perl 스크립트 내에서 Perl 스크립트를 실행할 때(내부 스크립트는 의도적으로 STDIN에 대한 입력을 제공했다고 생각하지만 실제로는 그렇지 않습니다. 지적해야 하지만 부모인지는 알 수 없습니다) 실제로 해당 핸들 수준 또는 하위 수준을 연 경우)
- 입력 파일은 하위 디렉터리에 있는 내부 스크립트 호출에 제공됩니다.
0r
lsof가 보고하는 파일 설명자가 있는 파일은 다음과 같습니다.
/Library/Perl/5.18/AppendToPath
다른 조건에서는 이 파일이 lsof 출력에 표시되지 않습니다. 호출 eof(STDIN)
전후에 실행 하면 lsof
결과는 매번 1입니다. -t STDIN
정의되지 않았습니다. fileno(STDIN)
예 0
.
나는 이 문서를 읽었습니다.여기내가 그것을 잡으면 그것은 다음과 같습니다:
>cat /Library/Perl/5.18/AppendToPath
/System/Library/Perl/Extras/5.18
이는 Perl 경로에 추가되도록 설계된 macOS-perl 전용 파일인 것으로 보이지만 @INC
다른 운영 체제에서도 유사한 메커니즘을 제공하는지 여부는 알 수 없습니다.
이 파일이 언제 존재하거나 열리는지, 언제 닫히는지 자세히 알고 싶습니다. 끌 수 있나요? 인터프리터가 파일 내용을 읽은 것 같습니다. 그런데 왜 내 스크립트에 열린 파일 핸들로 걸려 있습니까? 왜 STDIN에 있나요? 이 경우 실제로 파일을 직접 리디렉션하면 어떻게 되나요? 어떤 경우에는 내가 모르는 사이에 하위 프로세스가 상위 프로세스로부터 이를 상속받습니까?
고쳐 쓰다: 첨자 실행 중에 STDIN에서 AppendToPath 파일 핸들을 열어 두는 데 필요한 세 번째(아마도 최종) 요구 사항을 발견했습니다. STDIN을 끄는 상위 스크립트 상단에 코드 줄이 있는 것으로 밝혀졌습니다(아마도 비슷한 문제를 해결하기 위해 추가되었을 것입니다. 당시에는 STDIN에 대한 입력 감지에 대해 지금보다 훨씬 덜 알고 있었습니다). 나는 주석을 닫았고 이상한 파일을 제외하지 않고 모든 것이 작동하기 시작했습니다(즉, /Library/Perl/5.18/AppendToPath
lsof의 STDIN에서 더 이상 열린 것으로 표시되지 않습니다). 주석 처리된 코드는 다음과 같습니다.
close(STDIN) if(defined(fileno(STDIN)) && fileno(STDIN) ne '' &&
fileno(STDIN) > -1);
그에 대한 댓글에는 다음과 같은 내용이 있습니다.
#Prevent the passing of active standard in handles to the calls to the script
#being tested by closing STDIN.
그래서 나는 몇 년 전에 이 글을 썼을 때 아마도 stdin 감지에 대해 배우고 있었을 것입니다. 내 모듈은 결국 등을 사용할 수 있지만 -t STDIN
이와 -f STDIN
같은 문제에는 lsof를 사용하도록 전환하여 무슨 일이 일어나고 있는지 더 잘 볼 수 있습니다. 따라서 -t/-f/-p
상위 모듈에서 STDIN을 닫지 않으면 현재 모듈(lsof 또는 lsof를 사용하는 새(/restored?) 단순화 버전 사용)이 예상대로 잘 작동합니다.
그러나 상위 프로세스가 STDIN을 닫을 때 파일이 하위 프로세스의 STDIN에 있는 이유를 여전히 이해하고 싶습니다.
답변1
사용자가 다음과 같이 리디렉션 없이 대화형 셸에서 스크립트를 호출하는 경우:
your-script with args
귀하의 스크립트는 tty 장치가 될 쉘의 표준 입력을 상속하며, 읽기+쓰기 모드로 열릴 가능성이 높습니다.
사용자가 다음과 같이 호출하는 경우:
your-script with args < some-file
fd 0은 읽기 전용 모드로 열립니다 some-file
(모든 유형). 그렇다면 < /dev/pts/0
tty 장치이기도 합니다. fifo 파일이면 stdin은 파이프에서 나오는 것처럼 보입니다. 그렇다면 < /dev/null
다른 장치가 됩니다. 캐릭터 장치 등).
그리고:
your-script with args <> some-file
이는 파일이 읽기+쓰기 모드로 열린다는 점을 제외하면 위와 동일합니다. 이 경우 <> /dev/pts/0
터미널의 대화형 셸에서 리디렉션되지 않은 스크립트를 호출할 때와 정확히 동일합니다.
그리고:
your-script <&-
표준 입력이 닫힙니다.
그리고:
other-cmd | your-script
stdin은 대부분의 쉘에서 파이프이지만( < named-pipe
or 를 수행할 때와 동일 < <(cmd)
) ksh93에서는 소켓 쌍일 수 있습니다.
you-script &
비대화형 쉘 에서 stdin은 /dev/null
.
output=$(your-script)
or output=`your-scrip`
, or 에서 cmd <(your-script)
stdin은 동일하게 유지되지만 stdout은 파이프가 됩니다.
your-script |&
(ksh) 또는 (zsh, bash) 에서는 coproc your-script
stdin과 stdout이 모두 파이프입니다.
스크립트가 다음에서 실행되는 경우:
ssh host your-script
즉, sshd
on 을 사용 host
하면 stdin 및 stdout도 파이프가 됩니다(on 을 사용하면 rsh
읽기+쓰기에서 직접 네트워크 소켓이 됩니다).
cron
OR 작업에 의해 시작된 경우 at
stdin은 파이프일 수 있고 /dev/null
stdout은 파이프일 수 있습니다(출력이 있는 경우 결국 이메일로 전송됩니다).
등.
스크립트에서 이러한 모든 것을 감지하려면 lsof
.
발각:
- stdin이 열려 있는지 여부:
fcntl(STDIN, F_GETFL, 0)
stdin이 열려 있지 않으면 실행이 실패합니다. - 어떤 모드로 열 것인지(r, w, rw):
fcntl()
위 반환 값에 O_RDONLY, O_WRONLY, O_RDWR이 있는지 확인하세요. - 표준 입력(일반, 파이프, 장치)에서 열린 파일 유형:
fstat()
시스템 호출(stat STDIN
perl)을 수행하고 해당 필드에서 유형을 가져옵니다mode
. 또는 Perl의-f
//-d
...를 사용하여-p
가능한 모든 파일 유형을 테스트할 수 있습니다. - 장치 파일의 경우 tty 장치인지 여부에 관계없이
POSIX::isatty(STDIN)
또는 를 사용합니다-t
.
그러나 이것들은 질문에 대답하는 것과 거의 관련이 없습니다.표준 입력에서 읽을 수 있는 방법이 있나요?또는 read()
차단에 실패하거나 EOF를 반환합니다. 이를 위해서는 다음과 같은 것이 필요합니다 poll()
.
귀하의 최종 목표가 무엇인지는 잘 모르겠지만 스크립트에 대화형 모드(사용자가 상호 작용하는 곳)와 자동화 모드가 있고 stdin 및/또는 stdout을 기반으로 둘 사이를 전환하기를 원하는 것 같습니다.
따라서 이것은 stdin 및/또는 stdout이 tty인지(tty 장치를 통해 상호 작용하는 사용자가 있는지 여부) 확인 -t STDIN
하고 확인해야 합니다.-t STDOUT
답변2
백틱을 통해 Perl 스크립트 내에서 Perl 스크립트를 실행할 때(내부 스크립트는 STDIN에 입력이 있다고 잘못 생각함)
내부 스크립트는 input 이 있다고 올바르게 생각합니다 . 다른 열린 파일이 파일 설명자 0(perl에서는 항상 파일 핸들임 )을 STDIN
얻는다는 것입니다 . STDIN
아시다시피, stdin 파일 설명자는 다른 하위 프로세스와 마찬가지로 Perl qx{...}
이나 Perl에서 실행되는 프로그램을 통해 외부 스크립트에서 상속됩니다.`...`
내부 스크립트는 원본을 상속하기 때문에파일 설명자0, Perl STDIN이 아님파일 핸들, 이는 내부 스크립트 또는 외부 스크립트가 다른 스크립트에 대해 아무것도 남지 않을 때까지 필요한 입력을 더 많이 읽을 수 있기 때문에 버퍼링 문제를 발생시킵니다. 다음 예를 고려하십시오.
$ echo text | perl -e '$junk=`perl -e "eof(STDIN)"`; print while <>'
$ # nothing!
"EOF 테스트"만으로 내부 스크립트는 외부 스크립트에 대한 입력을 남기지 않습니다.
그러나 스크립트 내에서 버퍼링되지 않은 읽기를 수행하면 sysread
예상대로 작동합니다.
$ cat inner.pl
sysread STDIN, $d, 2
$ echo text | perl -e '$junk = `perl inner.pl`; print while <>'
xt
[다른 답변에서]
다음을 사용하면your-script <&-
stdin이 닫힙니다.
stdin과 같은 파일 설명자를 닫는 것은 결코 좋은 생각이 아닙니다. (데몬은 파일 설명자를 에서 리디렉션하지만 /dev/null
절대로폐쇄) 그러나 Perl이나 Python과 같은 언어로 작성된 스크립트를 실행할 때 특히 나쁩니다. 이로 인해 stdin이 종료될 수 있기 때문입니다.열려 있는(그리고 스크립트 참조) 대신폐쇄:
$ cat script.pl
seek STDIN, 0, 0;
print while <STDIN>;
$ perl script.pl <&-
seek STDIN, 0, 0;
print while <STDIN>;
open(2)
이는 첫 번째 무료 파일 설명자를 반환 하거나 같은 시스템 호출이 발생하기 때문에 발생합니다 socket(2)
. stdin이 닫히면 반환된 fd가 stdin이 됩니다.
답변3
지금까지의 답변은 질문에 대한 답변이지만, 지금까지 자식 Perl 프로세스가 파일 설명자 0(STDIN)에서 읽기 위해 AppendToPath라는 파일을 여는 시기와 이유에 대한 구체적인 질문은 직접 해결되지 않았습니다. 아래 @zevzek의 의견은 무슨 일이 일어나고 있는지 알려줍니다. 작동 방식을 설명하고 STDIN이 표준 입력 이외의 파일 핸들이 되는 방식을 설명하는 메커니즘을 제공하므로 선택한 답변을 유지하겠습니다. 그러나 AppendToPath
가능한 한 파일의 컨텍스트에 넣겠습니다. 관련 재현 가능한(macOS에서 시스템 Perl 사용) 예제가 있습니다.
파일 설명자가 어떻게 "생성"되었는지 알 수 있는 방법은 없지만 시스템은 이에 대한 기록을 유지하지 않습니다. 디버깅하는 경우 strace -f ./your_script 처럼 프로그램을 추적하는 것이 많은 도움이 됩니다.
위의 인용문에 따르면 부모 또는 자식 프로세스에 의해 열렸는지 알 수 없지만 AppendToPath
이것이 초기에 필요한 AppendToPath
perl을 업데이트하는 데 사용되는 파일이라는 @INC
점을 고려하면 자식 프로세스의 perl에 의해 열렸을 가능성이 높습니다. 인터프리터 준비 중 제공된 스크립트를 실행합니다.
다음은 부모가 STDIN을 닫고 자식의 STDIN(fd 0)이 로 판명되는 장난감 예제입니다 AppendToPath
.
bash-3.2$ perl -e 'close(STDIN); \
$c=q{perl -e } . \
chr(39) . \
print(fileno(STDIN),"\n"); \
q{print qx{lsof -w -b -p $$}} . \
chr(39); \
print `$c`'
0
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
perl5.18 8901 robleach cwd DIR 1,8 832 35897246 /Users/robleach/GoogleDrive/WORK/RPST
perl5.18 8901 robleach txt REG 1,8 37552 1152921500311880916 /usr/bin/perl5.18
perl5.18 8901 robleach txt REG 1,8 1305808 1152921500312070866 /System/Library/Perl/5.18/darwin-thread-multi-2level/CORE/libperl.dylib
perl5.18 8901 robleach txt REG 1,8 1568368 1152921500312405021 /usr/lib/dyld
perl5.18 8901 robleach 0r REG 1,8 33 90514450 /Library/Perl/5.18/AppendToPath
perl5.18 8901 robleach 1 PIPE 0xd289f4ba11f1bbb8 16384 ->0x4d76dba4a1ac82fd
perl5.18 8901 robleach 2u CHR 16,0 0t13390 723 /dev/ttys000
perl5.18 8901 robleach 3 PIPE 0x9f2f7b3ec7eb66ba 16384 ->0xc303b3e01efc707c
이 예제는 STDIN을 닫지 않으며 fd 0이 tty(즉, STDIN)임을 보여줍니다.
bash-3.2$ perl -e '$c=q{perl -e } . \
chr(39) . \
print(fileno(STDIN),"\n"); \
q{print qx{lsof -w -b -p $$}} . \
chr(39); \
print `$c`'
0
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
perl5.18 8904 robleach cwd DIR 1,8 832 35897246 /Users/robleach/GoogleDrive/WORK/RPST
perl5.18 8904 robleach txt REG 1,8 37552 1152921500311880916 /usr/bin/perl5.18
perl5.18 8904 robleach txt REG 1,8 1305808 1152921500312070866 /System/Library/Perl/5.18/darwin-thread-multi-2level/CORE/libperl.dylib
perl5.18 8904 robleach txt REG 1,8 1568368 1152921500312405021 /usr/lib/dyld
perl5.18 8904 robleach 0u CHR 16,0 0t14630 723 /dev/ttys000
perl5.18 8904 robleach 1 PIPE 0xd289f4ba11f1bbb8 16384 ->0x4d76dba4a1ac82fd
perl5.18 8904 robleach 2u CHR 16,0 0t14630 723 /dev/ttys000
perl5.18 8904 robleach 3 PIPE 0x9f2f7b3ec7eb66ba 16384 ->0xc303b3e01efc707c
STDIN이 꺼지지 않으면 파일이 /Library/Perl/5.18/AppendToPath
lsof 출력에 포함되지 않습니다. fileno(STDIN)
자식에서 쿼리할 때 STDIN 파일 설명자가 정의된다는 점도 주목할 가치가 있습니다 .
다음은 @zenzek(인용문 편집)을 제가 직접 해석한 것입니다.
open(2) 맨페이지 인용: "성공적인 호출에 의해 반환된 파일 설명자는 현재 프로세스에 대해 열려 있지 않은 가장 낮은 번호의 파일 설명자가 됩니다." STDIN(fd 0)을 닫은 후 다음 열기 호출은 해당 파일 설명자(0)를 가져오고
-t STDIN
하위 프로세스의 내용은 새로 열린 파일이 됩니다. 예는 다음과 같습니다.perl -le 'close(STDIN); \ open PW, "/etc/passwd"; \ print fileno(PW); \ print `perl -e "print fileno(STDIN),qq(\n)";lsof -w -b -p $$`' 0 0 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME perl5.18 13320 robleach cwd DIR 1,8 832 35897246 /Users/robleach/GoogleDrive/WORK/RPST perl5.18 13320 robleach txt REG 1,8 37552 1152921500311880916 /usr/bin/perl5.18 perl5.18 13320 robleach txt REG 1,8 1305808 1152921500312070866 /System/Library/Perl/5.18/darwin-thread-multi-2level/CORE/libperl.dylib perl5.18 13320 robleach txt REG 1,8 1568368 1152921500312405021 /usr/lib/dyld perl5.18 13320 robleach 0r REG 1,8 6946 90523670 /private/etc/passwd perl5.18 13320 robleach 1u CHR 16,0 0t23169 723 /dev/ttys000 perl5.18 13320 robleach 2u CHR 16,0 0t23169 723 /dev/ttys000 perl5.18 13320 robleach 3 PIPE 0x5898fd9b21b123d7 16384 ->0xc4c5f2aecc8eaaf7
더 많은 관련 인용문:
파일 설명자 0은 표준 입력입니다. 이는 stdio의 stdin 스트림 객체나 Perl의 STDIN 파일 핸들 객체가 아닐 수 있습니다(둘 다 실제 파일이나 파일 설명자를 전혀 참조하지 않을 수 있는 상위 수준 래퍼입니다). 그러나 이는 항상 더 높은 수준의 래퍼가 아닌 exec를 통해 상속된 파일 설명자입니다. 이것은 역따옴표를 통해 lsof나 다른 프로그램을 실행할 때 발생하는 일입니다(표준 fd가 있어서는 안 되는 cloexec로 fd에 표시되지 않은 경우). 모든 파일 설명자(0 포함)는 열릴 때 항상 상속되지만 닫힐 때는 상속되지 않습니다. fd 0이 닫힌 경우 fd를 반환하는 모든 함수(open(), accept(), 소켓(), epoll_create() 등)는 성공하면 0을 반환합니다. 왜냐하면 0은 현재 사용되지 않는 가장 낮은 숫자의 fd이기 때문입니다.
STDIN을 끄면 지정된 fd 0의 상위에서 열린 임의의 파일을 재생할 수 있지만 fd 0의 하위에서 열린 임의의 파일은 재현할 수 없습니다(AppendToPath 열기가 하위 스크립트에서도 발생하는지 의심스럽기 때문입니다). 나할 수 있는위의 파일을 재생하면 AppendToPath
fd 0이 제공됩니다. 부모가 열었는지, 아니면 자식이 열었는지 명확하게 알 수 없습니다. 그러나 나는 하위 프로세스에서 Perl 인터프리터에 의해 열린다는 것이 합리적인 추측이라고 믿습니다.