다른 프로그램의 표준 입력과 출력을 다 가로채는 방법?
글쓴이: raymundo / 작성시간: 월, 2013/08/05 - 7:02오후
안녕하세요,
A라는 프로그램이 있는데, 이것은 아주 간단하게 표준출력 한 번 표준입력 한 번 입출력을 하고 끝납니다.
printf("%d", 100); scanf("%d", &num);
이 프로그램은 건드리지 않고, 이 프로그램의 출력을 읽고, 그 출력에 따라 적절한 입력값을 결정해서 넣어주는 프로그램을 만들고 싶습니다.
즉 사람이 눈으로 보고 키보드로 입력하는 걸 대신하는 건데...
제가 시도한 방법은
1) 셀에서 mkfifo 를 써서 네임드 파이프 두 개를 만들고
2) 파이프 하나에서 읽고, 다른 파이프로 쓰는 프로그램 B를 만들고:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <sys/stat.h> #include <sys/types.h> #include <errno.h> #define FIFO_READ "./fifo_read" #define FIFO_WRITE "./fifo_write" #define BUF_SIZE 100 int main() { int fd_r, fd_w; FILE *p_r, *p_w; char buf[BUF_SIZE]; // 두 개의 파이프를 열고 fd_r = open( FIFO_READ, O_RDONLY ); fd_w = open( FIFO_WRITE, O_WRONLY ); // fd_r 파이프에서 읽고 memset( buf, 0, BUF_SIZE ); read( fd_r, buf, BUF_SIZE ); printf("%s\n", buf); // fd_w 파이프로는 쓰고 strcpy(buf, "123"); write( fd_w, buf, strlen(buf) ); }
3) 이제 A 프로그램과 B 프로그램을 파이프를 매개로 연결하기 위해서 두 창에서 각각
$ ./B # 한쪽에서 B를 띄우고
$ ./A > fifo_read < fifo_write # 표준 입출력 리다이렉션 (파이프 이름을 B 입장에서 지었더니 여긴 좀 이상하군요)
이렇게 막상 해보니...
A가 종료되기 전에는 파이프로 전송이 이뤄지지 않더군요. 그래서 B가 read에서 하염없이 멈춥니다.
그런데 A가 종료되려면 scanf를 통과해야 하는데 거기에 fifo_write 파이프를 넣어주려면 B가 진행을 해야 하니...
제대로 교착 상태가 되어 버리는군요.
A프로그램을 손댈 수 없는 상황에서 어떻게 해결할 수 있을까요. 조언을 부탁드리겠습니다.
Forums:
*중요 : A 프로그램에는 fflush(stdout)
*중요 : A 프로그램에는 fflush(stdout) 이 선언되어 있어야 합니다.
A 프로그램
#include
int main()
{
int i;
printf("%d\n",100);
fflush(stdout);
scanf("%d",&i);
printf("i: %d\n",i);
fflush(stdout);
return 0;
}
B프로그램
#include
#define BUF_SIZE 1024
int main()
{
FILE *fp;
char buf[BUF_SIZE];
int i;
fp = popen("./a.out","w");
if(fp == NULL){
perror("popen() fail\n");
return -1;
}
fgets(buf,BUF_SIZE,fp);
sscanf(buf,"%d",&i);
fprintf(fp,"%d",50);
fgets(buf,BUF_SIZE,fp);
pclose(fp);
return 0;
}
A프로그램을 미리(따로) 실행시킬 필요없이 B를 실행시키면 내부적으로 A를 실행합니다
답변 감사합니다. 그런데 적어주신 코드로는 잘 안
답변 감사합니다. 그런데 적어주신 코드로는 잘 안 되는군요 ^^;
질문글에 제가 시도했던 방법을 충분히 적지 못했는데, popen을 쓰지 못했던 이유가,
입력과 출력 둘 다를 B가 처리하고자 하는건데 popen은 read 아니면 write 밖에 안 되니까요.
저 코드에서는 (혹시나 해서 다시 저 코드도 시도해봤습니다)
A의 출력을 B가 가로채지 못해서 fgets가 실패합니다. 자연히 sscanf도 실패하고 i 값도 갱신이 안되고요.
A의 출력은 그냥 스크린에 뜨지요.
popen의 모드를 "r"로 하면 출력은 잘 가로채지만 이번에는 fprintf 쪽이 실패하고, A의 scanf 는 키보드로 넣어줘야 합니다.
popen을 입력용 출력용 따로 해서 두 번 호출하는 것도 해봤지만, 예상할 수 있다시피 A 프로세스가 두 개 실행됩니다.
물론 popen을 써서 해결하는 방법이 있는데 제가 찾지 못한 걸 수도 있습니다만, 그래서 그 다음 시도가 본문에 적었던 네임드 파이프였는데... 본문에 적은 것처럼 막혔던 거죠.
하지만 지금 답글 덕분에 fflush(stdout)가 A에 있으면 본문에 적었던 코드로 성공하는 걸 확인했습니다. 그래서 일단 한 단계는 진전을 했네요 :-)
제가 이 질문을 다른 곳에서 보고는 직접 해보다가 막혀서 질문드리는 건데, 원질문자분의 상황이 어떤지 모르겠지만 A를 수정할 수는 없다고 하더라고요.
그래서 저도 A를 건드리지 않고 해결해보고 싶었거든요.
적어도 그 원질문자가 가지고 있는 A코드가 혹시라도 fflush를 사용하고 있다면 해결이 되겠네요.
셀에서 A를 실행할 때 출력의 버퍼링을 못하게 할 수 있다면 fflush가 없어도 되지 않을까 싶기도 한데 말이죠.
좋은 하루 되세요!
원프로그램을 수정할 수 없다면 setbuf 함수로
원프로그램을 수정할 수 없다면
setbuf 함수로 해결할 수 있습니다.
#include
#define BUF_SIZE 1024
int main()
{
FILE *fp;
char buf[BUF_SIZE];
int i;
setbuf(stdout,NULL);
fp = popen("./a.out","w");
if(fp == NULL){
perror("popen() fail\n");
return -1;
}
fgets(buf,BUF_SIZE,fp);
sscanf(buf,"%d",&i);
fprintf(fp,"%d",50);
fgets(buf,BUF_SIZE,fp);
pclose(fp);
return 0;
}
입 출력에 따라 자동화 시키기 위해 아주 예전에 사용해본 것이 expect를 써봤던 경험이 있는데
간단하게 목적 만을 이야기하자면, 매크로프로그램 만들고 싶으시단 거죠?
expect가 제격일 겁니다.
tcl/tk 에 익숙하지 않더라도 아마 간단한 정도는 쉽게 구현할 수 있을 겁니다.
감사합니다. 물론 expect가 딱 좋은 해결책인데,
감사합니다. 물론 expect가 딱 좋은 해결책인데, 원질문자가 C로 하는 법을 물었던 거라서 저도 C로 해결해보고 싶은 거라서요. :-)
좋은 하루 되세요!
음 ..
- child 와 parent 를 바꿔 쓴거 바로잡음.. ;;
되면 한다! / feel no sorrow, feel no pain, feel no hurt, there's nothing gained.. only love will then remain.. 『 Mizz 』
감사합니다. 그런데 해보니까 문제가요... A 쪽
감사합니다.
그런데 해보니까 문제가요...
A 쪽 동작이 scanf - printf 순이 아니라 그 반대인 경우이고,
B 에서 이걸 그냥 답글처럼 write, read 순으로 하면 잘돌아가는데 (A와 B가 둘 다 출력-입력 순으로 동작하는 이상한 모양새이지만 그래도 제대로 돌아가더군요)
B 에서는 반드시 read - write 순으로 해야 합니다.
(질문글에서는 자세히 안 썼는데, A가 숫자 두 개를 던져주면 B가 그걸 읽고 덧셈을 한 후 결과를 A에게 입력해주어 채점하려는 거라서)
그래서 B를 read - write 순으로 고쳤더니, 여전히 유사한 문제가 나옵니다.
1) A쪽에서 printf 후에 fflush 를 하지 않으면 아예 B가 전혀 읽지를 못 함
2) A쪽에서 printf 후에 fflush 를 하면, B가 읽고 화면에 출력까지는 하는데, 더 읽을 게 없을 때 read 가 블로킹되어 버리는지 while 문 밖으로 나가질 못함
결국 중간에 더 진행이 안 된 상태로 머무르게 되네요.
이리저리 수정해보면서 해 봤는데,
1) A는 역시 printf()직후에 fflush 가 있어야 함 - 없으면 아예 B가 읽지 못함
2) B는 read 를 while 을 써서 반복하지 않고, 딱 한 번만 긴 버퍼에 읽게 함 - 그래야 다음 진행이 가능
그러면 원하는 대로 동작하더군요. 제가 본문에 쓴 방법은 A와 B를 각각 실행해야 했는데, 이건 B만 실행해도 되니까 한결 편해지긴 했습니다. :-) 다시 한번 감사합니다.
(저 read 블로킹을 해결해보려고 non blocking 옵션도 넣으려고 해보고 하려는데 영 잘 안 되네요)
좋은 하루 되세요!
음 ..
A 에 데이터를 던져주고, 결과를 받는 걸로 착각했네요.
순서를 바꿔보니, A 가 write 를 호출하지 않고, 바로 read 에서 block 되어 버리던데..
말씀하신대로 printf 가 buffering 되어버리네요. (\n 이 있는데도 buffering 되는군요..;;)
결국 A 에서 fflush() 를 쓰던지, setbuf 로 unbuffering mode 로 바꿔줘야 하네요.
A 를 수정하지 않고, B 단독으로 해볼만한 거는 못 찾겠네요.
되면 한다! / feel no sorrow, feel no pain, feel no hurt, there's nothing gained.. only love will then remain.. 『 Mizz 』
위의 popen_rw()를 보면서 질문이 생겼습니다.
원래 제가 하려던 목적은 덕분에 잘 해결이되었는데요,
우연히 popen() 소스코드를 봤는데, 위의 popen_rw()와 상당히 비슷하더라구요.
[bushi@rose HYM]$ cat
음 ..
혹시나 해서 expect 소스 들여다 보다가, 좀 지저분해서 은근히 짜증났었는데.. 깔끔하네요.
덕분에 ptmx 쓰는 법에 대해 잘 배웠습니다. 감사합니다.
되면 한다! / feel no sorrow, feel no pain, feel no hurt, there's nothing gained.. only love will then remain.. 『 Mizz 』
오... 감사합니다. 원래의 A 코드에 맞게 좀
오... 감사합니다.
원래의 A 코드에 맞게 좀 고쳐보고 있는데, (코드를 잘 이해를 못해서 여기가 입출력이다 싶은 부분만)
recv_from_child 가 상당히 많이 불리더군요. wrap_cmd 가 보내는 것도 다시 여기에서 읽히기도 하고.
buf 를 매번 출력시켜보면 정체를 알 수 없는 내용이 잔뜩 출력되던데 이게 뭔지 이해하는 데 시간이 좀 걸리겠어요.
하지만 원하는 대로 숫자 두개를 받고 더해서 더한 결과를 A로 보내어 결과를 다시 출력받는 것까지 제대로 되었습니다.
A에 fflush가 없어도 잘 되는군요.
다시 한 번 감사합니다!
좋은 하루 되세요!
저도 비슷한 것을 해보고 있습니다.
제가 해보고 있는 것은 아래와 같이 string을 받아서 다시 출력하는 것인데요.
위에 보여주신 코드를 거의 대부분 사용해서 아래와 같이 만들어 봤습니다.
그런데 실행예를 보면 아래와 같이 입력을 받았지만 원래의 program으로 다시 write하는 곳에서 실패를
하는 것 같아요.
혹시 시간되시면, 조언을 좀 부탁드리고 싶습니다.
음 ..
read_write 가 scanf() 로 입력을 받고 있으니, 입력되는 문자열에 '\n' 까지 포함되어야 합니다.
get_user_input() 에서 문자열의 마지막 부분에 '\n' 을 넣어주면 될 것 같고..
두 번째의 ret = write_p(rwp, string, len); 에서.. len 에 아무런 값도 들어 있지 않습니다.
호출하기 전에 len = strlen(string); 한 줄 넣어주면 될 것 같네요.
되면 한다! / feel no sorrow, feel no pain, feel no hurt, there's nothing gained.. only love will then remain.. 『 Mizz 』
답변 감사합니다.
말씀해주신대로 2줄을 수정했습니다.
read_write()의 마지막에 buf[len] = '\0';을 buf[len] = '\n'으로 수정했구요.
main()에서 write_p를 호출하기 전에
len = strlen(string)을 추가했습니다.
그런데 아직도 뭔가 문제가 있는 모양이에요 ..
실행예가 다음과 같습니다.
$ ./a.out
Input string : 12345
ret = 6
^C
ret값이 제대로 인 것을 보면 write가 된 것 같은데
read_write로 부터 entered string is %s 가 출력이 안되네요..
이건 왜 그럴까요?
뭔가 실수가 있었나봐요..
알려주신 2줄 수정을 해보니 잘되네요 ^^
수정한 내용은요 ~
일단 read_write를 아래와 같이 수정했구요
한가지 더 추가 질문이 생겼습니다.
원래의 예제는 이제 잘되는데요,
제가만들 read_write 말고 리눅스의 /bin/passwd를 사용해서 password입력을 가로채보려고 했는데
그건 안되네요.
/bin/passwd를 실행하면
제일먼제 New password:라는 출력이 나오는데요.
이건 pipe를 통해서 출력이 안되나봐요 .
printf("%s", rbuf); 대신에 printf("---%s", rbuf)를 사용해봤는데
결과는 ---New password: 가 찍히지 않고, New password가 찍히네요.
이건 왜 그런걸까요?
음 ..
https://kldp.org/comment/622517#comment-622517
passwd 명령의 prompt 는 stderr 로 출력됩니다.
passwd 를 컨트롤 하실거면 linux-pam 이나 setspent, getspent, .. 류의 함수를 이용해서..
passwd 와 유사한 프로그램을 만드시는게 나을겁니다.
되면 한다! / feel no sorrow, feel no pain, feel no hurt, there's nothing gained.. only love will then remain.. 『 Mizz 』
아..그렇군요..
scp 도 stderr로 출력되나요?
위의 프로그램을 써서 scp도 한번 해봤는데, scp도 pipe로 출력이 안되더라구요.
음 ..
ssh 는 직접 tty 를 열어서 읽고 씁니다. (scp 가 내부적으로 ssh 를 호출함)
되면 한다! / feel no sorrow, feel no pain, feel no hurt, there's nothing gained.. only love will then remain.. 『 Mizz 』
popen()에 관한 질문인데요
원래 제가 하려던 목적은 덕분에 잘 해결이되었는데요,
우연히 popen() 소스코드를 봤는데, 위의 popen_rw()와 상당히 비슷하더라구요.
switch (pid = vfork()) {
case -1: /* error */
(void) close(pdes[0]);
(void) close(pdes[1]);
return (NULL);
/* NOTREACHED */
case 0: /* child */
if (*type == 'r') {
if (pdes[1] != fileno(stdout)) {
(void) dup2(pdes[1], fileno(stdout));
(void) close(pdes[1]);
}
(void) close(pdes[0]);
} else {
if (pdes[0] != fileno(stdin)) {
(void) dup2(pdes[0], fileno(stdin));
(void) close(pdes[0]);
}
(void) close(pdes[1]);
}
execl("/bin/sh", "sh", "-c", program, NULL);
_exit(127);
/* NOTREACHED */
}
/* parent; assume fdopen can't fail... */
if (*type == 'r') {
iop = fdopen(pdes[0], type);
(void) close(pdes[1]);
} else {
iop = fdopen(pdes[1], type);
(void) close(pdes[0]);
}
pids[fileno(iop)] = pid;
return (iop);
popen() 의 원형은 IEEE Std 1003.1
popen() 의 원형은 IEEE Std 1003.1-2008 에 다음처럼 정의되어 있습니다.
어째서 반환형이 int 가 아니라 FILE * 로 정해졌냐는 뜻의 질문이라면, 역사공부를 하면서 자료를 찾아보시면 되겠습니다.
원전인 SVID(System V Interface Definition) 은 AT&T UNIX System V 의 그것을 뜻합니다.
개인적으론, read-only 와 write-only 라는 제한을 그나마 비슷하게라도 구현가능하게 해주는 게 FILE * 이기 때문이라 생각합니다.
fopen() 의 원형은
이고, fopen() 을 아는 사람이라면 popen() 에 대해 별 의구심이 들지 않을 테죠.
음 ..
뭐를 쓰던지 그다지 큰 차이는 없습니다.
Stream I/O 든 Low Level I/O 든 적당히 취향에 따라 고르면 되는 문제일 뿐..
popen_rw() 의 이름을 popen() 에서 따오긴 했지만, process open 이라는 컨셉만 가져 왔을 뿐..
실제로는 open/read/write 와 유사한 wrapper 를 만들기 위해 그냥 fd 를 리턴하도록 한 겁니다.
되면 한다! / feel no sorrow, feel no pain, feel no hurt, there's nothing gained.. only love will then remain.. 『 Mizz 』
댓글 달기