メインコンテンツまでスキップ
Mikyan
白い柴犬
すべての著者を見る

ECR

· 約2分
Mikyan
白い柴犬

Amazon ECR は、フルマネージドのコンテナイメージレジストリサービスです。Docker ImageをPrivateで保存、管理できます。

アクセス制御:各リポジトリに対してリソースベースのIAMポリシーを設定し、プッシュ・プルの許可を細かくコントロールでいます。 保存:AWS S3上に保存されます。 ライフサイクル管理:自動削除ルールを定義可能。 脆弱性スキャン:プッシュ時に自動的に脆弱性スキャンを実行し、結果を取得できます。

ワークフロー

リポジトリの作成

aws ecr create-repository \
--repository-name my-org \
--region ap-northeast-1

認証&Dockerログイン

aws ecr get-login-password \
--region ap-northeast-1 \
--username AWS \
--password-stdin <account-id>.dkr.erc.ap-northeast-1.amazonaws.com

イメージのビルド、タグ付け、プッシュ

docker build -t myapp:v1 .
docker tag my-app:v1 \
<account-id>.dkr.erc.ap-northeast-1.amazonaws.com/my-app:v1
docker push <account-id>.dkr.erc.ap-northeast-1.amazonaws.com/my-app:v1

イメージのプル

docker pull <account-id>.dkr.erc.ap-northeast-1.amazonaws.com/my-app:v1

kubectlによるEKSにデプロイ

KubernetesマニフェストにそのデプロイしたイメージのURLを設定し、下記コマンドで適用したら簡単にEKSにデプロイできます。

# deploy
kubectl apply -f deployment.yaml

# status confirm
kubectl get pods -l app=my-app

React 19 Ref

· 約3分
Mikyan
白い柴犬

In React 19, ref can directly be passed to a component as a property, and forwardRef become unrecommanded.

About ref

In React, a ref (reference) let you operate lower-level imperative API on the following elements:

  • DOM nodes
  • Class component instances

Usually it is created with useRef hook.

function Chat() {
const endRef = useRef();
useEffect(() => {
endRef.current.scrollIntoView({ behavior: 'smooth' });
}, [messages]);
return (
<div className="chat-window">
{messages.map(m => <Message key={m.id} />)}
<div ref={endRef} />
</div>
);
}

Sometimes we need to pass ref down to point to one component of a child component, below are suitable situations:

  • When you build a HOC component
  • When you wrapping a core component with customized design (like wrapping a material UI Input)

Note that usually it is not a good practice, since it increase the complicity and voilate the capsluating philosoph, we should be careful about the usecases.

Improved ref API in React.19

Before React 19, it is not allowed to pass ref as a property, Instead, forwardRef is needed:

import React, { forwardRef, useRef, useImperativeHandle } from 'react';

const FancyInput = forwardRef(function FancyInput(_, ref) {
const inputRef = useRef();

useImperativeHandle(ref, () => ({
focus: () => inputRef.current?.focus(),
clear: () => { if (inputRef.current) inputRef.current.value = ''; }
}), []);

return <input ref={inputRef} className="border rounded p-2" />;
});

export default FancyInput;

// Parent.jsx
import React, { useRef } from 'react';
import FancyInput from './FancyInput';

function Parent() {
const fancyRef = useRef();

return (
<>
<FancyInput ref={fancyRef} />
<button onClick={() => fancyRef.current.focus()}>Focus</button>
<button onClick={() => fancyRef.current.clear()}>Clear</button>
</>
);
}

Note in the example, useImperativeHandle is used to defines the customer handler of ref. By declaring useImperativeHandle in the component, we can:

  • rewrite the ref handler behavior
  • safely limit the handlers of the ref

By using useImperativeHandle, the original ref donot directly pass to the input, instead in the component we define a new ref, to let the ref operates indirectly.

However, from React 19, forwardRef is not recommended, and ref can be passed directly, which is more concise and have better readability:

import React, { forwardRef, useRef, useImperativeHandle } from 'react';

function FancyInput({ ref }) {
const inputRef = useRef();

useImperativeHandle(ref, () => ({
focus: () => inputRef.current?.focus(),
clear: () => { if (inputRef.current) inputRef.current.value = ''; }
}), []);

return <input ref={inputRef} className="border rounded p-2" />;
});

export default FancyInput;

// Parent.jsx
import React, { useRef } from 'react';
import FancyInput from './FancyInput';

function Parent() {
const fancyRef = useRef();

return (
<>
<FancyInput ref={fancyRef} />
<button onClick={() => fancyRef.current.focus()}>Focus</button>
<button onClick={() => fancyRef.current.clear()}>Clear</button>
</>
);
}

