Node.jsでストリームを使用してファイルを操作する方法

提供:Dev Guides
移動先:案内検索

著者は、 Write for DOnations プログラムの一環として、 Girls WhoCodeを選択して寄付を受け取りました。

序章

コンピューティングにおけるストリームの概念は、通常、安定した連続的なフローでのデータの配信を表します。 ソースからの読み取りまたはソースへの書き込みにストリームを継続的に使用できるため、すべてのデータを一度にメモリに収める必要がなくなります。

ストリームを使用すると、2つの大きな利点があります。 1つは、処理を開始する前にすべてのデータをメモリにロードする必要がないため、メモリを効率的に使用できることです。 もう1つの利点は、ストリームの使用が時間効率に優れていることです。 ペイロード全体を待つのではなく、ほぼすぐにデータの処理を開始できます。 これらの利点により、ストリームはI/O操作での大規模なデータ転送に適したツールになります。 ファイルは、いくつかのデータを含むバイトのコレクションです。 ファイルはNode.jsの一般的なデータソースであるため、ストリームはNode.jsのファイルを効率的に操作する方法を提供します。

Node.jsは、ストリームを操作するためのコアNode.jsモジュールであるstreamモジュールでストリーミングAPIを提供します。 すべてのNode.jsストリームは、EventEmitterクラスのインスタンスです(詳細については、 Node.js でのイベントエミッターの使用を参照してください)。 これらは、データ送信プロセス中にさまざまな間隔でリッスンできるさまざまなイベントを発行します。 ネイティブstreamモジュールは、データの読み取りと書き込み、送信ライフサイクルの管理、および送信エラーの処理に使用できるイベントをリッスンするためのさまざまな機能で構成されるインターフェイスを提供します。

Node.jsには4種類のストリームがあります。 彼らです:

  • 読み取り可能なストリーム:データを読み取ることができるストリーム。
  • 書き込み可能なストリーム:データを書き込むことができるストリーム。
  • デュプレックスストリーム:読み取りと書き込みが可能なストリーム(通常は同時に)。
  • 変換ストリーム:出力(または書き込み可能なストリーム)が入力(または読み取り可能なストリーム)の変更に依存しているデュプレックスストリーム。

ファイルシステムモジュール(fs)は、ファイルを操作し、一般的にローカルファイルシステムをナビゲートするためのネイティブNode.jsモジュールです。 これを行うためのいくつかの方法を提供します。 これらのメソッドのうちの2つは、ストリーミングAPIを実装します。 これらは、ストリームを使用してファイルを読み書きするためのインターフェイスを提供します。 これらの2つの方法を使用して、読み取りおよび書き込み可能なファイルストリームを作成できます。

この記事では、fs.createReadStreamおよびfs.createWriteStream関数を使用してファイルの読み取りと書き込みを行います。 また、あるストリームの出力を別のストリームの入力として使用し、カスタム変換スチームを実装します。 これらのアクションを実行することで、ストリームを使用してNode.jsのファイルを操作する方法を学びます。 これらの概念を示すために、Linuxベースのシステムにあるcat機能を複製し、端末からファイルに入力を書き込み、ファイルをコピーし、コンテンツを変換するコマンドを使用してコマンドラインプログラムを作成します。ファイル。

前提条件

このチュートリアルを完了するには、次のものが必要です。

  • 開発マシンにインストールされているNode.js。 このチュートリアルでは、バージョン14.10.0を使用します。 さまざまなプラットフォームへのインストールについては、チュートリアルシリーズNode.jsをインストールしてローカル開発環境を作成する方法を参照してください。
  • 私たちのシリーズNode.jsでコーディングする方法で見つけることができるNode.jsの基本的な知識。
  • Node.js fsモジュールの基本的な知識。これは、Node.jsのfsモジュールを使用したファイルの操作方法にあります。

ブラウザの端末を使用してこのチュートリアルのコマンドを試してみたい場合は、次のインタラクティブ端末の起動!ボタンをクリックして開始してください。

インタラクティブターミナルを起動します!

ステップ1—ファイル処理コマンドラインプログラムの設定

