#include <tcl.h>
int main(int argc, char** argv)
{
Tcl_Interp *interp = Tcl_CreateInterp();
Tcl_Channel stdoutChannel = Tcl_GetChannel(interp, "stdout", NULL);
Tcl_UnregisterChannel(interp, stdoutChannel);
Tcl_Channel myChannel = Tcl_OpenFileChannel(interp, "/home/aminasya/nlb_rundir/imfile", "w", 0744);
Tcl_RegisterChannel(interp, myChannel);
Tcl_Eval(interp, "puts hello");
}
在这段代码中,我试图关闭stdout频道并将其重定向到文件。 (如Get the output from Tcl C Procedures所述)。运行后,“imfile”已创建但为空。怎么了?
我也见过How can I redirect stdout into a file in tcl,但我需要使用Tcl C API。
我也尝试过这种方式,但是没有结果。
FILE *myfile = fopen("myfile", "W+");
Tcl_Interp *interp = Tcl_CreateInterp();
Tcl_Channel myChannel = Tcl_MakeFileChannel(myfile, TCL_WRITABLE);
Tcl_SetStdChannel(myChannel, TCL_STDOUT);
答案 0 :(得分:3)
您的案例中的难点是Tcl解释器的标准通道与标准流的文件描述符(FD)之间的交互,如主程序(和C运行时)所见,再加上{{的语义Unix中的1}}。
使所有输出重定向的卷的过程如下:
操作系统确保在程序开始执行时打开三个标准文件描述符(FD)(编号为0,1和2,其中1为标准输出)。
一旦您创建的Tcl解释器初始化其三个标准通道(当您为“stdout”调用open(2)
时会发生这种情况,如here所述),它们已经与那些已经相关联主程序中现有的三个FD。
请注意,基础FD不会被克隆,相反,它们只是从封闭程序中“借用”。事实上,我认为在99%的情况下这是明智之举。
当您关闭(取消注册时发生)Tcl解释器中的标准通道Tcl_GetChannel()
时,基础FD(1)也将关闭。
对stdout
的调用内部调用fopen(3)
,它获取最低的空闲FD,即1,从而获得主程序(和C运行时)理解的标准输出流)现在已连接到该打开的文件。
然后从文件中创建一个Tcl通道并将其注册到解释器。对于翻译来说,频道确实变为open(2)
。
最后,在主程序中写入标准输出流并写入Tcl解释器中的标准输出通道都会发送到相同的底层FD,因此最终会出现在同一个文件中。
我可以看到两种方法来处理这种行为:
stdout
打开文件,使用大于2的FD。这两种方法都有其优点和缺点:
“保留FD 1”通常更容易实现,如果您想在Tcl解释器中仅重定向 stdout
,并将其他两个标准通道保留为连接到封闭程序使用的相同标准流,这种方法似乎是明智的。可能的缺点是:
stdout
(见下文),可能还需要其他方法。dup(2)
和stdin
的标准流可能会有用。手动初始化Tcl解释器中的标准通道需要更多代码,据称保证正确排序(stderr
,stdin
,stdout
, 以该顺序)。如果您希望Tcl解释器中剩余的两个标准通道连接到封闭程序的匹配流,则此方法更有效;第一种方法是免费的。
以下是如何保留FD 1以使Tcl解释器中只有stderr
连接到文件;对于封闭程序,FD 1仍然连接到由OS设置的相同流。
stdout
构建和运行(在Debian Wheezy中完成):
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <tcl.h>
int redirect(Tcl_Interp *interp)
{
Tcl_Channel chan;
int rc;
int fd;
/* Get the channel bound to stdout.
* Initialize the standard channels as a byproduct
* if this wasn't already done. */
chan = Tcl_GetChannel(interp, "stdout", NULL);
if (chan == NULL) {
return TCL_ERROR;
}
/* Duplicate the descriptor used for stdout. */
fd = dup(1);
if (fd == -1) {
perror("Failed to duplicate stdout");
return TCL_ERROR;
}
/* Close stdout channel.
* As a byproduct, this closes the FD 1, we've just cloned. */
rc = Tcl_UnregisterChannel(interp, chan);
if (rc != TCL_OK)
return rc;
/* Duplicate our saved stdout descriptor back.
* dup() semantics are such that if it doesn't fail,
* we get FD 1 back. */
rc = dup(fd);
if (rc == -1) {
perror("Failed to reopen stdout");
return TCL_ERROR;
}
/* Get rid of the cloned FD. */
rc = close(fd);
if (rc == -1) {
perror("Failed to close the cloned FD");
return TCL_ERROR;
}
/* Open a file for writing and create a channel
* out of it. As FD 1 is occupied, this FD won't become
* stdout for the C code. */
chan = Tcl_OpenFileChannel(interp, "aaa.txt", "w", 0666);
if (chan == NULL)
return TCL_ERROR;
/* Since stdout channel does not exist in the interp,
* this call will make our file channel the new stdout. */
Tcl_RegisterChannel(interp, chan);
return TCL_OK;
}
int main(void)
{
Tcl_Interp *interp;
int rc;
interp = Tcl_CreateInterp();
rc = redirect(interp);
if (rc != TCL_OK) {
fputs("Failed to redirect stdout", stderr);
return 1;
}
puts("before");
rc = Tcl_Eval(interp, "puts stdout test");
if (rc != TCL_OK) {
fputs("Failed to eval", stderr);
return 2;
}
puts("after");
Tcl_Finalize();
return 0;
}
正如您所看到的,$ gcc -W -Wall -I/usr/include/tcl8.5 -L/usr/lib/tcl8.5 -ltcl main.c
$ ./a.out
before
after
$ cat aaa.txt
test
输出的字符串“test”进入文件,而字符串“before”和“after”,在封闭程序中为puts
n到FD 1 (这是write(2)
最终做的事情)去终端。
手工初始化方法将是这样的(某种伪代码):
puts(3)
我没有测试过这种方法。
答案 1 :(得分:0)
在C API级别,假设您使用的是基于Unix的操作系统(即非Windows),您可以通过使用正确的操作系统调用来更简单地执行此操作:
#include <fcntl.h>
#include <unistd.h>
// ... now inside a function
int fd = open("/home/aminasya/nlb_rundir/imfile", O_WRONLY|O_CREAT, 0744);
// Important: deal with errors here!
dup2(fd, STDOUT_FILENO);
close(fd);
您还可以使用dup()
保存旧标准输出(对于Tcl将忽略的任意数字),以便您可以在以后恢复它,如果需要。
答案 2 :(得分:0)
试试这个:
FILE *myfile = fopen("myfile", "W+");
Tcl_Interp *interp = Tcl_CreateInterp();
Tcl_Channel myChannel = Tcl_MakeFileChannel(myfile, TCL_WRITABLE);
Tcl_RegisterChannel(myChannel);
Tcl_SetStdChannel(myChannel, TCL_STDOUT);
您需要先使用解释器注册通道,然后才能重置std通道以使用它。