Java Optional Best Practices

· 約6分
Mikyan
白い柴犬

JavaのOptionalは、値が存在する場合も存在しない場合もあるコンテナオブジェクトです。これにより、nullチェックの手間を減らし、メソッドの戻り値として「値があるかどうか」を明示的に伝えることができます。Optionalは以下のような利点があります:

明確な意図: メソッドが値を返さない可能性があることを示す。 安全な操作: 明示的なnullチェックを回避し、NullPointerExceptionのリスクを減少させる。 関数型スタイル: map, filter, flatMap などを活用したチェーン処理が可能。

Optionalの作成

Optionalは主に2つのファクトリーメソッドを使って生成します。

  • Optional.of(T value):
    • 値が必ず非nullであることを期待します。
    • nullを渡すと、NullPointerExceptionが発生します。
  • Optional.ofNullable(T value):
    • 値がnullの場合は空のOptional(Optional.empty())を返し、非nullの場合は値を保持します。

作成例

// Method that processes an input string and returns an Optional.
public static Optional<String> processValue(String input) {
// Return empty if input is null or blank
if (input == null || input.trim().isEmpty()) {
return Optional.empty();
}
// Otherwise, return a trimmed value wrapped in an Optional
return Optional.of(input.trim());
}

Optionalの利用方法

Optionalの利用は主に以下のユースケースに分類されます。

  1. 値の存在チェック

    • isPresent() / isEmpty():
      • 値が存在するかどうかを確認する。
      • 例: if (optional.isPresent()) { ... }
  2. 値の取得

    • get():
      • 値が存在する場合に返すが、存在しない場合はNoSuchElementExceptionをスローします。
      • 注意: 直接get()を使うのはリスクがあるため、他のメソッドと組み合わせることが望ましい。
    • orElse(defaultValue):
      • 値が存在しない場合にデフォルト値を返します。
    • orElseThrow(exceptionSupplier):
      • Optionalが空の場合に、指定した例外をスローします。
  3. 値の処理

    • ifPresent(Consumer):
      • 値が存在する場合に、Consumer(値を受け取って何らかの処理を行うラムダ式)を実行します。
    • ifPresentOrElse(Consumer, Runnable):
      • 値が存在する場合はConsumerを、存在しない場合はRunnable(引数を取らない処理)を実行します。
  4. Optionalのチェーン処理

    • map(Function):
      • 値が存在する場合に、Functionを適用し結果を新しいOptionalとして返します。
    • filter(Predicate):
      • 値が条件を満たすかどうかをチェックし、満たさない場合は空のOptionalを返します。
    • flatMap(Function):
      • ネストされたOptionalの解除に利用します。
    • or(Supplier):
      • Optionalが空の場合に、新たなOptionalを返すために利用します。

利用例:

String value1 = "  Hello, World!  ";
Optional<String> opt1 = processValue(value1);

// 存在チェック
if (opt1.isPresent()) {
System.out.println("Value exists!");
} else {
System.out.println("Value is empty.");
}

// 値の取得
try {
// 値がなければ例外がスローされる
String result = opt1.orElseThrow(() -> new IllegalStateException("Value is empty"));
System.out.println("Result: " + result);
} catch (IllegalStateException ex) {
System.err.println(ex.getMessage());
}

// デフォルト値を利用
String defaultResult = opt1.orElse("Default Value");
System.out.println("Default Result: " + defaultResult);

// 値の処理
opt1.ifPresent(val -> System.out.println("Processed value1: " + val));

// ifPresentOrElseを使用して、存在する場合は処理し、存在しない場合は別の処理を実行
opt1.ifPresentOrElse(
val -> System.out.println("Processed value1: " + val),
() -> System.out.println("Value is empty")
);

// チェーン処理:変換とフィルタ
Optional<Integer> lengthOpt = opt1
.map(String::toUpperCase) // 値を大文字に変換
.filter(val -> val.length() > 10) // 長さが10より大きいかフィルター
.map(String::length); // 文字列の長さを取得

lengthOpt.ifPresent(len -> System.out.println("Length of processed value: " + len));

// orを使用して、Optionalが空の場合に代替値を提供
Optional<String> opt2 = opt1.or(() -> Optional.of("Default Greeting"));
System.out.println("Result from opt2: " + opt2.get());

Optionalのベストプラクティス

  1. 戻り値としての利用:

    • Optionalはメソッドの戻り値に利用し、値が存在しない可能性を明示する。
  2. メソッド引数としては使用しない:

    • Optionalを引数にすると、呼び出し側に無駄なラッピングを強いるため、シンプルなnullチェックやオーバーロード、あるいは別のパラメータ設計を検討する。
  3. 直接get()の使用を避ける:

    • 値の取得はorElse, orElseThrow, ifPresentなどのメソッドを利用して、安全に扱う。
  4. 不必要なオブジェクト生成を避ける:

    • チェーン操作を利用して、コードをシンプルに保つ。