このステップでは、基本的なコマンドを使用してコマンドラインプログラムを作成します。 このコマンドラインプログラムは、チュートリアルの後半で学習する概念を示します。ここでは、ファイルを操作するために作成する関数でこれらのコマンドを使用します。

まず、このプログラムのすべてのファイルを含むフォルダーを作成します。 ターミナルで、node-file-streamsという名前のフォルダーを作成します。

mkdir node-file-streams

cdコマンドを使用して、作業ディレクトリを新しいフォルダに変更します。

cd node-file-streams

次に、お気に入りのテキストエディタでmycliprogramというファイルを作成して開きます。 このチュートリアルでは、ターミナルテキストエディタであるGNUnanoを使用します。 nanoを使用してファイルを作成して開くには、次のコマンドを入力します。

nano mycliprogram

テキストエディタで、次のコードを追加してshebangを指定し、Node.jsプロセスからのコマンドライン引数の配列を格納し、アプリケーションに必要なコマンドのリストを格納します。

node-file-streams / mycliprogram

#!/usr/bin/env node

const args = process.argv;
const commands = ['read', 'write', 'copy', 'reverse'];

最初の行には、プログラムインタープリターへのパスであるシバンが含まれています。 この行を追加すると、プログラムローダーはNode.jsを使用してこのプログラムを解析するようになります。

コマンドラインでNode.jsスクリプトを実行すると、Node.jsプロセスの実行時にいくつかのコマンドライン引数が渡されます。 これらの引数には、argvプロパティまたはNode.jsprocessを使用してアクセスできます。 argvプロパティは、Node.jsスクリプトに渡されるコマンドライン引数を含む配列です。 2行目では、そのプロパティをargsという変数に割り当てます。

次に、getHelpText関数を作成して、プログラムの使用方法のマニュアルを表示します。 以下のコードをmycliprogramファイルに追加します。

node-file-streams / mycliprogram

...
const getHelpText = function() {
    const helpText = `
    simplecli is a simple cli program to demonstrate how to handle files using streams.
    usage:
        mycliprogram <command> <path_to_file>

        <command> can be:
        read: Print a file's contents to the terminal
        write: Write a message from the terminal to a file
        copy: Create a copy of a file in the current directory
        reverse: Reverse the content of a file and save its output to another file.

        <path_to_file> is the path to the file you want to work with.
    `;
    console.log(helpText);
}

getHelpText関数は、プログラムのヘルプテキストとして作成した複数行の文字列を出力します。 ヘルプテキストには、プログラムが期待するコマンドライン引数またはパラメータが表示されます。

次に、制御ロジックを追加してargsの長さを確認し、適切な応答を提供します。

node-file-streams / mycliprogram

...
let command = '';

if(args.length < 3) {
    getHelpText();
    return;
}
else if(args.length > 4) {
    console.log('More arguments provided than expected');
    getHelpText();
    return;
}
else {
    command = args[2]
    if(!args[3]) {
        console.log('This tool requires at least one path to a file');
        getHelpText();
        return;
    }
}

上記のコードスニペットでは、端末から受信したコマンドを格納するための空の文字列commandを作成しました。 最初のifブロックは、args配列の長さが3未満かどうかをチェックします。 3未満の場合は、プログラムの実行時に他の追加の引数が渡されなかったことを意味します。 この場合、ヘルプテキストを端末に出力して終了します。

else ifブロックは、args配列の長さが4より大きいかどうかを確認します。 そうである場合、プログラムは必要以上の引数を受け取りました。 プログラムは、ヘルプテキストとともにこの趣旨のメッセージを出力して終了します。

最後に、elseブロックで、3番目の要素または[X106X]変数のargs配列の2番目のインデックスにある要素を格納します。 このコードは、args配列に4番目の要素またはインデックス=3の要素があるかどうかもチェックします。 アイテムが存在しない場合は、続行するにはファイルパスが必要であることを示すメッセージが端末に出力されます。

ファイルを保存します。 次に、アプリケーションを実行します。

./mycliprogram

以下の出力のようなpermission deniedエラーが発生する可能性があります。

Output-bash: ./mycliprogram: Permission denied

このエラーを修正するには、ファイルに実行権限を付与する必要があります。これは、次のコマンドで実行できます。

chmod +x mycliprogram

ファイルを再実行してください。 出力は次のようになります。

