如何使用 EXECL 从 C 代码执行 Python 脚本?


How to execute Python script from C code using EXECL?

我想知道如何使用 execl(或类似的)从我的 C 代码中执行 Python(或 Lua 等)脚本?

以下是一些"父/子"代码,显示了我如何使用 PIPES 将数据流发送给孩子。代码可能并不完美,但你明白了。注意底部的 execl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include <sys/types.h>
#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  

#define STDIN_FILENO    0       /* Standard input.  */
#define STDOUT_FILENO   1       /* Standard output.  */
#define STDERR_FILENO   2       /* Standard error output.  */
#define MAXLINE 4096

int main(void){
 int  n, parent_child_pipe[2], child_parent_pipe[2];
 pid_t pid;
 char line[MAXLINE];

 if (pipe(parent_child_pipe) < 0 || pipe(child_parent_pipe) < 0)
  puts("Error creating pipes...\
"
);

 if ( (pid = fork()) < 0)
  puts("Error forking...\
"
);
 else if (pid > 0) { /* PARENT */
  close(parent_child_pipe[0]);
  close(child_parent_pipe[1]);
  while (fgets(line, MAXLINE, stdin) != NULL) {
   n = strlen(line);
   if (write(parent_child_pipe[1], line, n) != n)
    puts("write error to pipe...\
"
);
   if ( (n = read(child_parent_pipe[0], line, MAXLINE)) < 0)
    puts("read error from pipe...\
"
);
   if (n == 0) {
    puts("child closed pipe...\
"
);
    break;
   }
   line[n] = 0; /* null terminate */
   if (fputs(line, stdout) == EOF)
    puts("fputs error...\
"
);
  }
  if (ferror(stdin))
   puts("fgets error on stdin...\
"
);
  exit(0);

 } else {  /* CHILD */
  close(parent_child_pipe[1]);
  close(child_parent_pipe[0]);
  if (parent_child_pipe[0] != STDIN_FILENO) {
   if (dup2(parent_child_pipe[0], STDIN_FILENO) != STDIN_FILENO)
    puts("dup2 error to stdin...\
"
);
   close(parent_child_pipe[0]);
  }
  if (child_parent_pipe[1] != STDOUT_FILENO) {
   if (dup2(child_parent_pipe[1], STDOUT_FILENO) != STDOUT_FILENO)
    puts("dup2 error to stdout...\
"
);
   close(child_parent_pipe[1]);
  }
  **if (execl("./child","child", (char *) 0) < 0)**
   puts("execl error...\
"
);
 }
}

现在上面的"child"程序是用 C 语言编写的,它只是通过 STDIN 接收流,操作流,然后使用 STDOUT 将其发送回。

类似:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  

#define STDIN_FILENO    0       /* Standard input.  */
#define STDOUT_FILENO   1       /* Standard output.  */
#define STDERR_FILENO   2       /* Standard error output.  */
#define MAXLINE 4096

int main(void){
 int  n;
 char line[MAXLINE];

 while ( (n = read(STDIN_FILENO, line, MAXLINE)) > 0) {
  line[n] = 0;  /* null terminate */  
  n = strlen(line);
  if (write(STDOUT_FILENO, line, n) != n)
   puts("write error");
 }
 exit(0);
}

所以工作正常,但现在我希望能够用 Python / Lua 等任何脚本语言编写 CHILD。我该怎么做?我尝试过类似的东西:

if (execl("python 的路径", "test.py", (char *) 0) < 0)

但它似乎只是挂起等待接收输入?

有人可以帮我解决这个问题吗?我假设 PIPES 可以与可以从 STDIN 读取并发送回 STDOUT 的 Lua / Python 之类的任何东西通信?

更新:

我现在做了一些小改动,这里是 Python 文件:"NullFilter.py",它简单地回显了它发送的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/usr/bin/python
import sys

class NullFilter:

      def execute(self):
            #Read data from STDIN...
            data = sys.stdin.read()
            #Write data to STDOUT...
            sys.stdout.write(data)
            exit(0)
if __name__ == '__main__':
      nf = NullFilter()
      nf.execute()

现在 C 代码调用它使用:

1
2
3
4
5
...
if (execl("/usr/bin/python","./NullFilter.py","./NullFilter.py",NULL, (char *) 0) < 0)
puts("execl error...\
"
);
...

当我现在运行它时,我可以在 STDIN 中输入文本,但必须按 CRTL-C 才能看到发生了什么:结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
debian@debian:~/Desktop/pipe example$ ./parent
hello
hello again
^CTraceback (most recent call last):
  File"./NullFilter.py", line 17, in <module>

nf.execute()
  File"./NullFilter.py", line 10, in execute
debian@debian:~/Desktop/pipe example$     data = sys.stdin.read()
KeyboardInterrupt

debian@debian:~/Desktop/pipe example$

更新 2:

好的,所以我一直在玩,只是将 Python 代码从 "sys.stdin.read()" 更改为 "sys.stdin.readline()" ,它似乎工作在一定程度上,但根本不完美......

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/bin/python
import sys