機械学習の理論から学ぶ効果的な勉強法

· 約4分
Mikyan
白い柴犬

僕は、機械学習のニューラルネットワークの理論が、人間にとっての最高の勉強法だと考えています。

勉強を進める上で、入力(Input)、処理(Process)、出力(Output)のサイクルは非常に重要です。ここでは、そのフレームワークをどのように学習に応用できるかを解説します。

勉強の効果を定義するにあたって、単に知識を暗記することではなく、**「何かの目標を達成するためのモデルを構築すること」**だと考えます。

以下は、僕が思っている効果的な勉強のフレームワークです:

勉強の第一歩は、何よりも「何を達成したいか」を明確にすることです。

  • 具体的な目標:試験合格、プロジェクト完遂、スキル習得など、測定可能な目標を設定する
  • 期限の設定:目標に向けたスケジュールを決め、進捗を確認できるようにする

2. Input:良質な情報を取り入れる

機械学習における有名な言葉に「GIGO(garbage in, garbage out)」があります。

  • 質の高い教材・情報:信頼できる書籍、論文、オンラインコースなどから学ぶ
  • 多様な視点:異なる角度や分野から情報を取り入れ、知識の幅を広げる

3. Process:自分に合った学習モデルで処理する

入力した情報を自分の中で消化し、知識として定着させるための「プロセス」が必要です。

学習モデルの構築:例えば、マインドマップ、ノート整理、問題演習など、目的に応じた学習法を試行錯誤する フィードバックの活用:定期的に理解度をテストし、間違いや疑問点を洗い出すことで、学習モデルを改善する

4. Output:成果を生み出し、評価する

学んだ内容を実際に「出力」することで、理解度を深め、記憶を定着させます。

アウトプットの方法:問題を解く、プレゼンテーションをする、ブログにまとめるなど 自己評価とフィードバック:成果を評価し、どこが不足しているのかを振り返る。これにより、次の学習プロセスの改善点が明確になります

5. 継続的な改善のサイクル

学習は一度きりの行動ではなく、継続的なサイクルです。

入力:最新の情報や知識を取り入れる 処理:取り入れた知識を自分のものにするための工夫をする 出力:成果を実際に試すことで、理解度を確認する フィードバック:出力をもとに学習方法を修正し、次のサイクルへ反映させる このサイクルを回し続けることで、着実に知識が深まり、より高度な問題にも対応できる力が養われます。

Git Cheat Sheet

· 約4分
Mikyan
白い柴犬

問題の背景

チームで開発する際、よくあるパターンとして、

  • developブランチから、featureブランチAを切って実装、PullRequestを作成し、レビュー待ち
  • その間に、featureブランチAにfeatureブランチBを切って、新しい機能を実装します
  • featureブランチAのレビューを経て承認され、スカッシュマージによってdevelopブランチへ統合され
  • この時、featureブランチBのコミットがfeatureブランチAの元のコミットに含まれてしまうことがあって、developブランチと多くのマージコンフリクトが発生してしまいました。

問題発生の原因

  • スカッシュマージによって個々のコミットがまとめられたため、ブランチBには依然としてブランチAの個々の変更が残っていること
  • その結果、developブランチに既に存在するスカッシュコミットとブランチBの変更が重複して適用され、コンフリクトが発生したこと

解決策:リベースによる解決

ブランチBをブランチA由来のコミットを取り除いて、developブランチ上のスカッシュコミットに合わせてリベースすることで、効率よく解決できます。

具体的には、以下の手順で行います:

git checkout feature/branch-b
git rebase --onto develop HASH_BASE feature/branch-b

このコマンドは、ブランチ B の中で HASH_BASE より後のコミットだけを、develop ブランチ上に再適用します。 HASH_BASEは、ブランチ A が develop から分岐した時点のコミットハッシュにすると、元ブランチ A のコミットがブランチ B に含まれなくなります。

リベース中にコンフリクトが発生した場合、各コンフリクト箇所で適切に修正し、 以下のコマンドでリベースを続行します。

git add <修正済みファイル>
git rebase --continue

リベースが正常に完了したら、ブランチ B の変更を develop ブランチへPRを作成してマージしましょう。

この方法により、履歴がクリーンになり、将来的なマージやデバッグ作業が大幅に楽になります。

Remote ブランチにコミットしました

問題の背景

リモートブランチをLocalにFetchせずに、チェックアウトし、

git checkout origin/branch-a