Outputsimplecli is a simple cli program to demonstrate how to handle files using streams.
usage:
    mycliprogram <command> <path_to_file>

    read: Print a file's contents to the terminal
    write: Write a message from the terminal to a file
    copy: Create a copy of a file in the current directory
    reverse: Reverse the content of a file and save it output to another file.

最後に、前に作成したcommands配列にコマンドを部分的に実装します。 mycliprogramファイルを開き、以下のコードを追加します。

node-file-streams / mycliprogram

...
switch(commands.indexOf(command)) {
    case 0:
        console.log('command is read');
        break;
    case 1:
        console.log('command is write');
        break;
    case 2:
        console.log('command is copy');
        break;
    case 3:
        console.log('command is reverse');
        break;
    default:
        console.log('You entered a wrong command. See help text below for supported functions');
        getHelpText();
        return;
}

switchステートメントで見つかったコマンドを入力するたびに、プログラムはそのコマンドに適切なcaseブロックを実行します。 この部分的な実装では、コマンドの名前を端末に出力します。 文字列が上記で作成したコマンドのリストにない場合、プログラムはヘルプテキストとともにその旨のメッセージを出力します。 その後、プログラムは終了します。

ファイルを保存し、readコマンドと任意のファイル名を使用してプログラムを再実行します。

./mycliprogram read test.txt

出力は次のようになります。

Outputcommand is read

これで、コマンドラインプログラムが正常に作成されました。 次のセクションでは、createReadStream()を使用して、アプリケーションでcat機能をreadコマンドとして複製します。

ステップ2—createReadStream()でファイルを読み取る

コマンドラインアプリケーションのreadコマンドは、ファイルシステムからファイルを読み取り、Linuxベースの端末のcatコマンドと同様に端末に出力します。 このセクションでは、fsモジュールのcreateReadStream()を使用してその機能を実装します。

createReadStream関数は、EventsEmitterクラスから継承するため、リッスンできるイベントを発行する読み取り可能なストリームを作成します。 dataイベントは、これらのイベントの1つです。 読み取り可能なストリームがデータを読み取るたびに、dataイベントを発行し、データを解放します。 コールバック関数とともに使用すると、そのデータまたはchunkを使用してコールバックが呼び出され、そのコールバック関数内でそのデータを処理できます。 この場合、そのチャンクをターミナルに表示します。

まず、簡単にアクセスできるように、作業ディレクトリにテキストファイルを追加します。 このセクションとそれに続くいくつかのセクションでは、lorem-ipsum.txtというファイルを使用します。 これは、 Lorem Ipsum Generator を使用して生成された約1200行のloremipsumテキストを含むテキストファイルであり、GitHubでホストされます。 ターミナルで、次のコマンドを入力して、ファイルを作業ディレクトリにダウンロードします。

wget https://raw.githubusercontent.com/do-community/node-file-streams/999e66a11cd04bc59843a9c129da759c1c515faf/lorem-ipsum.txt

コマンドラインアプリケーションでcat機能を複製するには、必要なcreateReadStream関数が含まれているため、fsモジュールをインポートする必要があります。 これを行うには、mycliprogramファイルを開き、shebangの直後に次の行を追加します。

node-file-streams / mycliprogram

#!/usr/bin/env node

const fs = require('fs');

次に、switchステートメントの下にread()という関数を作成します。この関数は、読み取りたいファイルのファイルパスという1つのパラメーターを使用して作成します。 この関数は、そのファイルから読み取り可能なストリームを作成し、そのストリームでdataイベントをリッスンします。

node-file-streams / mycliprogram

...
function read(filePath) {
    const readableStream = fs.createReadStream(filePath);

    readableStream.on('error', function (error) {
        console.log(`error: ${error.message}`);
    })

    readableStream.on('data', (chunk) => {
        console.log(chunk);
    })
}

このコードは、errorイベントをリッスンすることによってエラーもチェックします。 エラーが発生すると、エラーメッセージが端末に出力されます。

最後に、以下のコードブロックに示すように、最初のケースブロックcase 0で、console.log()read()関数に置き換える必要があります。

node-file-streams / mycliprogram

...
switch (command){
    case 0:
        read(args[3]);
        break;
    ...
}

