나는 방금 bash 대화형 쉘이 기본적으로 프롬프트에 쓰고 파일 설명자 2(stderr)에 입력한 내용을 에코한다는 것을 알았습니다. 이는 strace에서 bash를 실행하여 확인할 수 있습니다.
그 이유는 무엇입니까? 왜 이 내용을 표준 출력에 쓰지 않습니까?
답변1
원래 UNIX 버전 5-7도 동일한 작업을 수행했음을 알 수 있습니다. (UFD output = 2;
http://minnie.tuhs.org/cgi-bin/utree.pl?file=V7/usr/src/cmd/sh/main.c)
time
확실히 유용한 stderr로 출력하는 내장 함수입니다. 그러나 time
이는 V5에 내장된 기능 중 하나가 아닙니다.
큰 이유는 없는 것 같아요아니요터미널 출력을 stderr에 씁니다. 쉘의 출력은 명시적인 오류 메시지이거나 대화형 터미널 전용 출력입니다. 표준 출력을 리디렉션하여 대화형 출력을 리디렉션할 필요가 없습니다.
stderr는 V5가 아닌 V6에서 도입되었지만 V5에서는 sh
필요한 경우 이전 FD 2를 닫은 후 수동으로 stdout을 FD 2로 보낼 수 있습니다. 실패한 유사한 명령을 실행하려고 할 dup()
때 오류 메시지를 인쇄해야 할 필요성을 발견한 것 같습니다 .exec()
foo > output
이제 방법을 알아보세요.포켓역사적인 UNIX 코드는 다음과 같습니다. 물리적 RAM이 반드시 많을 필요는 없기 때문에 의도적으로 짧게 유지되었습니다.
prs()
문자열을 출력하는 함수가 있습니다 . FD 매개변수를 사용하지 않습니다. FD 2에 오류 메시지를 인쇄하고, 다른 문자열도 FD 2에 인쇄할 수 있으므로 무조건 FD 2에만 인쇄합니다. 몇 가지 명령어로 컴파일된 짧은 코드이므로 최소한의 RAM을 사용합니다.
잠시 무슨 일이 생기면,변화그들은 항상 자신이 개선하는 것보다 더 많은 것을 깨뜨릴 위험이 있습니다.
나를 당혹스럽게 만드는 것은 Python 개발자들이 이것을 발견하고 복사한 이유입니다. 제 생각에는 다소 모호한 사실입니다. 어쩌면 이것은 내가 아직 찾지 못한 또 다른 이유를 의미할 수도 있습니다.
err(s)
char *s;
{
prs(s);
prs("\n");
if(promp == 0) {
seek(0, 0, 2);
exit();
}
}
prs(as)
char *as;
{
register char *s;
s = as;
while(*s)
putc(*s++);
}
putc(c)
{
write(2, &c, 1);
}
답변2
쉘은 -c
스크립트를 해석하지 않고(아마도 inline ) stdin이 터미널일 때 대화형입니다(그러나 아래 POSIX 쉘 참조).
이 경우, 동일한 터미널에 프롬프트(그리고 echo
쉘이 자체 줄 편집기를 갖도록 입력한 내용)가 나타나기를 원합니다. 문제는 표준 입력이 읽기+쓰기 모드에서 열리도록 보장되지 않으므로 표준 입력에 대한 프롬프트 출력이 신뢰할 수 없다는 것입니다. 또한 사람들은 대화형 세션 중간에 이 작업을 수행하려고 할 수 있으며, exec < other-file
이로 인해 프롬프트 표시도 중단될 수 있습니다.
zsh
사용자 상호 작용을 위해 어떤 터미널(사용됨)을 결정 ttyname()
하고 읽기+쓰기(10 이상의 전용 독립 실행형 fd에서)를 위해 다시 여는 것이 더 현명하고 더 나은 방법 입니다. 열린 파일 설명자 /dev/tty
(대부분의 경우 stdin이 터미널인 경우 stdin에 있는 것과 동일한 터미널을 참조하는 제어 터미널)를 사용하는 것이 의미가 있지만 어떤 쉘도 이를 수행하지 않는 것 같습니다. 콘솔의 복구 셸과 같이 일부 경우에 발생할 수 있는 제어 터미널)
하지만, 그POSIX 사양에는 쉘이 대화형이고 stderr도 위의 요구 사항이 충족되는 경우에만 터미널이라고 명시되어 있습니다., 그리고POSIX에서는 stderr에 프롬프트를 작성해야 합니다..
bash
POSIX와 호환되도록 설계되었으므로 이러한 요구 사항을 따라야 합니다.
나는 이것이 역사적인 이유라고 생각한다. POSIX 사양은 이것이 동작하는 방식과 그 이전의 Bourne 쉘을 sh
기반으로 합니다 . ( Bourne 쉘이 처음 출시되었을 때 Unix V7에는 이미 존재했지만).ksh88
ksh88
ttyname()
rm
터미널 애플리케이션에 대한 사용자 상호 작용(예: mv
/ /... 프롬프트 find -ok
)은 일반적으로 stdin+stderr에서 이루어집니다. stderr은 쓰기 모드로 열리고 터미널 세션에서 터미널을 가리킵니다. stdout은 명령의 일반 출력에 사용되며 리디렉션을 사용하여 일반 출력을 저장하거나 사후 처리할 수 있도록 사용자 상호 작용 메시지와 별도로 유지하는 것이 중요합니다. 열려 있는 내부 fd 대신 stderr(미리 알려진 특정 fd)에 배치하면 /dev/tty
이러한 메시지를 더 쉽게 제거하거나(예: 명령 실행 2> /dev/null
) 명령을 비대화식으로 사용할 수 있습니다.
그러나 쉘의 경우에는 특별히 유용하지 않습니다. 실행하면 sh 2> /dev/null
셸이 비대화형이 됩니다(즉, 프롬프트가 표시되지 않지만 다른 많은 부작용이 발생함을 의미). 즉, 비활성화 프롬프트를 사용할 수 있지만 exec 2> /dev/null
를 사용하여 모든 명령을 실행하지 않는 한 모든 명령 오류를 삭제하는 부작용이 있습니다 cmd 2> something
. PS1, PS2, PS3, PS4를 클리어하는 것이 훨씬 나을 것 같습니다.
이를 통해 사용자 입력이 한 터미널에서 나와 다른 터미널로 출력될 수 있지만 왜 누군가가 이것을 원하는지 이해할 수 없습니다.
한 가지 가능한 이유는 이것이 더 완벽할 것이라는 점입니다.
/dev/tty
위와 같이 제어단자가 없는 극단적인 경우에는 동작하지 않을 수 있으며,ttyname()
/dev
s가 올바르게 채워지지 않거나 사용되지 않으면 작동하지 않을 수 있습니다chroot
.
일부 다른 쉘에서는 상황이 더 나쁩니다. csh
, tcsh
, rc
, es
, akanga
, fish
는 프롬프트와 stdout에 입력한 내용의 에코를 표시합니다(단, fish
stdout이 터미널이 아니고 csh
라인 편집기가 아무것도 출력하지 않는 경우 프롬프트는 표시되지 않습니다 echo
(터미널 장치에서 생성된 tty 라인에 유의하세요). 결정하다)).
답변3
이전 답변을 읽는 방법은 다음과 같습니다. bash는 이전 버전, 주로 Bourne 및 Korn 쉘의 동작을 모방하기 때문입니다. "늘 그래왔어." 모두가 현 상태를 받아들였고, 누구도 현 상태에 의문을 제기하지 않았습니다. 죄송하지만 이건 형편없습니다.
IMO, 진짜 이유 stdout
는리디렉션의 영향을 받음명령줄 도구(셸 스크립트 포함)는 일반적으로 *ix 셸 내에서 작동하므로 파이프는 한 프로세스의 출력을 다른 프로세스의 입력으로 전달하는 데 사용됩니다. 이 경우 프롬프트는 표시되지 않으며 파이프라인 데이터의 일부가 되며 이는 원하는 것이 아닙니다.