をすると、そのまま作業し、コミットしたら、プッシュできない。下記のエラーメッセージ表示されました:

error: src refspec feature/branch does not match any
error: failed to push some refs to 'github.com:organization/project.git'

問題発生の原因

Fetchが忘れて、RemoteブランチをCheckoutするとき、実際のHEADはそのブランチではなく、detached HEADの状態です。 すると、ブランチに所属せず、特定のコミットを指している状態です。

git checkout origin/main
# または
git checkout <コミットID>
HEAD → origin/main の最新コミットを直接指す(ローカルブランチではない)

ブランチは指していない
→ この状態でコミットすると、その履歴はどのブランチにも属しません。

そのまま作業して、コミットできるか、そのコミットはブランチ属していない、浮いた状態です。 git log, git reflogで確認できますが、後で消える可能性がある あくまで一時的な作業や、過去のコミットを試す時に使うモード

解決策

  1. ブランチからそのコミットハッシュをcherry-pickしたら解決
git log
# copy the コミット hash abc1234
git checkout branch
git cherry-pick abc1234
git push origin branch
  1. このコミットから新しいブランチを作成で解決
git branch feature/tmp abc1234

プロジェクト管理についての理解1、一人プロジェクト

· 約5分
Mikyan
白い柴犬

これまでいくつかの会社で、さまざまなプロジェクトの管理やリードに関わってきました。
その中で得た知見を、将来の自分のためにも簡単にまとめておきたいと思います。

プロジェクトとは、「限られたリソースを使い、限られた期間で、特定の目的を達成する取り組み」です。
その目的を実現するためには、適切な管理が欠かせません。

プロジェクトの規模によって管理方法は大きく異なるため、私は以下の3つに分類できると考えています。

  • 一人プロジェクト
  • チームプロジェクト
  • 複数組織が関わるプロジェクト

今回はこの中でも、「一人プロジェクト」の管理についてまとめてみます。


一人プロジェクトとは?

プロジェクトの定義を広く捉えれば、さまざまな取り組みがプロジェクトとして扱えます。
たとえば、以下のようなものも一人プロジェクトに該当します。

  • 個人旅行の計画
  • ブログの運営
  • 上司に依頼された勉強会の企画

一人で進めるプロジェクトは、特に管理が軽視されがちです。
単なるタスクのように感覚的に進めてしまうことも多いでしょう。

しかし、「これはプロジェクトだ」と意識し、適切に管理することで、より楽しく、より良い結果を得ることができます。


一人プロジェクト管理で意識すべき3つのポイント

1. 目的を明確にする

  • 「なぜそれをやるのか?」を繰り返し問いかけ、問題を言語化します。
  • 曖昧なまま進めると、途中で迷子になったり、やり直しが発生したりします。

特に他人から頼まれたプロジェクトでは、目的が明確にされていないことが多いです。

例:勉強会の幹事を任された場合

  • チームのコミュニケーションを活性化するため?
  • AIなど最先端技術を学ぶ場として?
  • プレゼン能力を伸ばすため?
  • チームカルチャーを育てるため?

目的によって、トピック選定、参加メンバー、運営方針がすべて変わってきます。
だからこそ、最初に目的を明確にすることが最重要です。


2. マイルストーンに分解する

  • タスクが小さく見えても、実際は難しい部分や不確実な要素が隠れていることがあります。
  • あらかじめ全体像を把握し、マイルストーン(節目)に分解しておくことで、計画的に進めることができます。

また、マイルストーンは進捗の報告タイミングとしても活用できます。


3. 報連相(報告・連絡・相談)を怠らない

一人で進めるからこそ、「見える化」が重要です。
上司や依頼者に対して、今どこまで進んでいるか・どんな課題があるかを適切に共有しましょう。

これには以下のメリットがあります:

  • 認識違いがあっても、早い段階で軌道修正できる
  • 環境や要件の変化にも柔軟に対応できる
  • 自分の成果が評価されやすくなる

おわりに

一人で動くプロジェクトこそ、意識的に「プロジェクト」として扱うことで成果が変わってきます。

  • 目的の明確化
  • マイルストーンの設計
  • 可視化と報連相

この3つを意識すれば、小さな取り組みでも効果的に、そして気持ちよく進めることができるでしょう。

How to setup SFTP in EC2 allowing file upload

· 約2分
Mikyan
白い柴犬

SFTP use SSH protocal, allowing user to transfer files safely. It is a subsystem of SSH, so it runs in port 22.

The name contains FTP, however it is not implements FTP protocal

  • It achieves the same function with FTP
  • It is widely supported by FTP clients

When you want to transfer files with server, it might be a good choice.

How to setup a sftp user allowing it to upload files into specific folder