ファイルを保存して新しい変更を保持し、プログラムを実行します。

./mycliprogram read lorem-ipsum.txt

出力は次のようになります。

Output<Buffer 0a 0a 4c 6f 72 65 6d 20 69 70 73 75 6d 20 64 6f 6c 6f 72 20 73 69 74 20 61 6d 65 74 2c 20 63 6f 6e 73 65 63 74 65 74 75 72 20 61 64 69 70 69 73 63 69 ... >
...
<Buffer 76 69 74 61 65 20 61 6e 74 65 20 66 61 63 69 6c 69 73 69 73 20 6d 61 78 69 6d 75 73 20 75 74 20 69 64 20 73 61 70 69 65 6e 2e 20 50 65 6c 6c 65 6e 74 ... >

上記の出力に基づいて、データがチャンクまたはピースで読み取られ、これらのデータがBufferタイプであることがわかります。 簡潔にするために、上記の端末出力には2つのチャンクのみが表示され、省略記号は、ここに表示されているチャンクの間にいくつかのバッファーがあることを示しています。 ファイルが大きいほど、バッファーまたはチャンクの数が多くなります。

人間が読める形式でデータを返すには、createReadStream()関数に2番目の引数として必要なエンコードタイプの文字列値を渡して、データのエンコードタイプを設定します。 createReadStream()関数の2番目の引数に、次の強調表示されたコードを追加して、エンコードタイプをutf8に設定します。

node-file-streams / mycliprogram

...
const readableStream = fs.createReadStream(filePath, 'utf8')
...

プログラムを再実行すると、ターミナルにファイルの内容が表示されます。 プログラムは、lorem-ipsum.txtファイルのloremipsumテキストを、ファイルに表示されているとおりに1行ずつ出力します。

OutputLorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean est tortor, eleifend et enim vitae, mattis condimentum elit. In dictum ex turpis, ac rutrum libero tempus sed...

...

...Quisque nisi diam, viverra vel aliquam nec, aliquet ut nisi. Nullam convallis dictum nisi quis hendrerit. Maecenas venenatis lorem id faucibus venenatis. Suspendisse sodales, tortor ut condimentum fringilla, turpis erat venenatis justo, lobortis egestas massa massa sed magna. Phasellus in enim vel ante viverra ultricies.

上記の出力は、端末に印刷されたファイルの内容のごく一部を示しています。 端末の出力をlorem-ipsum.txtファイルと比較すると、catコマンドの場合と同様に、内容がファイルと同じで、同じ形式であることがわかります。

このセクションでは、コマンドラインプログラムにcat機能を実装して、ファイルの内容を読み取り、createReadStream関数を使用して端末に出力しました。 次のステップでは、createWriteStream()を使用して端末からの入力に基づいてファイルを作成します。

ステップ3—createWriteStream()を使用してファイルに書き込む

このセクションでは、createWriteStream()を使用して端末からファイルに入力を書き込みます。 createWriteStream関数は、データを書き込むことができる書き込み可能なファイルストリームを返します。 前の手順の読み取り可能なストリームと同様に、この書き込み可能なストリームは、errorfinishpipeなどの一連のイベントを発行します。 さらに、データをチャンクまたはビットでストリームに書き込むためのwrite関数を提供します。 write関数は、chunkを取り込みます。これは、文字列、Buffer<Uint8Array>、またはその他のJavaScript値です。 チャンクが文字列の場合は、エンコーディングタイプを指定することもできます。

端末からファイルに入力を書き込むには、コマンドラインプログラムでwriteという関数を作成します。 この関数では、端末から入力を受け取り(ユーザーが入力を終了するまで)、データをファイルに書き込むプロンプトを作成します。

まず、mycliprogramファイルの先頭にあるreadlineモジュールをインポートする必要があります。 readlineモジュールはネイティブのNode.jsモジュールであり、標準入力(stdin)や端末などの読み取り可能なストリームから一度に1行ずつデータを受信するために使用できます。 mycliprogramファイルを開き、強調表示された行を追加します。

node-file-streams / mycliprogram

#!/usr/bin/env node

const fs = require('fs');
const readline = require('readline');

次に、read()関数の下に次のコードを追加します。

