Node.jsでZipファイルを操作する方法
著者は、 Write for DOnations プログラムの一環として、 Open Sourcing MentalIllnessを選択して寄付を受け取りました。
序章
ファイルの操作は、開発者の間で一般的なタスクの1つです。 ファイルのサイズが大きくなると、ハードドライブにかなりのスペースが必要になります。 遅かれ早かれ、ファイルを他のサーバーに転送したり、ローカルマシンから別のプラットフォームに複数のファイルをアップロードしたりする必要があるかもしれません。 これらのプラットフォームの一部にはファイルサイズの制限があり、大きなファイルを受け入れません。 これを回避するために、ファイルを1つのZIPファイルにグループ化できます。 ZIPファイルは、可逆圧縮アルゴリズムを使用してファイルをパックおよび圧縮するアーカイブ形式です。 アルゴリズムは、データを失うことなくデータを再構築できます。 Node.js では、adm-zipモジュールを使用してZIPアーカイブを作成および読み取ることができます。
このチュートリアルでは、adm-zipモジュールを使用して、ファイルを圧縮、読み取り、および解凍します。 まず、adm-zipを使用して複数のファイルをZIPアーカイブに結合します。 次に、ZIPアーカイブの内容を一覧表示します。 その後、既存のZIPアーカイブにファイルを追加し、最後に、ZIPアーカイブをディレクトリに抽出します。
前提条件
このチュートリアルに従うには、次のものが必要です。
-
Node.jsがローカル環境またはサーバー環境にインストールされています。 Node.jsのインストール方法とローカル開発環境の作成に従って、Node.jsをインストールします。
-
Node.jsプログラムの作成方法に関する知識については、Node.jsで最初のプログラムを作成して実行する方法を参照してください。
-
JavaScriptでの非同期プログラミングの基本的な理解。 基本については、チュートリアル JavaScript でのイベントループ、コールバック、プロミス、非同期/待機について理解してください。
-
Node.jsのファイルを操作する方法に関する知識。 チュートリアルNode.jsのfsモジュールを使用してファイルを操作する方法を参照して、ファイルの操作を確認してください。
ステップ1—プロジェクトの設定
このステップでは、プロジェクトのディレクトリを作成し、依存関係としてadm-zipをインストールします。 このディレクトリは、プログラムファイルを保存する場所です。 また、テキストファイルと画像を含む別のディレクトリを作成します。 このディレクトリは次のセクションでアーカイブします。
次のコマンドを使用して、zip_appというディレクトリを作成します。
- mkdir zip_app
cdコマンドを使用して、新しく作成されたディレクトリに移動します。
- cd zip_app
ディレクトリ内に、プロジェクトの依存関係を管理するためのpackage.jsonファイルを作成します。
- npm init -y
-yオプションは、デフォルトのpackage.jsonファイルを作成します。
次に、npm installコマンドを使用してadm-zipをインストールします。
- npm install adm-zip
コマンドを実行すると、npmはadm-zipをインストールし、package.jsonファイルを更新します。
次に、testというディレクトリを作成し、そのディレクトリに移動します。
- mkdir test && cd test
このディレクトリに、3つのテキストファイルを作成し、画像をダウンロードします。 3つのファイルは、ファイルサイズを大きくするためにダミーコンテンツで埋められます。 これは、このディレクトリをアーカイブするときにZIP圧縮を示すのに役立ちます。
file1.txtを作成し、次のコマンドを使用してダミーコンテンツを入力します。
- yes "dummy content" | head -n 100000 > file1.txt
yesコマンドは、文字列dummy contentを繰り返しログに記録します。 パイプコマンド|を使用して、yesコマンドからの出力を送信し、headコマンドの入力として使用します。 headコマンドは、指定された入力の一部を標準出力に出力します。 -nオプションは、標準出力に書き込む必要のある行数を指定します。 最後に、>を使用して、head出力を新しいファイルfile1.txtにリダイレクトします。
「dummycontent」という文字列を300,000行繰り返して2番目のファイルを作成します。
- yes "dummy content" | head -n 300000 > file2.txt
dummy content文字列を600,000行繰り返して別のファイルを作成します。
- yes "dummy content" | head -n 600000 > file3.txt
最後に、curlを使用してディレクトリにイメージをダウンロードします。
- curl -O https://assets.digitalocean.com/how-to-process-images-in-node-js-with-sharp/underwater.png
次のコマンドを使用して、メインプロジェクトディレクトリに戻ります。
- cd ..
..は、親ディレクトリであるzip_appに移動します。
これで、プロジェクトディレクトリが作成され、adm-zipがインストールされ、アーカイブ用のファイルを含むディレクトリが作成されました。 次のステップでは、adm-zipモジュールを使用してディレクトリをアーカイブします。
ステップ2—ZIPアーカイブを作成する
このステップでは、adm-zipを使用して、前のセクションで作成したディレクトリを圧縮およびアーカイブします。
ディレクトリをアーカイブするには、adm-zipモジュールをインポートし、モジュールのaddLocalFolder()メソッドを使用して、ディレクトリをadm-zipモジュールのZIPオブジェクトに追加します。 その後、モジュールのwriteZip()メソッドを使用して、アーカイブをローカルシステムに保存します。
新しいファイルcreateArchive.jsを作成し、お好みのテキストエディタで開きます。 このチュートリアルでは、コマンドラインテキストエディタであるnanoを使用します。
- nano createArchive.js
次に、createArchive.jsファイルのadm-zipモジュールで次のように要求します。
const AdmZip = require("adm-zip");
adm-zipモジュールは、ZIPアーカイブを作成するためのメソッドを含むクラスを提供します。
アーカイブプロセス中に大きなファイルが発生することがよくあるため、ZIPアーカイブが保存されるまでメインスレッドをブロックしてしまう可能性があります。 非ブロッキングコードを作成するには、ZIPアーカイブを作成して保存する非同期関数を定義します。
createArchive.jsファイルに、次の強調表示されたコードを追加します。
const AdmZip = require("adm-zip");
async function createZipArchive() {
const zip = new AdmZip();
const outputFile = "test.zip";
zip.addLocalFolder("./test");
zip.writeZip(outputFile);
console.log(`Created ${outputFile} successfully`);
}
createZipArchive();
createZipArchiveは、指定されたディレクトリからZIPアーカイブを作成する非同期関数です。 非同期にするのは、関数ラベルの前に定義したasyncキーワードです。 関数内で、adm-zipモジュールのインスタンスを作成します。これは、アーカイブの読み取りと作成に使用できるメソッドを提供します。 インスタンスを作成すると、adm-zipは、ファイルまたはディレクトリを追加できるメモリ内のZIPを作成します。
次に、アーカイブ名を定義し、outputDir変数に保存します。 testディレクトリをメモリ内アーカイブに追加するには、ディレクトリパスを引数としてadm-zipからaddLocalFolder()メソッドを呼び出します。
ディレクトリを追加したら、ZIPアーカイブの名前を含む変数を使用してadm-zipからwriteZip()メソッドを呼び出します。 writeZip()メソッドは、アーカイブをローカルディスクに保存します。
それが完了したら、console.log()を呼び出して、ZIPファイルが正常に作成されたことをログに記録します。
最後に、createZipArchive()関数を呼び出します。
ファイルを実行する前に、コードをtry…catchブロックでラップして、ランタイムエラーを処理します。
const AdmZip = require("adm-zip");
async function createZipArchive() {
try {
const zip = new AdmZip();
const outputFile = "test.zip";
zip.addLocalFolder("./test");
zip.writeZip(outputFile);
console.log(`Created ${outputFile} successfully`);
} catch (e) {
console.log(`Something went wrong. ${e}`);
}
}
createZipArchive();
tryブロック内で、コードはZIPアーカイブの作成を試みます。 成功すると、createZipArchive()関数は終了し、catchブロックをスキップします。 ZIPアーカイブを作成するとエラーが発生した場合、実行はcatchブロックにスキップし、コンソールにエラーを記録します。
nanoのファイルをCTRL+Xで保存して終了します。 yと入力して変更を保存し、Windowsの場合はENTER、Macの場合はRETURNキーを押してファイルを確認します。
nodeコマンドを使用して、createArchive.jsファイルを実行します。
- node createArchive.js
次の出力が表示されます。
OutputCreated test.zip successfully
ディレクトリの内容を一覧表示して、ZIPアーカイブが作成されているかどうかを確認します。
- ls
コンテンツの中のアーカイブを示す次の出力が表示されます。
OutputcreateArchive.js node_modules package-lock.json
package.json test test.zip
ZIPアーカイブが作成されたことを確認したら、ZIPアーカイブとtestディレクトリファイルサイズを比較して、圧縮が機能するかどうかを確認します。
duコマンドを使用して、testディレクトリサイズを確認します。
- du -h test
-hフラグは、duにディレクトリサイズを人間が読める形式で表示するように指示します。
コマンドを実行すると、次の出力が表示されます。
Output15M test
次に、test.zipアーカイブファイルのサイズを確認します。
- du -h test.zip
duコマンドは、次の出力をログに記録します。
Output760K test.zip
ご覧のとおり、ZIPファイルを作成すると、ディレクトリサイズが15メガバイト(MB)から760キロバイト(KB)に減少しました。これは、大きな違いです。 ZIPファイルはよりポータブルでサイズが小さくなっています。
これでZIPアーカイブを作成したので、ZIPファイルにコンテンツを一覧表示する準備が整いました。
ステップ3—ZIPアーカイブにファイルを一覧表示する
この手順では、adm-zipを使用して、ZIPアーカイブ内のすべてのファイルを読み取って一覧表示します。 これを行うには、ZIPアーカイブパスを使用してadm-zipモジュールをインスタンス化します。 次に、オブジェクトの配列を返すモジュールのgetEntries()メソッドを呼び出します。 各オブジェクトは、ZIPアーカイブ内のアイテムに関する重要な情報を保持しています。 ファイルを一覧表示するには、配列を繰り返し処理し、オブジェクトからファイル名にアクセスして、コンソールに記録します。
お気に入りのテキストエディタでreadArchive.jsを作成して開きます。
- nano readArchive.js
readArchive.jsに次のコードを追加して、ZIPアーカイブのコンテンツを読み取って一覧表示します。
const AdmZip = require("adm-zip");
async function readZipArchive(filepath) {
try {
const zip = new AdmZip(filepath);
for (const zipEntry of zip.getEntries()) {
console.log(zipEntry.toString());
}
} catch (e) {
console.log(`Something went wrong. ${e}`);
}
}
readZipArchive("./test.zip");
まず、adm-zipモジュールで必要です。
次に、非同期関数であるreadZipArchive()関数を定義します。 関数内で、読み取りたいZIPファイルのパスを使用してadm-zipのインスタンスを作成します。 ファイルパスは、filepathパラメーターによって提供されます。 adm-zipはファイルを読み取り、解析します。
アーカイブを読み取った後、adm-zipのgetEntries()メソッドが呼び出されたときに返す配列内のオブジェクトを反復処理するfor ….ofステートメントを定義します。 各反復で、オブジェクトはzipEntry変数に割り当てられます。 ループ内で、Node.js toString()メソッドを使用してオブジェクトを表す文字列に変換し、console.log()メソッドを使用してコンソールにログインします。
最後に、ZIPアーカイブファイルのパスを引数としてreadZipArchive()関数を呼び出します。
ファイルを保存して終了し、次のコマンドでファイルを実行します。
- node readArchive.js
次のような出力が得られます(簡潔にするために編集)。
Output{
"entryName": "file1.txt",
"name": "file1.txt",
"comment": "",
"isDirectory": false,
"header": {
...
},
"compressedData": "<27547 bytes buffer>",
"data": "<null>"
}
...
コンソールは4つのオブジェクトをログに記録します。 他のオブジェクトは、チュートリアルを簡潔にするために編集されています。
アーカイブ内の各ファイルは、前の出力と同様のオブジェクトで表されます。 各ファイルのファイル名を取得するには、nameプロパティにアクセスする必要があります。
readArchive.jsファイルに、次の強調表示されたコードを追加して、各ファイル名にアクセスします。
const AdmZip = require("adm-zip");
async function readZipArchive(filepath) {
try {
const zip = new AdmZip(filepath);
for (const zipEntry of zip.getEntries()) {
console.log(zipEntry.name);
}
} catch (e) {
console.log(`Something went wrong. ${e}`);
}
}
readZipArchive("./test.zip");
テキストエディタを保存して終了します。 次に、nodeコマンドを使用してファイルを再実行します。
- node readArchive.js
ファイルを実行すると、次の出力が得られます。
Outputfile1.txt
file2.txt
file3.txt
underwater.png
出力は、ZIPアーカイブ内の各ファイルのファイル名をログに記録するようになりました。
これで、ZIPアーカイブ内の各ファイルを読み取って一覧表示できます。 次のセクションでは、既存のZIPアーカイブにファイルを追加します。
ステップ4—既存のアーカイブにファイルを追加する
この手順では、ファイルを作成し、解凍せずに前に作成したZIPアーカイブに追加します。 まず、adm-zipインスタンスを作成してZIPアーカイブを読み取ります。 次に、モジュールのaddFile()メソッドを呼び出して、ファイルをZIPに追加します。 最後に、ZIPアーカイブをローカルシステムに保存します。
600,000行繰り返されるダミーコンテンツを含む別のファイルfile4.txtを作成します。
- yes "dummy content" | head -n 600000 > file4.txt
テキストエディタでupdateArchive.jsを作成して開きます。
- nano updateArchive.js
adm-zipモジュールとfsモジュールで、updateArchive.jsファイル内のファイルを操作できるようにする必要があります。
const AdmZip = require("adm-zip");
const fs = require("fs").promises;
promiseベースのバージョンのfsモジュールバージョンが必要です。これにより、非同期コードを記述できます。 fsメソッドを呼び出すと、promiseが返されます。
次に、updateArchive.jsファイルに、次の強調表示されたコードを追加して、ZIPアーカイブに新しいファイルを追加します。
const AdmZip = require("adm-zip");
const fs = require("fs").promises;
async function updateZipArchive(filepath) {
try {
const zip = new AdmZip(filepath);
content = await fs.readFile("./file4.txt");
zip.addFile("file4.txt", content);
zip.writeZip(filepath);
console.log(`Updated ${filepath} successfully`);
} catch (e) {
console.log(`Something went wrong. ${e}`);
}
}
updateZipArchive("./test.zip");
updateZipArchiveは、ファイルシステム内のファイルを読み取り、それを既存のZIPに追加する非同期関数です。 この関数では、filepathのZIPアーカイブファイルパスをパラメーターとしてadm-zipのインスタンスを作成します。 次に、fsモジュールのreadFile()メソッドを呼び出して、ファイルシステム内のファイルを読み取ります。 readFile()メソッドはpromiseを返します。これは、awaitキーワードで解決します(awaitは非同期関数でのみ有効です)。 解決されると、メソッドはファイルの内容を含むbufferオブジェクトを返します。
次に、adm-zipからaddFile()メソッドを呼び出します。 このメソッドは2つの引数を取ります。 最初の引数はアーカイブに追加するファイル名であり、2番目の引数はreadFile()メソッドが読み取るファイルの内容を含むバッファーオブジェクトです。
その後、adm-zipモジュールのwriteZip()メソッドを呼び出して、ZIPアーカイブに新しい変更を保存および書き込みます。 それが完了したら、console.log()メソッドを呼び出して成功メッセージをログに記録します。
最後に、Zipアーカイブファイルのパスを引数としてupdateZipArchive()関数を呼び出します。
ファイルを保存して終了します。 次のコマンドを使用して、updateArchive.jsファイルを実行します。
- node updateArchive.js
次のような出力が表示されます。
OutputUpdated ./test.zip successfully
ここで、ZIPアーカイブに新しいファイルが含まれていることを確認します。 次のコマンドを使用して、readArchive.jsファイルを実行し、ZIPアーカイブの内容を一覧表示します。
- node readArchive.js
次の出力が表示されます。
file1.txt
file2.txt
file3.txt
file4.txt
underwater.png
これにより、ファイルがZIPに追加されたことを確認できます。
既存のアーカイブにファイルを追加できるようになったので、次のセクションでアーカイブを抽出します。
ステップ5—Zipアーカイブを抽出する
このステップでは、ZIPアーカイブ内のすべてのコンテンツを読み取ってディレクトリに抽出します。 ZIPアーカイブを抽出するには、アーカイブファイルのパスを使用してadm-zipをインスタンス化します。 その後、抽出したZIPコンテンツを保存するディレクトリ名を使用して、モジュールのextractAllTo()メソッドを呼び出します。
テキストエディタでextractArchive.jsを作成して開きます。
- nano extractArchive.js
extractArchive.jsファイルのadm-zipモジュールとpathモジュールに必要です。
const AdmZip = require("adm-zip");
const path = require("path");
pathモジュールは、ファイルパスを処理するための便利なメソッドを提供します。
extractArchive.jsファイルに、次の強調表示されたコードを追加してアーカイブを抽出します。
const AdmZip = require("adm-zip");
const path = require("path");
async function extractArchive(filepath) {
try {
const zip = new AdmZip(filepath);
const outputDir = `${path.parse(filepath).name}_extracted`;
zip.extractAllTo(outputDir);
console.log(`Extracted to "${outputDir}" successfully`);
} catch (e) {
console.log(`Something went wrong. ${e}`);
}
}
extractArchive("./test.zip");
extractArchive()は、ZIPアーカイブのファイルパスを含むパラメーターを受け取る非同期関数です。 関数内で、filepathパラメーターで指定されたZIPアーカイブファイルパスを使用してadm-zipをインスタンス化します。
次に、テンプレートリテラルを定義します。 テンプレートリテラルプレースホルダー(${})内で、ファイルパスを使用してpathモジュールからparse()メソッドを呼び出します。 parse()メソッドはオブジェクトを返します。 ファイル拡張子のないZIPファイルの名前を取得するには、parse()メソッドが返すオブジェクトにnameプロパティを追加します。 アーカイブ名が返されると、テンプレートリテラルは値を_extracted文字列で補間します。 次に、値はoutputDir変数に格納されます。 これは、抽出されたディレクトリの名前になります。
次に、outputDirに格納されているディレクトリ名でadm-zipモジュールのextractAllToメソッドを呼び出し、ディレクトリ内のコンテンツを抽出します。 その後、console.log()を呼び出して、成功メッセージをログに記録します。
最後に、ZIPアーカイブパスを使用してextractArchive()関数を呼び出します。
ファイルを保存してエディターを終了し、次のコマンドでextractArchive.jsファイルを実行します。
- node extractArchive.js
次の出力が表示されます。
OutputExtracted to "test_extracted" successfully
ZIPコンテンツを含むディレクトリが作成されていることを確認します。
- ls
次の出力が表示されます。
OutputcreateArchive.js file4.txt package-lock.json
readArchive.js test.zip updateArchive.js
extractArchive.js node_modules package.json
test test_extracted
次に、抽出されたコンテンツを含むディレクトリに移動します。
- cd test_extracted
ディレクトリの内容を一覧表示します。
- ls
次の出力が表示されます。
Outputfile1.txt file2.txt file3.txt file4.txt underwater.png
これで、ディレクトリに元のディレクトリにあったすべてのファイルが含まれていることがわかります。
これで、ZIPアーカイブの内容がディレクトリに抽出されました。
結論
このチュートリアルでは、ZIPアーカイブを作成し、その内容を一覧表示し、アーカイブに新しいファイルを追加し、adm-zipモジュールを使用してすべての内容をディレクトリに抽出しました。 これは、Node.jsでZIPアーカイブを操作するための優れた基盤として機能します。
adm-zipモジュールの詳細については、adm-zipのドキュメントを参照してください。 Node.jsの知識を構築し続けるには、Node.jsシリーズでコーディングする方法を参照してください。