The following scripts helps setup a SFTP user in

Save it to .sh file, execute the following commands

chmod +x setup_sftp_user.sh
sudo ./setup_sftp_user.sh vendor

It will prompt you to set the password and configure the rest automatically.

#!/bin/bash

# Check if running as root
if [ "$EUID" -ne 0 ]; then
echo "Please run as root or use sudo"
exit 1
fi

# Check if username is provided
if [ -z "$1" ]; then
echo "Usage: $0 <username>"
exit 1
fi

USER=$1

# Create user
useradd -m $USER
passwd $USER

# Setup SFTP directories
mkdir -p /var/sftp/$USER/uploads
chown root:root /var/sftp/$USER
chmod 755 /var/sftp/$USER

chown $USER:$USER /var/sftp/$USER/uploads
chmod 755 /var/sftp/$USER/uploads

# Backup sshd_config
cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak

# Add SSH config for user
cat <<EOL >> /etc/ssh/sshd_config

Match User $USER
ChrootDirectory /var/sftp/$USER
ForceCommand internal-sftp
AllowTcpForwarding no
X11Forwarding no
EOL

# Restart SSH daemon
systemctl restart sshd

# Confirm success
echo "SFTP user '$USER' has been set up successfully."

Node.jsからプロセス実行:child_processのガイド

· 約14分
Mikyan
白い柴犬

Node.jsで外部プロセスを扱う際は、具体的なニーズに基づいて適切な手法を選択してください:

  • exec()を使用 - 短時間実行で小さな出力のコマンドをシェル環境で実行したい場合
  • spawn()を使用 - 長時間実行のプロセス、大きな出力、stdinで入力コントロール、またはリアルタイムでのデータストリーミングが必要な場合
  • execFile()を使用 - シェルを介さずに特定のファイルを実行してセキュリティを向上させたい場合
  • fork()を使用 - Node.jsモジュールを別プロセスでIPC通信とともに実行する場合
  • ユーザー入力の必須サニタイズ - シェルモードでコマンド実行時はインジェクション攻撃を防ぐため
  • util.promisify()の活用を検討 - コールバックベースのメソッドをPromiseに変換してasync/await構文をよりクリーンに使用

プロセスと子プロセスの紹介

プロセスとは?

プロセスとは、実行中のプログラムのインスタンスです。システム上のすべてのアプリケーションは一つ以上のプロセスとして動作し、それぞれが独自のメモリ空間とシステムリソースを持ちます。Node.jsでは、アプリケーションは固有のプロセスID(PID)を持つ単一のプロセスとして動作します。

子プロセスの理解

子プロセスとは、既存のプロセス(親プロセス)によって作成された新しいプロセスです。Node.jsの文脈では:

  • メインのNode.jsアプリケーションが親プロセス
  • 実行する外部プログラムが子プロセスになる
  • 子プロセスは独自のPIDとメモリ空間を持つ
  • 標準I/Oストリーム(stdin、stdout、stderr)を通じて通信が行われる

なぜ子プロセスを使うのか?

子プロセスを使用することで以下が可能になります:

  • システムコマンドや外部プログラムの実行
  • メインイベントループをブロックせずにCPU集約的なタスクを実行
  • 他の言語で書かれた既存のツールやユーティリティの活用
  • 複数のコアにわたってアプリケーションをスケール

Node.js child_process API概要

Node.jsは、子プロセスを作成するための4つの主要メソッドを持つ組み込みchild_processモジュールを提供します:

const { spawn, exec, execFile, fork } = require('child_process');

ChildProcessクラス

すべての子プロセスメソッドはChildProcessクラスのインスタンスを返します。このクラスはEventEmitterを拡張し、以下を提供します:

  • イベントハンドリング: 'exit'、'close'、'error'、'disconnect'
  • ストリームアクセス: stdinstdoutstderrプロパティ
  • プロセス制御: kill()disconnect()メソッド
  • プロセス情報: pidconnectedexitCodeプロパティ
const child = spawn('ls', ['-la']);

child.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});

child.on('exit', (code) => {
console.log(`プロセスが終了しました。終了コード: ${code}`);
});

共通オプション

ほとんどの子プロセスメソッドは、共通プロパティを持つオプションオブジェクトを受け入れます:

const options = {
cwd: '/path/to/working/directory', // 作業ディレクトリ
env: { ...process.env, NODE_ENV: 'production' }, // 環境変数
shell: true, // シェルで実行
timeout: 5000, // 最大実行時間
maxBuffer: 1024 * 1024, // stdout/stderrの最大バッファサイズ
stdio: 'inherit' // stdin/stdout/stderrの処理方法
};

メソッド間の類似点

戻り値の型(非同期)

