我正在尝试将samtools的使用集成到C程序中。此应用程序以名为BAM的二进制格式读取数据,例如从stdin读取数据。
$ cat foo.bam | samtools view -h -
...(我意识到这是对cat的无用使用,但我只是在展示如何将BAM文件的字节通过管道传输到命令行上的samtools。这些字节可能来自其他上游进程。)
在C程序中,我希望将大块unsigned char字节写入samtools二进制文件,同时在处理这些字节之后从samtools捕获标准输出。
因为我不能使用popen()同时对进程进行写入和读取,所以我研究了使用公开可用的popen2()实现,它似乎是为了支持这一点而编写的。
我编写了以下测试代码,尝试将位于同一个目录中的write() 4 kB块字节分配到samtools进程。然后,它将read()的字节从samtools输出到一行缓冲区,打印为标准错误:
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define READ 0
#define WRITE 1
pid_t popen2(const char *command, int *infp, int *outfp)
{
int p_stdin[2], p_stdout[2];
pid_t pid;
if (pipe(p_stdin) != 0 || pipe(p_stdout) != 0)
return -1;
pid = fork();
if (pid < 0)
return pid;
else if (pid == 0)
{
close(p_stdin[WRITE]);
dup2(p_stdin[READ], READ);
close(p_stdout[READ]);
dup2(p_stdout[WRITE], WRITE);
execl("/bin/sh", "sh", "-c", command, NULL);
perror("execl");
exit(1);
}
if (infp == NULL)
close(p_stdin[WRITE]);
else
*infp = p_stdin[WRITE];
if (outfp == NULL)
close(p_stdout[READ]);
else
*outfp = p_stdout[READ];
return pid;
}
int main(int argc, char **argv)
{
int infp, outfp;
/* set up samtools to read from stdin */
if (popen2("samtools view -h -", &infp, &outfp) <= 0) {
printf("Unable to exec samtools\n");
exit(1);
}
const char *fn = "foo.bam";
FILE *fp = NULL;
fp = fopen(fn, "r");
if (!fp)
exit(-1);
unsigned char buf[4096];
char line_buf[65536] = {0};
while(1) {
size_t n_bytes = fread(buf, sizeof(buf[0]), sizeof(buf), fp);
fprintf(stderr, "read\t-> %08zu bytes from fp\n", n_bytes);
write(infp, buf, n_bytes);
fprintf(stderr, "wrote\t-> %08zu bytes to samtools process\n", n_bytes);
read(outfp, line_buf, sizeof(line_buf));
fprintf(stderr, "output\t-> \n%s\n", line_buf);
memset(line_buf, '\0', sizeof(line_buf));
if (feof(fp) || ferror(fp)) {
break;
}
}
return 0;
}(对于foo.bam的本地副本,下面是用于测试的二进制文件的链接。但是任何BAM文件都可以用于测试。)
汇编:
$ cc -Wall test_bam.c -o test_bam问题是,过程挂起在write()调用之后:
$ ./test_bam
read -> 00004096 bytes from fp
wrote -> 00004096 bytes to samtools process
[bam_header_read] EOF marker is absent. The input is probably truncated.如果我在close()调用之后立即执行infp变量,那么循环在挂起之前再进行一次迭代:
...
write(infp, buf, n_bytes);
close(infp); /* <---------- added after the write() call */
fprintf(stderr, "wrote\t-> %08zu bytes to samtools process\n", n_bytes);
...使用close()语句:
$ ./test_bam
read -> 00004096 bytes from fp
wrote -> 00004096 bytes to samtools process
[bam_header_read] EOF marker is absent. The input is probably truncated.
[main_samview] truncated file.
output ->
@HD VN:1.0 SO:coordinate
@SQ SN:seq1 LN:5000
@SQ SN:seq2 LN:5000
@CO Example of SAM/BAM file format.
read -> 00004096 bytes from fp
wrote -> 00004096 bytes to samtools process通过这一更改,我得到了一些输出,如果我在命令行上运行samtools,就会得到一些输出,但是如前所述,这个过程再次挂起。
如何使用popen2()将数据以块形式写入内部缓冲区?如果这是不可能的,那么除了popen2()之外,还有其他更适合这项任务的方法吗?
发布于 2014-06-19 22:58:37
作为pipe的替代方案,为什么不通过samtools与socket进行通信呢?在检查samtools源代码时,文件knetfile.c指示samtools具有可用的套接字通信:
#include "knetfile.h"
/* In winsock.h, the type of a socket is SOCKET, which is: "typedef
* u_int SOCKET". An invalid SOCKET is: "(SOCKET)(~0)", or signed
* integer -1. In knetfile.c, I use "int" for socket type
* throughout. This should be improved to avoid confusion.
*
* In Linux/Mac, recv() and read() do almost the same thing. You can see
* in the header file that netread() is simply an alias of read(). In
* Windows, however, they are different and using recv() is mandatory.
*/这可能提供一个比使用pipe2更好的选择。
发布于 2014-06-19 22:39:46
这个问题与popen2的特定实现无关。还请注意,在OS上,popen允许您打开双向管道,在其他BSD系统上也可能是这样。如果这是可移植的,您将需要一个配置检查,看看popen是否允许双向管道(或类似于配置检查的东西)。
您需要将管道切换到非阻塞模式,并在没完没了的循环中交替使用read和write调用。这样的循环,为了避免在samtools进程繁忙时浪费CPU,需要使用select、poll或类似的机制来阻止文件描述符变得“可用”(需要读取更多的数据,或者准备接受用于写入的数据)。
请参阅这个问题获得一些灵感。
https://stackoverflow.com/questions/24316460
复制相似问题