node-file-streams / mycliprogram

...
function write(filePath) {
    const writableStream = fs.createWriteStream(filePath);

    writableStream.on('error',  (error) => {
        console.log(`An error occured while writing to the file. Error: ${error.message}`);
    });
}

ここでは、filePathパラメーターを使用して書き込み可能なストリームを作成しています。 このファイルパスは、writeワードの後のコマンドライン引数になります。 また、問題が発生した場合(たとえば、存在しないfilePathを指定した場合)にエラーイベントをリッスンします。

次に、端末からメッセージを受信するプロンプトを作成し、前にインポートしたreadlineモジュールを使用して、指定されたfilePathにメッセージを書き込みます。 リードラインインターフェイスとプロンプトを作成し、lineイベントをリッスンするには、ブロックに示すようにwrite関数を更新します。

node-file-streams / mycliprogram

...
function write(filePath) {
    const writableStream = fs.createWriteStream(filePath);

    writableStream.on('error',  (error) => {
        console.log(`An error occured while writing to the file. Error: ${error.message}`);
    });

    const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout,
        prompt: 'Enter a sentence: '
    });

    rl.prompt();
      
    rl.on('line', (line) => {
        switch (line.trim()) {
            case 'exit':
                rl.close();
                break;
            default:
                sentence = line + '\n'
                writableStream.write(sentence);
                rl.prompt();
                break;
        }
    }).on('close', () => {
        writableStream.end();
        writableStream.on('finish', () => {
            console.log(`All your sentences have been written to ${filePath}`);
        })
        setTimeout(() => {
            process.exit(0);
        }, 100);
    });
}

プログラムが端末から標準入力(stdin)を行ごとに読み取り、指定されたpromptを書き込むことができるreadlineインターフェース(rl)を作成しました。 ]文字列から標準出力(stdout)。 また、prompt()関数を呼び出して、構成されたpromptメッセージを新しい行に書き込み、ユーザーが追加の入力を提供できるようにしました。

次に、rlインターフェイスで2つのイベントリスナーをチェーンしました。 1つ目は、入力ストリームが行末入力を受信するたびに発行されるlineイベントをリッスンします。 この入力は、改行文字(\n)、復帰文字(\r)、または両方の文字を合わせたもの(\r\n)であり、通常はを押したときに発生します。コンピュータのENTERまたはリターンキー。 したがって、端末に入力しているときにこれらのキーのいずれかを押すと、lineイベントが発生します。 コールバック関数は、入力lineの1行を含む文字列を受け取ります。

行をトリミングして、それがexitという単語であるかどうかを確認しました。 そうでない場合、プログラムはlineに改行文字を追加し、.write()関数を使用してsentencefilePathに書き込みます。 次に、プロンプト関数を呼び出して、ユーザーに別のテキスト行を入力するように促しました。 lineexitの場合、プログラムはrlインターフェースでclose関数を呼び出します。 close関数は、rlインスタンスを閉じ、標準の入力(stdin)および出力(stdout)ストリームを解放します。

この関数は、rlインスタンスでリッスンした2番目のイベントcloseイベントに移動します。 このイベントは、rl.close()を呼び出すと発生します。 ストリームにデータを書き込んだ後、ストリームでend関数を呼び出して、書き込み可能なストリームにデータを書き込まないようにプログラムに指示する必要があります。 これを行うと、データが出力ファイルに完全にフラッシュされます。 したがって、exitという単語を入力すると、rlインスタンスを閉じ、end関数を呼び出して書き込み可能なストリームを停止します。

プログラムが端末から指定されたfilePathにすべてのテキストを正常に書き込んだことをユーザーにフィードバックするために、writableStreamfinishイベントをリッスンしました。 コールバック関数では、書き込みが完了したときにユーザーに通知するメッセージを端末に記録しました。 最後に、finishイベントがフィードバックを提供するのに十分な時間を提供するために、100ミリ秒後にプロセスを終了しました。

最後に、mycliprogramでこの関数を呼び出すには、switchステートメントのcase 1ブロックのconsole.logステートメントを新しいwriteに置き換えます。 ]関数、ここに示すように:

node-file-streams / mycliprogram