すべての非同期メソッドはChildProcessインスタンスを返します:

const child1 = spawn('echo', ['hello']);
const child2 = exec('echo hello');
const child3 = execFile('echo', ['hello']);
const child4 = fork('./child-script.js');

同期バリアント

ほとんどのメソッドには実行をブロックする同期版があります:

  • spawnSync()
  • execSync()
  • execFileSync()

同期メソッドは結果オブジェクトを返します:

{
pid: 12345,
output: [], // stdioの結果配列
stdout: '', // 標準出力(BufferまたはString)
stderr: '', // 標準エラー(BufferまたはString)
status: 0, // 終了コード(シグナルで終了した場合はnull)
signal: null, // プロセスを終了させたシグナル
error: undefined // プロセスが失敗した場合のErrorオブジェクト
}

イベント駆動アーキテクチャ

すべての子プロセスは同じ主要イベントを発火します:

child.on('spawn', () => {});     // プロセス開始
child.on('exit', (code, signal) => {}); // プロセス終了
child.on('error', (error) => {}); // エラー発生
child.on('close', (code, signal) => {}); // すべてのstdioストリームが閉じられた

メソッド間の違い

spawn()

目的: ストリーミングI/Oを使用した低レベルプロセス作成

特徴:

  • デフォルトではシェルを使用しない
  • 即座にChildProcessを返す
  • リアルタイムでデータをストリーミング
  • 自動バッファリングなし
  • 最もメモリ効率が良い
const child = spawn('grep', ['pattern'], {
stdio: ['pipe', 'pipe', 'pipe']
});

child.stdout.on('data', (data) => {
console.log(`発見: ${data}`);
});

最適な用途: 長時間実行プロセス、大きな出力、リアルタイムデータ処理

exec()

目的: バッファ出力を持つシェルでのコマンド実行

特徴:

  • 常にシェルを使用
  • 出力全体をメモリにバッファ
  • 完全なstdout/stderrを持つコールバックを提供
  • maxBuffer制限の対象
  • シェル機能(パイプ、リダイレクトなど)をサポート
exec('ls -la | grep .js', (error, stdout, stderr) => {
if (error) {
console.error(`エラー: ${error}`);
return;
}
console.log(`ファイル: ${stdout}`);
});

最適な用途: シンプルなシェルコマンド、小さな出力、一回限りの操作

注意: exec()も他のメソッド同様ChildProcessインスタンスを返し、stdin、stdout、stderrのストリームデータを取得できますが、内部的なBuffer処理により、大きな出力ではパフォーマンスに影響を与える可能性があります。

execFile()

目的: シェルを介さない直接的なファイル実行

特徴:

  • デフォルトではシェルを使用しない
  • exec()よりも安全
  • exec()と同様に出力をバッファ
  • 直接的なファイル実行
  • shell: trueオプションでシェルを有効化可能
execFile('node', ['--version'], (error, stdout, stderr) => {
if (error) {
console.error(`エラー: ${error}`);
return;
}
console.log(`Nodeバージョン: ${stdout}`);
});

最適な用途: プログラムの安全な実行、シェルインジェクションの回避

fork()

目的: IPC通信を持つ新しいNode.jsプロセスの作成

特徴:

  • Node.jsモジュール専用
  • IPC(プロセス間通信)チャンネルを確立
  • Node.js環境を継承
  • 組み込みメッセージパッシング
  • 独立したV8インスタンス
// parent.js
const child = fork('./worker.js');
child.send({ task: 'process-data', data: largeDataSet });
child.on('message', (result) => {
console.log('受信:', result);
});

// worker.js
process.on('message', (msg) => {
if (msg.task === 'process-data') {
const result = processData(msg.data);
process.send({ result });
}
});

最適な用途: CPU集約的なタスク、Node.jsモジュール実行、ワーカープロセス

実践的な使用例

タイムアウト処理

長時間実行されるプロセスには適切なタイムアウトを設定しましょう:

const { spawn } = require('child_process');

function runWithTimeout(command, args, timeoutMs = 5000) {
return new Promise((resolve, reject) => {
const child = spawn(command, args);
let output = '';
let errorOutput = '';

// タイムアウトタイマー設定
const timer = setTimeout(() => {
child.kill('SIGTERM');
reject(new Error(`プロセスがタイムアウトしました: ${timeoutMs}ms`));
}, timeoutMs);

child.stdout.on('data', (data) => {
output += data.toString();
});

child.stderr.on('data', (data) => {
errorOutput += data.toString();
});

child.on('close', (code) => {
clearTimeout(timer);
if (code === 0) {
resolve(output);
} else {
reject(new Error(`プロセスが異常終了: コード ${code}, エラー: ${errorOutput}`));
}
});

child.on('error', (error) => {
clearTimeout(timer);
reject(error);
});
});
}

