comando - cómo controlar popen stdin, stdout, stderr redirection?
biopython manual (5)
Estoy confundido acerca de cómo popen () redirige stdin, stdout y stderr del proceso hijo en Unix. La página man en popen () no es muy clara en este sentido. La llamada
FILE *p = popen("/usr/bin/foo", "w");
bifurca un proceso hijo y ejecuta un shell con argumentos "-c", "/ usr / bin / foo" y redirige stdin de este shell (que se redirige stdin de foo), stdout a p. ¿Pero qué pasa con stderr? ¿Cuál es el principio general detrás de esto?
Me di cuenta de que si abro un archivo en foo (usando fopen, socket, accept etc.) y el proceso principal no tiene stdout, se le asigna el siguiente número de archivo disponible, que es 1 y así sucesivamente. Esto ofrece resultados inesperados de llamadas como fprintf (stderr, ...).
Se puede evitar escribiendo
FILE *p = popen("/usr/bin/foo 2>/dev/null", "w");
en el programa para padres, pero ¿son sus mejores formas?
El valor de retorno de popen () es una secuencia de E / S estándar normal en todos los aspectos, salvo que debe cerrarse con pclose () en lugar de fclose (3). Escribir en una secuencia de este tipo escribe en la entrada estándar del comando; la salida estándar del comando es la misma que la del proceso que llamó a popen (), a menos que el comando lo modifique. Por el contrario, la lectura de un flujo "popened" lee la salida estándar del comando, y la entrada estándar del comando es la misma que la del proceso que llamó a popen ().
Desde su página de manual, le permite leer los comandos de salida estándar o escribir en su entrada estándar. No dice nada sobre stderr. Por lo tanto, eso no se redirige.
Si proporciona "w", enviará sus cosas al stdin del shell que se ejecuta. Por lo tanto, haciendo
FILE * file = popen("/bin/cat", "w");
fwrite("hello", 5, file);
pclose(file);
Hará que el shell ejecute / bin / cat, y le pasará la cadena "hello"
como su flujo de entrada estándar. Si desea redireccionar, por ejemplo, stderr al archivo "foo"
haga esto primero, antes de ejecutar el código anterior:
FILE * error_file = fopen("foo", "w+");
if(error_file) {
dup2(fileno(error_file), 2);
fclose(error_file);
}
Abrirá el archivo y duplicará su descriptor de archivo a 2, cerrando luego el descriptor del archivo original.
Ahora, si su stdout está cerrado en su padre, entonces si el niño llama open
obtendrá 1, ya que eso es (si stdin ya está abierto) el siguiente descriptor de archivo libre. La única solución que veo es usar dup2 y duplicar algo en el padre, como el código anterior. Tenga en cuenta que si el hijo abre stdout
, no hará que stdout
abra también en el padre. Permanece cerrado allí.
Visite popenRWE por Bart Trojanowski. Manera limpia de hacer las 3 tuberías.
idea simple: por qué no agregar "2> & 1" a la cadena de comandos para forzar a bash a redirigir stderr a stdout (OK, escribir en stdin todavía no es posible, pero al menos obtenemos stderr y stdout en nuestro programa C).
si solo quieres obtener STDERR, prueba esto:
#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");
}
y obtendrás esto:
shellcmd:ls: cannot access kangear: No such file or directory
popen(3)
es solo una función de biblioteca, que se basa en fork(2)
y pipe(2)
para hacer el trabajo real.
Sin embargo, la pipe(2)
solo puede crear tuberías unidireccionales. Para enviar la entrada de proceso secundario, y también capturar la salida, debe abrir dos tubos.
Si también desea capturar el stderr
, es posible, pero luego necesitará tres tubos y un bucle de select
para arbitrar las lecturas entre las corrientes stdout
y stderr
.
Aquí hay un ejemplo para la versión de dos tubos.