3

samtoolsの使用をC プログラムに統合しようとしています。このアプリケーションは、 BAMと呼ばれるバイナリ形式でデータを読み取りますstdin

$ cat foo.bam | samtools view -h -
...

(これは の無駄な使い方だと思いますが、BAM ファイルのバイトをコマンド ラインcatでパイプする方法を示しているだけです。これらのバイトは、他のアップストリーム プロセスから取得される可能性があります。)samtools

unsigned charCプログラム内で、バイトのチャンクをsamtoolsバイナリに書き込み、同時にsamtoolsこれらのバイトを処理した後に標準出力をキャプチャしたいと思います。

プロセスへの書き込みとプロセスからの読み取りを同時に行うことはできないためpopen()、公開されている の実装を使用することを検討しました。これはpopen2()、これをサポートするように作成されているようです。

私は次のテスト コードを書きました。これwrite()は、同じディレクトリにある BAM ファイルの 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.

Iが呼び出しの直後に変数close()である場合、ループはハングする前にもう 1 回繰り返されます。infpwrite()

...
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()このタスクに適した代替手段はありますか?

4

2 に答える 2

-2

この問題は、 の特定の実装とは関係ありませんpopen2。また、OS X ではpopen、双方向パイプを開くことができることに注意してください。これは、他の BSD システムでも同様です。これを移植可能にする場合は、双方向パイプを許可するかどうかの構成チェックpopen(または構成チェックと同等のもの) が必要です。

パイプをノンブロッキング モードに切り替え、無限ループで と を交互に呼び出す必要がありますread。このようなループは、プロセスがビジーなwriteときに CPU を浪費しないようにするために、を使用するか、ファイル記述子が「利用可能」になるのをブロックする同様のメカニズム (より多くのデータを読み取るか、または書き込み用のデータを受け入れる準備ができている) を使用する必要があります。samtoolsselectpoll

インスピレーションについては、この質問を参照してください。

于 2014-06-19T22:39:46.690 に答える