// 使用例
runWithTimeout('ping', ['-c', '3', 'google.com'], 10000)
.then(output => console.log('結果:', output))
.catch(error => console.error('エラー:', error.message));

AbortControllerを使用したプロセス中断

Node.js 15+では、AbortControllerを使用してプロセスを中断できます:

const { spawn } = require('child_process');
const { AbortController } = require('abort-controller');

async function runWithAbort(command, args, signal) {
return new Promise((resolve, reject) => {
const child = spawn(command, args);
let output = '';

// AbortSignalが発火したときの処理
if (signal) {
signal.addEventListener('abort', () => {
child.kill('SIGTERM');
reject(new Error('プロセスが中断されました'));
});
}

child.stdout.on('data', (data) => {
output += data.toString();
});

child.on('close', (code) => {
if (code === 0) {
resolve(output);
} else {
reject(new Error(`プロセスが失敗: ${code}`));
}
});

child.on('error', reject);
});
}

// 使用例
const controller = new AbortController();
const { signal } = controller;

// 5秒後に中断
setTimeout(() => controller.abort(), 5000);

runWithAbort('sleep', ['10'], signal)
.then(output => console.log('完了:', output))
.catch(error => console.error('エラー:', error.message));

対話型プロセスの処理

stdinを使用してプロセスと対話する例:

const { spawn } = require('child_process');

function interactiveProcess() {
const child = spawn('node', ['-i'], {
stdio: ['pipe', 'pipe', 'pipe']
});

// コマンドを送信
child.stdin.write('console.log("Hello from child process");\n');
child.stdin.write('process.version;\n');
child.stdin.write('.exit\n');

child.stdout.on('data', (data) => {
console.log('出力:', data.toString());
});

child.stderr.on('data', (data) => {
console.error('エラー:', data.toString());
});

child.on('close', (code) => {
console.log(`対話型プロセスが終了: ${code}`);
});
}

interactiveProcess();

環境変数とワーキングディレクトリの設定

const { spawn } = require('child_process');
const path = require('path');

function runWithCustomEnv() {
const child = spawn('node', ['-e', 'console.log(process.env.CUSTOM_VAR, process.cwd())'], {
cwd: path.join(__dirname, 'subdir'), // 作業ディレクトリを変更
env: {
...process.env,
CUSTOM_VAR: 'カスタム値',
NODE_ENV: 'development'
}
});

child.stdout.on('data', (data) => {
console.log('出力:', data.toString());
});

child.on('error', (error) => {
console.error('実行エラー:', error);
});
}

runWithCustomEnv();

大量データの効率的な処理

大きなファイルやストリームを処理する際のパフォーマンス最適化:

const { spawn } = require('child_process');
const fs = require('fs');

function processLargeFile(inputFile, outputFile) {
return new Promise((resolve, reject) => {
const child = spawn('gzip', ['-c'], {
stdio: ['pipe', 'pipe', 'pipe']
});

const readStream = fs.createReadStream(inputFile);
const writeStream = fs.createWriteStream(outputFile);

// ストリームをパイプで接続
readStream.pipe(child.stdin);
child.stdout.pipe(writeStream);

// エラーハンドリング
readStream.on('error', reject);
writeStream.on('error', reject);
child.stderr.on('data', (data) => {
console.error('gzipエラー:', data.toString());
});

child.on('close', (code) => {
if (code === 0) {
resolve(`ファイル圧縮完了: ${outputFile}`);
} else {
reject(new Error(`圧縮失敗: ${code}`));
}
});
});
}

// 使用例
processLargeFile('large-file.txt', 'large-file.txt.gz')
.then(message => console.log(message))
.catch(error => console.error(error));

プロセスプールパターン

複数の同時実行プロセスを管理:

const { spawn } = require('child_process');

class ProcessPool {
constructor(maxConcurrency = 3) {
this.maxConcurrency = maxConcurrency;
this.running = new Set();
this.queue = [];
}

async execute(command, args) {
return new Promise((resolve, reject) => {
const task = { command, args, resolve, reject };

if (this.running.size < this.maxConcurrency) {
this.runTask(task);
} else {
this.queue.push(task);
}
});
}

runTask(task) {
const { command, args, resolve, reject } = task;
const child = spawn(command, args);

this.running.add(child);
let output = '';
let errorOutput = '';

child.stdout.on('data', (data) => {
output += data.toString();
});

child.stderr.on('data', (data) => {
errorOutput += data.toString();
});

child.on('close', (code) => {
this.running.delete(child);

if (code === 0) {
resolve(output);
} else {
reject(new Error(`プロセス失敗: ${errorOutput}`));
}

// キューから次のタスクを実行
if (this.queue.length > 0) {
const nextTask = this.queue.shift();
this.runTask(nextTask);
}
});

child.on('error', (error) => {
this.running.delete(child);
reject(error);

if (this.queue.length > 0) {
const nextTask = this.queue.shift();
this.runTask(nextTask);
}
});
}
}