...
switch (command){
    ...

    case 1:
        write(args[3]);
        break;

    ...
}

新しい変更を含むファイルを保存します。 次に、writeコマンドを使用して、ターミナルでコマンドラインアプリケーションを実行します。

./mycliprogram write output.txt

Enter a sentenceプロンプトで、必要な入力を追加します。 いくつか入力した後、exitと入力します。

出力は次のようになります(強調表示された行の代わりに入力が表示されます)。

OutputEnter a sentence: Twinkle, twinkle, little star
Enter a sentence: How I wonder what you are
Enter a sentence: Up above the hills so high
Enter a sentence: Like a diamond in the sky
Enter a sentence: exit
All your sentences have been written to output.txt

output.txtをチェックして、前に作成したreadコマンドを使用してファイルの内容を確認します。

./mycliprogram read output.txt

ターミナル出力には、exitを除くコマンドに入力したすべてのテキストが含まれている必要があります。 上記の入力に基づいて、output.txtファイルには次の内容が含まれています。

OutputTwinkle, twinkle, little star
How I wonder what you are
Up above the hills so high
Like a diamond in the sky

このステップでは、ストリームを使用してファイルに書き込みました。 次に、コマンドラインプログラムでファイルをコピーする関数を実装します。

ステップ4—pipe()を使用してファイルをコピーする

このステップでは、pipe関数を使用して、ストリームを使用してファイルのコピーを作成します。 ストリームを使用してファイルをコピーする方法は他にもありますが、データフローを管理する必要がないため、pipeを使用することをお勧めします。

たとえば、ストリームを使用してファイルをコピーする1つの方法は、ファイルの読み取り可能なストリームを作成し、dataイベントでストリームをリッスンし、ストリームイベントから各chunkをに書き込むことです。ファイルコピーの書き込み可能なストリーム。 以下のスニペットは例を示しています。

example.js

const fs = require('fs');
const readableStream = fs.createReadStream('lorem-ipsum.txt', 'utf8');
const writableStream = fs.createWriteStream('lorem-ipsum-copy.txt');

readableStream.on('data', () => {
    writableStream.write(chunk);
});

writableStream.end();

この方法の欠点は、読み取り可能ストリームと書き込み可能ストリームの両方でイベントを管理する必要があることです。

ストリームを使用してファイルをコピーするための推奨される方法は、pipeを使用することです。 配管パイプは、水タンク(出力)などの水源から蛇口または蛇口(入力)に水を渡します。 同様に、pipeを使用して、データを出力ストリームから入力ストリームに転送します。 (Linuxベースのbashシェルに精通している場合は、pipe |コマンドがデータを1つのストリームから別のストリームに転送します。)

Node.jsのパイピングは、最初の方法を使用する場合のようにデータフローを管理することなく、ソースからデータを読み取り、別の場所に書き込む機能を提供します。 前のアプローチとは異なり、読み取り可能ストリームと書き込み可能ストリームの両方でイベントを管理する必要はありません。 このため、ストリームを使用するコマンドラインアプリケーションにコピーコマンドを実装するための推奨されるアプローチです。

mycliprogramファイルに、ユーザーがcopyコマンドライン引数を使用してプログラムを実行したときに呼び出される新しい関数を追加します。 copyメソッドは、pipe()を使用して、入力ファイルからファイルの宛先コピーにコピーします。 以下に示すように、write関数の後にcopy関数を作成します。

node-file-streams / mycliprogram

...
function copy(filePath) {
    const inputStream = fs.createReadStream(filePath)
    const fileCopyPath = filePath.split('.')[0] + '-copy.' + filePath.split('.')[1]
    const outputStream = fs.createWriteStream(fileCopyPath)

    inputStream.pipe(outputStream)
    
    outputStream.on('finish', () => {
        console.log(`You have successfully created a ${filePath} copy. The new file name is ${fileCopyPath}.`);
    })
}

コピー機能では、fs.createReadStream()を使用して入力または読み取り可能なストリームを作成しました。 また、宛先の新しい名前を生成し、ファイルのコピーを出力し、fs.createWriteStream()を使用して出力または書き込み可能なストリームを作成しました。 次に、.pipe()を使用して、inputStreamからoutputStreamにデータをパイプ処理しました。 最後に、finishイベントをリッスンし、ファイルのコピーが成功したときにメッセージを出力しました。

