2008-11-11 8 views
24

मैं उलझन में हूं कि कैसे popen() यूनिक्स में बाल प्रक्रिया के stdin, stdout और stderr को पुनर्निर्देशित करता है। पॉपन() पर मैन पेज इस संबंध में बहुत स्पष्ट नहीं है। कॉलpopen stdin, stdout, stderr redirection को नियंत्रित करने के लिए कैसे?

FILE *p = popen("/usr/bin/foo", "w"); 

कांटे एक बच्चे की प्रक्रिया और तर्क के साथ एक खोल "-c" "/ usr/bin/foo", और इस खोल के stdin (जो foo की stdin रीडायरेक्ट किया जाता है) पुनर्निर्देश कार्यान्वित करता है, करने के लिए stdout पी। लेकिन stderr के साथ क्या होता है? इसके पीछे सामान्य सिद्धांत क्या है?

मैंने देखा कि, अगर मैं foo (fopen, सॉकेट, स्वीकृति इत्यादि का उपयोग करके) में एक फ़ाइल खोलता हूं, और मूल प्रक्रिया में कोई स्टडआउट नहीं होता है, तो यह अगले उपलब्ध फ़ाइल नंबर को सौंपा जाता है, जो 1 और इसी तरह से होता है। यह fprintf (stderr, ...) जैसे कॉल से अप्रत्याशित परिणाम प्रदान करता है।

यह

FILE *p = popen("/usr/bin/foo 2>/dev/null", "w"); 
माता पिता कार्यक्रम में

लेखन से बचा जा सकता है, लेकिन उनके लिए बेहतर तरीके हैं?

+0

: कभी ऐसा मत करो । यूनिक्स पर सी प्रोग्राम 0,1,2 समझदार फ़ाइल वर्णनकर्ता हैं, और कुछ भी किसी बिंदु पर सी को आमंत्रित करता है। सामान्य नियम: * हमेशा * stdio के रूप में उपयोग के लिए आरक्षित 0,1,2। यदि आप उनमें से किसी एक को बंद करना चाहते हैं, तो इसे '/ dev/null' पर रीडायरेक्ट करें ताकि एफडी अभी भी लिया जा सके और कुछ भी इसे सौंपा गया न हो। –

उत्तर

28

popen(3) केवल एक लाइब्रेरी फ़ंक्शन है, जो वास्तविक कार्य करने के लिए fork(2) और pipe(2) पर निर्भर करता है।

हालांकि pipe(2) केवल यूनिडायरेक्शनल पाइप बना सकता है।बाल प्रक्रिया इनपुट भेजने के लिए, और आउटपुट को कैप्चर करने के लिए, आपको दो पाइप खोलने की आवश्यकता है।

आप भी stderr पर कब्जा करना चाहते हैं, संभव है कि है, लेकिन फिर आप तीन पाइप की आवश्यकता होगी, और मध्यस्थता करने की एक select पाश stdout और stderr धाराओं के बीच पढ़ता है।

दो-पाइप संस्करण के लिए here उदाहरण है।

7

popen से वापसी मान() एक सामान्य मानक मैं सभी मामलों में/ओ धारा को बचाने कि यह pclose के साथ बंद किया जाना चाहिए() के बजाय fclose (3)। इस तरह के एक स्ट्रीम को लिखने से कमांड के मानक इनपुट को लिखा जाता है; कमांड का मानक आउटपुट प्रक्रिया जैसा है जिसे popen() कहा जाता है, जब तक कि यह कमांड द्वारा परिवर्तित नहीं किया जाता है। इसके विपरीत, "पॉपडेड" स्ट्रीम से पढ़ने से कमांड के मानक आउटपुट को पढ़ता है, और कमांड का मानक इनपुट होता है जिसे पॉपन() कहा जाता है।

अपने मैनपेज से, इसलिए यह आपको मानक आउटपुट कमांड पढ़ने या इसके मानक इनपुट में लिखने की अनुमति देता है। यह stderr के बारे में कुछ भी नहीं कहता है। इस प्रकार इसे पुनर्निर्देशित नहीं किया गया है।