// 使用例
const pool = new ProcessPool(2);

const tasks = [
pool.execute('echo', ['タスク1']),
pool.execute('echo', ['タスク2']),
pool.execute('echo', ['タスク3']),
pool.execute('echo', ['タスク4'])
];

Promise.all(tasks)
.then(results => {
console.log('すべてのタスク完了:', results);
})
.catch(error => {
console.error('タスクエラー:', error);
});

セキュリティ上の考慮事項

シェルインジェクション防止

シェルモードを使用する際は、常にユーザー入力をサニタイズしてください:

// ❌ 危険 - これは絶対にやってはいけません
const userInput = req.body.filename;
exec(`cat ${userInput}`, callback); // インジェクション攻撃に脆弱

// ✅ 安全 - spawnで配列引数を使用
spawn('cat', [userInput], callback);

// ✅ 安全 - 入力を検証・サニタイズ
const safeFilename = path.basename(userInput).replace(/[^a-zA-Z0-9.-]/g, '');
exec(`cat ${safeFilename}`, callback);

シェルインジェクションに脆弱なメソッド

  • exec() (常にシェルを使用)
  • spawn() with shell: true
  • execFile() with shell: true

より安全な代替案

  • シェルモードなしでspawn()またはexecFile()を使用
  • すべてのユーザー入力を検証・サニタイズ
  • 許可される値のホワイトリストを使用
  • 適切なエスケープのためにshell-escapeなどのライブラリを検討

モダンなAsync/Await使用法

コールバックベースのメソッドをPromiseに変換:

const { promisify } = require('util');
const execAsync = promisify(exec);

async function getNodeVersion() {
try {
const { stdout } = await execAsync('node --version');
return stdout.trim();
} catch (error) {
console.error('Nodeバージョンの取得に失敗:', error);
throw error;
}
}

最終結論

Node.jsのchild_processモジュールは、外部プロセスを実行するための強力なツールを提供します。ニーズに基づいて適切なメソッドを選択してください:

  • パフォーマンス重視のアプリケーション: ストリーミング機能と低メモリフットプリントのためspawn()を使用
  • シンプルなシェルコマンド: 小さな出力での便利さのためexec()を使用
  • セキュリティ意識の高いアプリケーション: シェルモードなしでexecFile()またはspawn()を選択
  • Node.jsスケーリング: 並列化可能なCPU集約的タスクにはfork()を使用

特にユーザー入力を扱う際は常にセキュリティへの影響を考慮し、よりクリーンで保守しやすいコードのためにモダンなasync/awaitパターンを活用することを忘れないでください。

Javaにおけるファイル処理

· 約2分
Mikyan
白い柴犬

プロジェクト開発する際、ファイル処理はよくあるタスクです。一文でJavaにおけるファイル処理のおすすめ手法を紹介します。

結論から言うと、今のファイル処理は、java.nioを使ったら無難です。

java.nio と java.nio.file パッケージ

そもそも、java.nioはNon Blocking I/Oという意味で、スレッドをブロックせずにIO処理する機能を提供します。

java.nio.fileは、NIO技術に基づいて、FileSystem処理の抽象化機能です。java.nio.fileを提供するクラスを使うことによって、より高いパーフォマンス、より簡単に、ファイルの操作ができます。

java.nio.file におけるFileSystemの操作

Pathはファイルシステム上のファイルとパスを表します。

Pathsクラスは、Pathを作成するStaticメソッドだけで構成されます。

Filesクラスはファイル、ディレクトリなどを操作するStaticメソッドだけで構成されます。

ファイルシステムの操作

ファイルの作成、削除、ディレクトリの作成

Path path = Paths.get('mypath');
// エントリを一覧
Stream<Path> pathsStream = Files.list(path);
// ディレクトリの作成
Path subPath = Files.createDirectories(path.resolve('tmp'));
// ファイルの作成
Path file = Files.createFile(subPath.resolve('README.md'));
//
Files.delete(file);

boolean deleted = Files.deleteIfExists(subPath);

// コピー
Path sourcePath = path.resolve('soruce.txt');
Path targetPath = path.resolve('target.txt');

Files.copy(sourcePath, targetPath);

Files.exists(targetPath);
Files.isDirectory(targetPath);

Files.size()

Visit

書き出す、読み込み