書き込み可能なストリームを閉じるには、ストリームでend()関数を呼び出す必要があることを思い出してください。 ストリームをパイピングする場合、読み取り可能なストリーム(inputStream)がendイベントを発行すると、書き込み可能なストリーム(outputStream)でend()関数が呼び出されます。 書き込み可能ストリームのend()関数は、finishイベントを発行し、このイベントをリッスンして、ファイルのコピーが終了したことを示します。

この関数の動作を確認するには、mycliprogramファイルを開き、switchステートメントのcase 2ブロックを次のように更新します。

node-file-streams / mycliprogram

...
switch (command){
    ...

    case 2:
        copy(args[3]);
        break;
    
    ...
}

switchステートメントのcase 2ブロックでcopy関数を呼び出すと、mycliprogramプログラムをcopyコマンドと必要なファイルパスがあれば、copy関数が実行されます。

mycliprogramを実行します:

./mycliprogram copy lorem-ipsum.txt

出力は次のようになります。

OutputYou have successfully created a lorem-ipsum-copy.txt copy. The new file name is lorem-ipsum-copy.txt.

node-file-streamsフォルダー内に、lorem-ipsum-copy.txtという名前の新しく追加されたファイルが表示されます。

pipeを使用して、コマンドラインプログラムにコピー機能を正常に追加しました。 次のステップでは、ストリームを使用してファイルのコンテンツを変更します。

ステップ5—Transform()を使用してファイルの内容を元に戻す

このチュートリアルの前の3つのステップでは、fsモジュールを使用してストリームを操作しました。 このセクションでは、変換ストリームを提供するネイティブstreamモジュールのTransform()クラスを使用してファイルストリームを変更します。 変換ストリームを使用して、データの読み取り、データの操作、および出力としての新しいデータの提供を行うことができます。 したがって、出力は入力データの「変換」です。 変換ストリームを使用するNode.jsモジュールには、暗号化用のcryptoモジュールと、ファイルの圧縮および解凍用のgzipを備えたzlibモジュールが含まれます。

Transform()抽象クラスを使用してカスタム変換ストリームを実装します。 作成する変換ストリームは、ファイルの内容を1行ずつ反転します。これは、変換ストリームを使用してファイルの内容を必要に応じて変更する方法を示しています。

mycliprogramファイルに、ユーザーがreverseコマンドライン引数を渡したときにプログラムが呼び出すreverse関数を追加します。

まず、ファイルの先頭にあるTransform()クラスを他のインポートの下にインポートする必要があります。 以下に示すように、強調表示された行を追加します。

mycliprogram

#!/usr/bin/env node
...
const stream = require('stream');
const Transform = stream.Transform || require('readable-stream').Transform;

v0.10より前のNode.jsバージョンでは、Transform抽象クラスがありません。 したがって、上記のコードブロックにはreadable-streamsポリフィルが含まれているため、このプログラムは以前のバージョンのNode.jsで動作します。 Node.jsのバージョンが> 0.10の場合、プログラムは抽象クラスを使用し、そうでない場合はポリフィルを使用します。

注:Node.jsバージョン< 0.10を使用している場合は、npm init -yを実行してpackage.jsonファイルを作成し、 npm install readable-streamを、ポリフィルを適用するための作業ディレクトリに移動します。


次に、copy関数のすぐ下にreverse関数を作成します。 この関数では、filePathパラメーターを使用して読み取り可能なストリームを作成し、反転ファイルの名前を生成し、その名前を使用して書き込み可能なストリームを作成します。 次に、Transform()クラスのインスタンスであるreverseStreamを作成します。 Transform()クラスを呼び出すときは、1つの関数を含むオブジェクトを渡します。 この重要な機能はtransform機能です。

copy関数の下に、以下のコードブロックを追加して、reverse関数を追加します。

node-file-streams / mycliprogram

