내 문제를 다음 코드로 압축했습니다.
#include <stdio.h>
int main(){
char buffer[256];
printf("Enter input: ");
scanf("%s", buffer);
system("/bin/sh");
return 0;
}
사용자 입력으로 프로그램을 실행하면 다음과 같은 결과를 얻습니다.
user@ubuntu:~/testing/temp$ ./main
Enter input: Test
$
마지막 줄은 프로그램이 시작되는 쉘입니다.
그러나 파이프의 입력으로 프로그램을 실행하면 다음과 같습니다.
user@ubuntu:~/testing/temp$ echo 'test' | ./main
Enter input: user@ubuntu:~/testing/temp$
프로그램이 쉘을 열지 않는 것 같습니다.
약간의 고민 끝에 나는 다음과 같은 사실을 깨달았습니다.
user@ubuntu:~/testing/temp$ (python -c "print 'test'" ; echo 'ls') | ./main
a.out main main.c
Enter input: user@ubuntu:~/testing/temp
ls
열린 쉘에서 명령을 실행할 수 있습니다 .
따라서 두 가지 질문이 있습니다.
- 첫 번째 경우처럼 쉘이 열리지 않는 이유는 무엇입니까?
- 이 문제를 어떻게 처리해야 합니까? 실행할 명령을 결정해야 하는 것은 매우 불편합니다.앞으로프로그램 실행: 나는 실행할 명령을 동적으로 선택할 수 있는 셸을 갖고 싶습니다.
답변1
- 첫 번째 경우처럼 쉘이 열리지 않는 이유는 무엇입니까?
첫 번째 경우 stdin
는 터미널과 셸이 대화형이라는 것입니다. 그것은 당신의 명령 등을 기다립니다.
두 번째 경우 stdin
는 파이프이고 쉘은 비대화형입니다. 프로그램은 첫 번째 줄 stdin
(문자열 test\n
)을 소비한 다음 쉘이 이를 읽으려고 시도하고 stdin
이를 확인합니다 EOF
. 이것이 입력을 받는 프로그램이 EOF
수행해야 하는 작업이기 때문입니다.
세 번째 경우에도 같은 이유로 쉘은 다시 비대화형이 됩니다. 즉, 해당 라인의 첫 번째 라인을 소비한 scanf()
다음 쉘이 이를 읽습니다. 셸이 실행되고 더 많은 명령을 읽으려고 시도하고 확인한 다음 종료됩니다.stdin
test\n
ls
ls
EOF
- 이 문제를 어떻게 처리해야 합니까?
"처리"란 stdin
파이프에 연결된 동안 대화형 셸을 실행하는 것을 의미하는 경우 해결책은 pty(7)
.
답변2
C 프로그램은 (read x; /bin/sh)
.
명령줄에 이 코드 조각을 입력하면 표준 입력이 터미널 키보드에 연결되고 sh
파일 끝 조건이 발생할 때까지 한 줄을 읽은 다음 더 많은 줄을 읽습니다. Ctrl-D를 누르면 파일 종료 조건이 발생합니다).
프로그램에 입력을 파이핑하는 예에서는 입력 크기가 제한되어 있습니다. 입력 크기는 제한되어 있으며 그 이상은 없습니다. 첫 번째 줄(있는 경우) 이후의 줄이 해석되고 sh
종료됩니다.
cat을 사용하여 키보드 입력을 다음으로 전달하여 파이프리스 사례를 시뮬레이션할 수 있습니다 sh
.
(echo test; cat) | (read x; /bin/sh)
아마도:
(echo test; cat) | ./main
그러나 이것은 쉘을 직접 실행하는 것만큼 좋지 않을 수 있습니다. 입력이 터미널이 아니라는 것을 감지하면 줄 편집 기능이 비활성화될 수 있습니다.
답변3
파이프에 연결된 대화형 셸을 실행하려면 대화형으로 만들기만 하면 됩니다.
input | /bin/sh -i
쉘에는 상호작용을 위한 터미널이 필요하지 않습니다. 즉, 대화형 입력이 필요합니다. 일반적으로 대화식으로 실행될 때 쉘의 동작은 터미널과 거의 관련이 없습니다.(적어도 따르면사양)- 일반적으로 오류 처리가 무엇보다 중요한 비대화형 쉘의 동작과 다릅니다. 대화형 쉘은 비대화형 쉘이 종료되도록 하는 오류 조건에서 종료되지 않는 경향이 있습니다. 쉘은기본그러나 입력이 터미널에서 오는 경우에는 대화형 모드입니다.
일부 쉘은 대화형 사용을 위해 터미널 입력이 필요한 것처럼 보이지만 이는 착각입니다. 실제로 이러한 쉘은 예제 사례와 매우 유사하게 작동하는 경우가 많습니다. bash
예를 들어 설정은 readline
터미널 I/O 세부 정보를 대신 처리하고 zsh
ZLE 라인 편집기를 호출하며dash
(빌드타임 옵션 없이 컴파일한 경우작은)BSD 라이브러리 libedit
의 링크 . 이러한 라인 편집기는 터미널 입력을 읽고 이를 라인별 쉘 스크립트와 유사한 것으로 처리한 다음 필요에 따라 쉘이 실행합니다.
하지만 이러한 편집기에는 문제가 없습니다. 프롬프트와 execve 통화로 판단하면 dash
사용자 에게 전화를 걸고 있습니다.작은빌드 시간 옵션(데비안 기본값)이 경우에는 잘 작동합니다. 하지만 이 기능을 통해 얻을 수 있는 유일한 줄 편집 기능은 터미널의 줄 규칙에서 기본적으로 제공하는 기능뿐입니다.(바라보다stty
).
문제는 쉘에 입력이 없다는 것입니다. 다른 곳에서 지적한 것처럼 쉘이 EOF를 감지하면 죽습니다. 당신은 할 수도 있습니다 ...
input | /bin/sh -i -o ignoreeof
하지만 당신은 아마도아니요결과가 마음에 듭니다. dash
일부 쉘처럼 10번 연속 빈 읽기가 종료되지 않습니다. 단지 인쇄만 됩니다...
Type 'exit' to exit the shell
...표준 오류로영원히. 조금은 나았을지도...
cat input - | /bin/sh -i
...파일의 쉘 명령을 input
stdin에서 stdout으로 cat
연결 하고 -
결과를 대화식으로 실행합니다 /bin/sh
. 이것은 작동할 것입니다. 그러나 최소한 작동하는 백스페이스 키를 얻을 수 있도록 stty
회선 규칙을 표준 상태와 같은 것으로 구성하고 적절한 키를 사용하고 싶을 수도 있습니다 . erase
이미 올바르게 설정되었을 수도 있지만 어쨌든 확인해 볼 가치가 있습니다.
다른 방법들...
echo ": some command; exec <$(tty)" | /bin/sh -i
위의 방법은 파이프가 제어 터미널의 현재 전경 작업이기 때문에 작동합니다. 파이프는 현재 터미널 입력을 소유하고 cat
실제로 읽고 있습니다. 대조적으로, python -c
그것을 echo
읽지 마십시오. 명령줄 인수에 의해 생성된 출력만 전달합니다. 따라서 출력이 끝나면 쉘의 입력도 마찬가지입니다. echo
두 번째 예에서 했던 것처럼 쉘이 다른 곳에서 입력을 찾도록 지시하지 않는 한 이것은 맞습니다 .
터미널은 쉘에 입력할 수 있는 다양한 소스 중 하나일 뿐이며 다양한 방법으로 처리할 수 있습니다. 터미널의 세션 리더는 bash
파이프의 터미널 제어가 종료되어 제어권을 다시 얻고 다음 단계를 수행할 수 있을 때까지 기다립니다. 정보는 비동기 신호로 전달됩니다. 이것이 바로 터미널의 용도입니다. 터미널은 읽기/인쇄 프로세스 중에 디스플레이/입력 쌍을 다중화합니다. 그들은 이 일을 훌륭하게 해내고 있습니다.
예를 들어:
$ PS1='bgsh1: ' sh -i +m & PS1='bgsh2: ' sh -i +m &
$ bgsh1: bgsh2:
[2] + Stopped (tty input) sh -i +m
[1] - Stopped (tty input) sh -i +m
$ i=0; while [ "$((i+=1))" -lt 5 ]; do fg "%$(((i%2)+1))"; done
sh -i +m
bgsh2: var=something
bgsh2: kill -TSTP $$
sh -i +m
bgsh1: var=else
bgsh1: kill -TSTP $$
sh -i +m
bgsh2: echo $var; kill -TSTP $$
something
sh -i +m
bgsh1: echo $var; kill -TSTP $$
else
[1] + Stopped sh -i +m
[2] - Stopped sh -i +m
$