यदि आप "डब्ल्यू" प्रदान करते हैं, तो आप अपना सामान निष्पादित किए गए खोल के stdin पर भेज देंगे। इस प्रकार,

FILE * file = popen("/bin/cat", "w"); 
fwrite("hello", 5, file); 
pclose(file); 

कर खोल निष्पादित/bin/बिल्ली पड़ेगा, और वह अपने मानक इनपुट धारा के रूप में स्ट्रिंग "hello" गुजरती हैं। आप फ़ाइल पर रीडायरेक्ट करने, उदाहरण के stderr के लिए चाहते हैं "foo" पहले इससे पहले कि आप इसके बाद के संस्करण कोड निष्पादित ऐसा करते हैं,:

FILE * error_file = fopen("foo", "w+"); 
if(error_file) { 
    dup2(fileno(error_file), 2); 
    fclose(error_file); 
} 

यह 2 के लिए फ़ाइल खुल जाएगा, और नकली अपनी फ़ाइल वर्णनकर्ता, मूल फ़ाइल को बंद करने बाद में वर्णनकर्ता।

अब, यदि आपके पास अपने माता-पिता में अपना स्टडआउट बंद है, तो यदि बच्चा open पर कॉल करता है तो उसे 1 मिलेगा, क्योंकि यह (यदि stdin पहले ही खोला गया है) अगली मुफ्त फ़ाइल-डिस्क्रिप्टर है। केवल समाधान जो मैं देखता हूं वह सिर्फ उपरोक्त कोड की तरह, डुप्ली 2 का उपयोग करना और माता-पिता में कुछ डुप्लिकेट करना है। ध्यान दें कि यदि बच्चा stdout खुलता है, तो यह माता-पिता में भी stdout खुला होगा। यह वहां बंद रहता है।

+0

धन्यवाद। stdout के बारे में मेरी गलती के लिए खेद है (मुझे सोना चाहिए)। असल में, अब मैं देखता हूं कि मुझे अपने प्रश्न को फिर से लिखना चाहिए: क्या होता है यदि माता-पिता की प्रक्रिया में पहले मामले में कोई स्टडआउट नहीं है, या बाद में कोई स्टडीन नहीं है, या दोनों मामलों में कोई स्टडर नहीं है? –

+0

यदि आप पॉपन करने के लिए लिख रहे हैं, और यदि माता-पिता में स्टडआउट बंद कर दिया गया था, तो वास्तव में यदि बच्चा stdout पर लिखने का प्रयास करता है, तो यह व्यवहार करेगा जैसा कि आप दायर स्क्रिप्ट 4211 पर लिखने का प्रयास करेंगे: –

+0

धन्यवाद नहीं होगा। मैंने निष्कर्ष निकाला है कि एक प्रोग्राम में stdin, stdout, stderr पर भरोसा करना सुरक्षित नहीं है जिसका उपयोग किसी अन्य प्रोग्राम द्वारा कमांड पाइप में किया जा सकता है। वह तब तक है जब तक आप मैन्युअल पुनर्निर्देशन के माध्यम से इसे स्वयं नियंत्रित नहीं करते हैं (उदाहरण के लिए dup2() के साथ)। –

28

सरल विचार: "2> & 1" कमांड स्ट्रिंग के लिए stdout में stderr रीडायरेक्ट करने के लिए पार्टी के लिए मजबूर करने क्यों नहीं जोड़ (ठीक है, अभी भी stdin के लिए लिख संभव नहीं है लेकिन कम से कम हम stderr हो और हमारे सी में stdout कार्यक्रम)।

+0

बहुत बढ़िया, धन्यवाद! यह stderr के पॉपन पढ़ने के लिए महान काम करता है। –

5

बार्ट ट्रोजनोस्की द्वारा popenRWE देखें। सभी 3 पाइप करने के लिए साफ रास्ता।

+3

यह उपयोगी है। उत्सुकता के लिए यह जीपीएल है, इसलिए आप वास्तव में इसे कोड स्निपेट के रूप में नहीं मान सकते हैं। –

5

अगर आप सिर्फ STDERR प्राप्त करना चाहते हैं, इस प्रयास करें:

#include <stdio.h> 
#include <errno.h> 
#include <fcntl.h> 
#include <sys/wait.h> 
#include <malloc.h> 
#include <unistd.h> 
#include <string.h> 
#include <sys/types.h> 

/* 
* Pointer to array allocated at run-time. 
*/ 
static pid_t *childpid = NULL; 

/* 
* From our open_max(), {Prog openmax}. 
*/ 
static int  maxfd; 

FILE * 
mypopen(const char *cmdstring, const char *type) 
{ 
    int  i; 
    int  pfd[2]; 
    pid_t pid; 
    FILE *fp; 

    /* only allow "r" "e" or "w" */ 
    if ((type[0] != 'r' && type[0] != 'w' && type[0] != 'e') || type[1] != 0) { 
     errno = EINVAL;  /* required by POSIX */ 
     return(NULL); 
    } 

    if (childpid == NULL) {  /* first time through */ 
     /* allocate zeroed out array for child pids */ 
     maxfd = 256; 
     if ((childpid = calloc(maxfd, sizeof(pid_t))) == NULL) 
      return(NULL); 
    } 

    if (pipe(pfd) < 0) 
     return(NULL); /* errno set by pipe() */ 

    if ((pid = fork()) < 0) { 
     return(NULL); /* errno set by fork() */ 
    } else if (pid == 0) {       /* child */ 
     if (*type == 'e') { 
      close(pfd[0]); 
      if (pfd[1] != STDERR_FILENO) { 
       dup2(pfd[1], STDERR_FILENO); 
       close(pfd[1]); 
      } 
     } else if (*type == 'r') { 
      close(pfd[0]); 
      if (pfd[1] != STDOUT_FILENO) { 
       dup2(pfd[1], STDOUT_FILENO); 
       close(pfd[1]); 
      } 
     } else { 
      close(pfd[1]); 
      if (pfd[0] != STDIN_FILENO) { 
       dup2(pfd[0], STDIN_FILENO); 
       close(pfd[0]); 
      } 
     } 

     /* close all descriptors in childpid[] */ 
     for (i = 0; i < maxfd; i++) 
      if (childpid[i] > 0) 
       close(i); 

     execl("/bin/sh", "sh", "-c", cmdstring, (char *)0); 
     _exit(127); 
    } 

    /* parent continues... */ 
    if (*type == 'e') { 
     close(pfd[1]); 
     if ((fp = fdopen(pfd[0], "r")) == NULL) 
      return(NULL); 
    } else if (*type == 'r') { 
     close(pfd[1]); 
     if ((fp = fdopen(pfd[0], type)) == NULL) 
      return(NULL); 

    } else { 
     close(pfd[0]); 
     if ((fp = fdopen(pfd[1], type)) == NULL) 
      return(NULL); 
    } 

    childpid[fileno(fp)] = pid; /* remember child pid for this fd */ 
    return(fp); 
} 

int 
mypclose(FILE *fp) 
{ 
    int  fd, stat; 
    pid_t pid; 

    if (childpid == NULL) { 
     errno = EINVAL; 
     return(-1);  /* popen() has never been called */ 
    } 

    fd = fileno(fp); 
    if ((pid = childpid[fd]) == 0) { 
     errno = EINVAL; 
     return(-1);  /* fp wasn't opened by popen() */ 
    } 

    childpid[fd] = 0; 
    if (fclose(fp) == EOF) 
     return(-1); 

    while (waitpid(pid, &stat, 0) < 0) 
     if (errno != EINTR) 
      return(-1); /* error other than EINTR from waitpid() */ 

    return(stat); /* return child's termination status */ 
} 

int shellcmd(char *cmd){ 
    FILE *fp; 
    char buf[1024]; 
    fp = mypopen(cmd,"e"); 
    if (fp==NULL) return -1; 

    while(fgets(buf,1024,fp)!=NULL) 
    { 
     printf("shellcmd:%s", buf); 
    } 

    pclose(fp); 
    return 0; 
} 

int main() 
{ 
    shellcmd("ls kangear"); 
} 

और आप इस मिल जाएगा: मूल प्रश्न है, जो stdout को बंद करने का उल्लेख के बारे में

shellcmd:ls: cannot access kangear: No such file or directory 
संबंधित मुद्दे