...
function reverse(filePath) {
    const readStream = fs.createReadStream(filePath);
    const reversedDataFilePath = filePath.split('.')[0] + '-reversed.'+ filePath.split('.')[1];
    const writeStream = fs.createWriteStream(reversedDataFilePath);

    const reverseStream = new Transform({
        transform (data, encoding, callback) {
            const reversedData = data.toString().split("").reverse().join("");
            this.push(reversedData);
            callback();
        }
    });

    readStream.pipe(reverseStream).pipe(writeStream).on('finish', () => {
        console.log(`Finished reversing the contents of ${filePath} and saving the output to ${reversedDataFilePath}.`);
    });
}

transform関数は、dataencodingタイプ、およびcallback関数の3つのパラメーターを受け取ります。 この関数内で、データを文字列に変換し、文字列を分割し、結果の配列の内容を逆にして、それらを結合し直しました。 このプロセスは、データを順方向ではなく逆方向に書き換えます。

次に、2つのpipe()関数を使用して、readStreamreverseStreamに接続し、最後にwriteStreamに接続しました。 最後に、finishイベントをリッスンして、ファイルの内容が完全に逆になったことをユーザーに警告しました。

上記のコードは、finishイベントをリッスンするために別の構文を使用していることに気付くでしょう。 新しい行でwriteStreamfinishイベントをリッスンする代わりに、on関数を2番目のpipe関数にチェーンしました。 一部のイベントリスナーをストリームにチェーンできます。 この場合、これを行うと、writeStreamon('finish')関数を呼び出すのと同じ効果があります。

まとめると、switchステートメントのcase 3ブロックのconsole.logステートメントをreverse()に置き換えます。

node-file-streams / mycliprogram

...
switch (command){
    ...

    case 3:
        reverse(args[3]);
        break;
    
    ...
}

この機能をテストするには、国の名前をアルファベット順に含む別のファイル( countrys.csv )を使用します。 以下のコマンドを実行して、作業ディレクトリにダウンロードできます。

wget https://raw.githubusercontent.com/do-community/node-file-streams/999e66a11cd04bc59843a9c129da759c1c515faf/countries.csv

その後、mycliprogramを実行できます。

./mycliprogram reverse countries.csv

出力は次のようになります。

OutputFinished reversing the contents of countries.csv and saving the output to countries-reversed.csv.

countries-reversed.csvcountries.csvの内容を比較して、変換を確認します。 それぞれの名前が逆に書かれ、名前の順序も逆になっています(「アフガニスタン」は「natsinahgfA」と書かれて最後に表示され、「ジンバブエ」は「ewbabmiZ」と書かれて最初に表示されます)。

これで、カスタム変換ストリームが正常に作成されました。 また、ファイル処理にストリームを使用する関数を使用してコマンドラインプログラムを作成しました。

結論

ストリームは、ネイティブNode.jsモジュール、およびデータを効率的に処理する方法を提供するため、入出力操作を実行するさまざまなyarnおよびnpmパッケージで使用されます。 この記事では、さまざまなストリームベースの関数を使用してNode.jsのファイルを操作しました。 readwritecopy、およびreverseコマンドを使用してコマンドラインプログラムを作成しました。 次に、これらの各コマンドを、それに応じて名前が付けられた関数に実装しました。 関数を実装するには、fsモジュールのcreateReadStreamcreateWriteStreampipecreateInterface関数などの関数を使用しました。 X145X] モジュール、そして最後に抽象Transform()クラス。 最後に、これらの関数を小さなコマンドラインプログラムにまとめました。

次のステップとして、作成したコマンドラインプログラムを拡張して、ローカルで使用する可能性のある他のファイルシステム機能を含めることができます。 良い例は、データを.tsvストリームソースから.csvに変換するパーソナルツールを作成することや、この記事で使用したwgetコマンドを複製してGitHubからファイルをダウンロードすることです。 。

作成したコマンドラインプログラムは、コマンドライン引数自体を処理し、単純なプロンプトを使用してユーザー入力を取得します。 Node.jsスクリプトでコマンドライン引数を処理する方法およびInquirer.jsを使用してインタラクティブなコマンドラインプロンプトを作成する方法を参照すると、より堅牢で保守しやすいコマンドラインアプリケーションの構築について詳しく知ることができます。

さらに、Node.jsは、ユースケースに必要となる可能性のあるさまざまな Node.jsストリームモジュールクラス、メソッド、およびイベントに関する広範なドキュメントを提供します。