class NullFilter:

      def execute(self):
            #Read data from STDIN...
            data =""            
            for line in sys.stdin.readline():
                data = data + line
            #Write data to STDOUT...
            sys.stdout.write(data)
            exit(0)
if __name__ == '__main__':
      nf = NullFilter()
      nf.execute()

这是一种解决方法,但一点也不完美。还有其他想法如何让标准输入读取无缓冲的流吗?我查看了 Python 中的 SELECT 模块:

http://docs.python.org/library/select.html

我也尝试将 "-u" 传递给 Python 以使其成为 "unbuffered" 但仍然没有运气 ;-(

但这肯定不会这么难吧?

林顿


在玩了很多游戏并尝试刷新所有内容之后,我意识到我需要关闭从父级到子级的 PIPE 的 STDOUT 端(当然是在写入管道之后)...

所以现在的代码是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include <sys/types.h>  
#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  

#define STDIN_FILENO    0       /* Standard input.  */
#define STDOUT_FILENO   1       /* Standard output.  */
#define STDERR_FILENO   2       /* Standard error output.  */
#define MAXLINE 4096

int main(void){
 int  n, parent_child_pipe[2], child_parent_pipe[2];
 pid_t pid;
 char line[MAXLINE];
 int rv;

 if (pipe(parent_child_pipe) < 0 || pipe(child_parent_pipe) < 0)
  puts("Error creating pipes...\
"
);

 if ( (pid = fork()) < 0)
  puts("Error forking...\
"
);
 else if (pid > 0) { /* PARENT */
  close(parent_child_pipe[0]);  
  close(child_parent_pipe[1]);
  while (fgets(line, MAXLINE, stdin) != NULL) {
   n = strlen(line);
   if (write(parent_child_pipe[1], line, n) != n)
    puts("write error to pipe...\
"
);
   close(parent_child_pipe[1]);
   wait(&rv);
   if ( (n = read(child_parent_pipe[0], line, MAXLINE)) < 0)
    puts("read error from pipe...\
"
);
   if (n == 0) {
    puts("child closed pipe...\
"
);
    break;
   }
   line[n] = 0; /* null terminate */
   if (fputs(line, stdout) == EOF)
    puts("fputs error...\
"
);
  }
  if (ferror(stdin))
   puts("fgets error on stdin...\
"
);
  exit(0);

 } else {  /* CHILD */
  close(parent_child_pipe[1]);
  close(child_parent_pipe[0]);
  if (parent_child_pipe[0] != STDIN_FILENO) {
   if (dup2(parent_child_pipe[0], STDIN_FILENO) != STDIN_FILENO)
    puts("dup2 error to stdin...\
"
);
   close(parent_child_pipe[0]);
  }
  if (child_parent_pipe[1] != STDOUT_FILENO) {
   if (dup2(child_parent_pipe[1], STDOUT_FILENO) != STDOUT_FILENO)
    puts("dup2 error to stdout...\
"
);
   close(child_parent_pipe[1]);
  }
  if (execl("./NullFilter.py","./NullFilter.py", (char *) 0) < 0)
   puts("execl error...\
"
);
 }
}

你可以看到"close(parent_child_pipe[1]);"在写完上面的 PIPE 之后,这是我必须做的关键部分。这将强制将流刷新到 Python 脚本、Lua 脚本、C 代码等......

在上面你会看到我正在执行一个 Python 脚本"NullFilter.py"....

注意:如果您运行上面的代码,它将适用于输入/输出的一次迭代,因为 Python 脚本在第一次测试后关闭了管道,但是您可以在此基础上构建必需品...

感谢所有的帮助,我从这个练习中学到了很多;-)

林顿


我相信您可以通过使用 shebang 行启动 python 文件来实现您想要做的事情,如下所示:

1
#!/usr/bin/python

并确保它是可执行的,然后将脚本用作 execl()

中的可执行字段

见 http://linux.about.com/od/commands/l/blcmdl2_execve.htm:

Filename must be either a binary
executable, or a script starting with
a line of the form"#! interpreter
[arg]". In the latter case, the
interpreter must be a valid pathname
for an executable which is not itself
a script, which will be invoked as
interpreter [arg] filename.

在传递参数的情况下(理论上也是有效的),它看起来并不像 python 实际上正在执行脚本并完成它应该 - 而是打开一个交互式终端,这就是它挂起的原因 - 它希望你通过标准输入进行通信。尝试"test.py"的绝对路径,看看会发生什么,因为听起来 python 无法找到它。

实际上,还有另一种方法。没有理由不能通过管道将脚本逐行传递给解释器并以这种方式运行。


你试过冲孩子吗?

更新:

我尝试使用您的 C 代码和这 2 个脚本(perl 和 python),它们的行为与我机器上的 C 孩子完全一样

1
2
3
4
5
6
7
8
#!/usr/bin/perl -w
use IO::Handle;

while (<>)
{
        print;
        flush STDOUT;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/usr/bin/python
import sys

class NullFilter:
        def execute(self):
                while True:
                        line=sys.stdin.readline()
                        if not line: break
                        sys.stdout.write(line)
                        sys.stdout.flush()
                exit(0)
if __name__ == '__main__':
        nf = NullFilter()
        nf.execute()