T-CREATOR

Shell Script でファイル操作を極める:find・rsync・圧縮/展開の実践レシピ

Shell Script でファイル操作を極める:find・rsync・圧縮/展開の実践レシピ

Shell Script を使ったファイル操作は、開発現場での日常業務を大きく効率化してくれます。大量のファイルを検索したり、サーバー間でデータを同期したり、ログファイルをアーカイブしたりする作業は、手動で行うと時間がかかりますが、適切なコマンドを使えば一瞬で完了するでしょう。

本記事では、Shell Script の中でも特に実用的な「find」「rsync」「圧縮/展開」という 3 つの操作に焦点を当て、実践的なレシピをご紹介します。初心者の方でもすぐに使える具体例から、現場で役立つ応用テクニックまで、段階的に解説していきますね。

背景

Shell Script によるファイル操作の重要性

現代の開発現場では、扱うファイルの数と種類が爆発的に増えています。ソースコード、ログファイル、設定ファイル、ビルド成果物、データベースバックアップなど、プロジェクトの規模が大きくなるほど、ファイル管理の複雑さも増していきます。

これらのファイルを効率的に管理するには、GUI ツールだけでは限界があるでしょう。Shell Script を使ったコマンドライン操作なら、以下のようなメリットが得られます。

#メリット具体例
1大量処理の自動化数千個のファイルを一度に検索・変換
2再現性の確保スクリプト化することで同じ操作を確実に再実行
3リモート操作SSH 経由でサーバー上のファイルを直接操作
4スケジュール実行cron と組み合わせて定期的なバックアップを実現
5柔軟な条件指定複雑な検索条件でファイルを抽出

find・rsync・圧縮/展開が必要とされる場面

以下の図は、開発ワークフローの中でこれら 3 つの操作がどのように活用されるかを示しています。

mermaidflowchart TB
  dev["開発者"] -->|コーディング| files["ソースファイル"]
  files -->|find で検索| search["特定ファイルの抽出"]
  search -->|rsync で同期| server["サーバーへデプロイ"]
  files -->|tar/gzip で圧縮| archive["アーカイブ作成"]
  archive -->|バックアップ| storage[("ストレージ")]
  storage -->|展開| restore["復元"]
  server -->|ログ収集| logs["ログファイル"]
  logs -->|find + 圧縮| archive

この図から分かるように、find・rsync・圧縮操作は開発フローの随所で必要とされます。これらを使いこなせるようになると、以下のような作業が劇的に効率化されるでしょう。

  • 特定の拡張子やサイズのファイルを探す
  • 開発環境と本番環境を同期する
  • 古いログファイルをアーカイブして容量を節約する
  • バックアップからファイルを復元する

課題

手動操作の限界とリスク

ファイル操作を手動で行う場合、以下のような課題に直面します。

1. 作業時間の増大

数百個のファイルから特定の条件に合うものを探す場合、GUI のファイルマネージャーでは一つずつ確認する必要があります。これでは膨大な時間がかかってしまうでしょう。

2. ヒューマンエラーのリスク

手動でファイルをコピーしたり移動したりする際、誤って重要なファイルを削除したり、上書きしたりするリスクがあります。特に似たような名前のファイルが多い場合、間違いが起こりやすくなりますね。

3. 再現性の欠如

同じ操作を別の環境で行う必要がある場合、手順を覚えていないと再現できません。また、チームメンバーに作業を引き継ぐ際にも、口頭説明だけでは正確に伝わらないことがあるでしょう。

以下の表は、手動操作と Shell Script 操作の比較をまとめたものです。

#項目手動操作Shell Script
1処理速度遅い(1 ファイル数秒)速い(数千ファイルを数秒)
2エラー率高い低い(スクリプト検証済み)
3再現性低い高い(同じスクリプトで再現可能)
4記録残らないスクリプトとして残る
5自動化不可能可能(cron 等と連携)

各コマンドの学習曲線

Shell Script のファイル操作コマンドには、それぞれ独特の構文とオプションがあります。初心者が直面する主な課題は以下の通りです。

mermaidflowchart TD
  start["ファイル操作の学習開始"] --> find_learn["findコマンドの学習"]
  find_learn --> find_issue1["複雑な検索条件の指定"]
  find_learn --> find_issue2["execオプションの理解"]

  start --> rsync_learn["rsyncコマンドの学習"]
  rsync_learn --> rsync_issue1["オプションの組み合わせ"]
  rsync_learn --> rsync_issue2["除外パターンの指定"]

  start --> compress_learn["圧縮コマンドの学習"]
  compress_learn --> compress_issue1["tar/gzip/bzip2の使い分け"]
  compress_learn --> compress_issue2["パイプとリダイレクト"]

  find_issue1 --> solution["体系的な学習と実践"]
  find_issue2 --> solution
  rsync_issue1 --> solution
  rsync_issue2 --> solution
  compress_issue1 --> solution
  compress_issue2 --> solution

find コマンドの課題

  • 検索条件の論理演算(AND、OR、NOT)の組み合わせ方
  • -execオプションでの他コマンド実行の記法
  • パーミッションや更新日時による検索の指定方法

rsync コマンドの課題

  • オプションの意味(-a、-v、-z など)の理解
  • ローカル同期とリモート同期の記法の違い
  • 削除されたファイルの同期処理

圧縮/展開の課題

  • tar、gzip、bzip2、xz の使い分け
  • 圧縮と展開のオプションの違い
  • パイプを使った複合的な処理

これらの課題を一つずつ解決していくことで、ファイル操作のスキルが着実に向上していきます。

解決策

find コマンドによる高度なファイル検索

find コマンドは、指定したディレクトリ配下のファイルを様々な条件で検索できる強力なツールです。基本的な使い方から応用まで、段階的に見ていきましょう。

基本的な構文

find コマンドの基本構文は以下の通りです。

bash# 基本構文
find [検索開始ディレクトリ] [検索条件] [アクション]

名前による検索

ファイル名やパターンで検索する最も基本的な使い方です。

bash# カレントディレクトリ配下のすべての .txt ファイルを検索
find . -name "*.txt"

# 大文字小文字を区別しない検索
find . -iname "readme.md"

# 複数のパターンにマッチするファイルを検索
find . -name "*.js" -o -name "*.ts"

-nameオプションは大文字小文字を区別しますが、-inameを使うと区別しません。また、-o(OR 演算子)を使うと複数の条件を指定できますね。

ファイルタイプによる検索

ファイルの種類(通常ファイル、ディレクトリ、シンボリックリンクなど)で絞り込みができます。

bash# 通常ファイルのみを検索
find . -type f -name "*.log"

# ディレクトリのみを検索
find . -type d -name "config"

# シンボリックリンクを検索
find . -type l

-typeオプションの主な値は以下の通りです。

#タイプ説明
1f通常ファイル
2dディレクトリ
3lシンボリックリンク
4sソケット
5p名前付きパイプ

サイズによる検索

ファイルサイズを条件にした検索も頻繁に使われます。

bash# 100MB以上のファイルを検索
find . -type f -size +100M

# 10KB未満のファイルを検索
find . -type f -size -10k

# ちょうど1GBのファイルを検索
find . -type f -size 1G

サイズ指定の単位は、c(バイト)、k(キロバイト)、M(メガバイト)、G(ギガバイト)が使用できます。+は「より大きい」、-は「より小さい」、単位のみなら「ちょうど」という意味になりますね。

更新日時による検索

ファイルの更新日時で検索することで、最近変更されたファイルや古いファイルを抽出できます。

bash# 7日以内に更新されたファイルを検索
find . -type f -mtime -7

# 30日以上前に更新されたファイルを検索
find . -type f -mtime +30

# 24時間以内に更新されたファイルを検索
find . -type f -mmin -1440

-mtimeは日数単位、-mminは分単位で指定します。-atime(アクセス時刻)や-ctime(状態変更時刻)も同様に使えるでしょう。

パーミッションによる検索

セキュリティ監査やトラブルシューティングで役立つ、パーミッションを条件にした検索です。

bash# 誰でも書き込み可能なファイルを検索(セキュリティリスク)
find . -type f -perm -002

# 実行権限のあるファイルを検索
find . -type f -perm -111

# 特定のパーミッション(644)を持つファイルを検索
find . -type f -perm 644

複数条件の組み合わせ

論理演算子を使って、複雑な検索条件を組み立てることができます。

bash# 1MB以上で、かつ .log ファイルを検索(AND条件)
find . -type f -size +1M -name "*.log"

# .js または .ts ファイルを検索(OR条件)
find . -type f \( -name "*.js" -o -name "*.ts" \)

# .log ファイルで、かつ 30日以上前のものを検索
find . -type f -name "*.log" -mtime +30

条件を並べると自動的に AND 条件になります。OR 条件には-oを、NOT 条件には!を使用しましょう。

exec オプションで検索結果に対してコマンド実行

find コマンドの真価は、検索したファイルに対して自動的に処理を実行できる点にあります。

bash# 検索したファイルを削除
find . -type f -name "*.tmp" -exec rm {} \;

# 検索したファイルの詳細情報を表示
find . -type f -name "*.log" -exec ls -lh {} \;

# 検索したファイルを別ディレクトリにコピー
find . -type f -name "*.jpg" -exec cp {} /backup/ \;

# 確認してから削除(-ok オプション)
find . -type f -name "*.bak" -ok rm {} \;

{}は検索されたファイル名に置き換えられます。\;は各ファイルごとにコマンドを実行し、\+を使うとまとめて実行されますね。

rsync による効率的なファイル同期

rsync は、ファイルの差分のみを転送することで、高速かつ効率的な同期を実現するツールです。ローカル間でもリモート間でも使用できる万能性が魅力でしょう。

rsync の基本構文と主要オプション

rsync の基本的な使い方を見ていきます。

bash# 基本構文
rsync [オプション] [送信元] [送信先]

主要なオプションは以下の通りです。

#オプション説明
1-aアーカイブモード(再帰的、属性保持など)
2-v詳細表示(verbose)
3-z転送時に圧縮
4-h人間が読みやすい形式で表示
5-nドライラン(実際には実行しない)
6--delete送信元にないファイルを削除
7--exclude除外パターンを指定
8--progress進捗状況を表示

ローカル間でのファイル同期

同じマシン内でのディレクトリ同期の例です。

bash# 基本的な同期(再帰的にコピー)
rsync -av /source/directory/ /destination/directory/

# ドライランで確認(実際には実行しない)
rsync -avn /source/ /dest/

# 進捗状況を表示しながら同期
rsync -avh --progress /source/ /dest/

送信元のパスの最後に​/​を付けるかどうかで動作が変わります。​/​を付けると中身のみ、付けないとディレクトリごとコピーされますね。

リモートサーバーとの同期

SSH 経由でリモートサーバーとファイルを同期する方法です。

bash# ローカルからリモートへ送信
rsync -avz /local/path/ user@remote.server.com:/remote/path/

# リモートからローカルへ受信
rsync -avz user@remote.server.com:/remote/path/ /local/path/

# SSHのポート番号を指定
rsync -avz -e "ssh -p 2222" /local/ user@server:/remote/

-zオプションを使うと転送時に圧縮されるため、ネットワーク帯域を節約できます。

削除も含めた完全同期

送信元と送信先を完全に一致させたい場合は、--deleteオプションを使用します。

bash# 送信元にないファイルは送信先から削除
rsync -av --delete /source/ /dest/

# 削除前に確認(--dry-run と組み合わせ)
rsync -avn --delete /source/ /dest/

--deleteは強力なオプションなので、必ず事前にドライランで確認することをお勧めします。

除外パターンの指定

特定のファイルやディレクトリを同期から除外できます。

bash# node_modules ディレクトリを除外
rsync -av --exclude 'node_modules' /source/ /dest/

# 複数のパターンを除外
rsync -av --exclude '*.log' --exclude '.git' /source/ /dest/

# 除外パターンをファイルから読み込み
rsync -av --exclude-from='exclude.txt' /source/ /dest/

exclude.txtファイルには、1 行に 1 つずつ除外パターンを記述します。

text# exclude.txt の例
*.log
*.tmp
.git/
node_modules/
.DS_Store

バックアップを残しながら同期

上書きされるファイルのバックアップを残すこともできます。

bash# バックアップディレクトリを指定
rsync -av --backup --backup-dir=/backup/$(date +%Y%m%d) /source/ /dest/

# バックアップファイルにサフィックスを付ける
rsync -av --suffix=.bak /source/ /dest/

これにより、同期時に上書きされるファイルを別の場所に保存できるため、安全性が高まりますね。

圧縮と展開の実践テクニック

ファイルやディレクトリを圧縮してアーカイブ化することで、ストレージ容量を節約し、転送時間を短縮できます。各圧縮形式の特徴と使い分けを見ていきましょう。

tar + gzip による圧縮

最も一般的な組み合わせで、幅広い環境で利用できます。

bash# ディレクトリを tar.gz 形式で圧縮
tar -czf archive.tar.gz /path/to/directory/

# ファイルを追加しながら圧縮
tar -czf backup.tar.gz file1.txt file2.txt dir1/

# 詳細表示しながら圧縮
tar -czvf archive.tar.gz /path/to/directory/

オプションの意味は以下の通りです。

#オプション説明
1-cアーカイブを作成
2-zgzip で圧縮
3-fファイル名を指定
4-v詳細表示

tar.gz ファイルの展開

圧縮されたアーカイブを展開する方法です。

bash# 基本的な展開
tar -xzf archive.tar.gz

# 展開先を指定
tar -xzf archive.tar.gz -C /destination/path/

# 内容を確認してから展開
tar -tzf archive.tar.gz

# 詳細表示しながら展開
tar -xzvf archive.tar.gz

-xオプションで展開、-tオプションで内容の一覧表示ができます。

tar + bzip2 による高圧縮

gzip よりも圧縮率が高い bzip2 を使用する方法です。

bash# bzip2 形式で圧縮
tar -cjf archive.tar.bz2 /path/to/directory/

# bzip2 ファイルを展開
tar -xjf archive.tar.bz2

# 内容を確認
tar -tjf archive.tar.bz2

-jオプションで bzip2 を指定します。gzip より圧縮に時間がかかりますが、ファイルサイズはより小さくなるでしょう。

tar + xz による最高圧縮

最も圧縮率の高い xz 形式を使用する方法です。

bash# xz 形式で圧縮
tar -cJf archive.tar.xz /path/to/directory/

# xz ファイルを展開
tar -xJf archive.tar.xz

# 詳細表示で圧縮
tar -cJvf archive.tar.xz /path/to/directory/

-Jオプション(大文字)で xz を指定します。圧縮時間は最も長いですが、最高の圧縮率が得られますね。

圧縮形式の比較と使い分け

以下の表は、各圧縮形式の特徴をまとめたものです。

#形式圧縮速度圧縮率CPU 負荷用途
1gzip★★★★★☆一般的なバックアップ
2bzip2★★☆★★★長期保存アーカイブ
3xz★☆☆★★★容量重視のアーカイブ
mermaidflowchart LR
  files["ファイル群"] --> choice{"圧縮方式の選択"}
  choice -->|速度重視| gzip["gzip 圧縮"]
  choice -->|バランス重視| bzip2["bzip2 圧縮"]
  choice -->|容量重視| xz["xz 圧縮"]

  gzip --> storage1[("アーカイブ<br/>tar.gz")]
  bzip2 --> storage2[("アーカイブ<br/>tar.bz2")]
  xz --> storage3[("アーカイブ<br/>tar.xz")]

特定のファイルを除外して圧縮

不要なファイルを除外してアーカイブを作成できます。

bash# 特定のパターンを除外
tar -czf archive.tar.gz --exclude='*.log' --exclude='node_modules' /path/

# 除外リストをファイルから読み込み
tar -czf archive.tar.gz --exclude-from=exclude-list.txt /path/

# 隠しファイルを除外
tar -czf archive.tar.gz --exclude='.*' /path/

exclude-list.txtには除外したいパターンを 1 行ずつ記述します。

パイプを使った高度な圧縮

パイプを使うことで、より柔軟な処理が可能になります。

bash# 圧縮しながらリモートサーバーへ転送
tar -czf - /source/ | ssh user@remote "cat > /backup/archive.tar.gz"

# リモートから圧縮ファイルを受け取って展開
ssh user@remote "tar -czf - /path/" | tar -xzf -

# 進捗状況を表示しながら圧縮
tar -cf - /large/directory/ | pv | gzip > archive.tar.gz

-を使うと標準入出力を介して処理できます。pvコマンドを組み合わせると、進捗状況が視覚的に分かりやすくなりますね。

具体例

実践例 1:古いログファイルを自動的にアーカイブ

開発環境やサーバーでは、ログファイルが日々蓄積されていきます。ディスク容量を節約するために、古いログファイルを定期的に圧縮してアーカイブする実践例をご紹介しましょう。

シナリオと要件

以下の条件でログファイルを管理します。

  • ​/​var​/​log​/​application​/​配下のログファイルが対象
  • 30 日以上前のログファイルを圧縮
  • 圧縮後は元ファイルを削除
  • 90 日以上前の圧縮ファイルは完全に削除

ステップ 1:古いログファイルを検索

まず、30 日以上前のログファイルを検索します。

bash# 30日以上前の .log ファイルを検索して表示
find /var/log/application/ -type f -name "*.log" -mtime +30

このコマンドで、圧縮対象のファイル一覧が確認できます。-mtime +30は「30 日より前」という意味ですね。

ステップ 2:検索したファイルを個別に圧縮

検索したファイルを gzip で圧縮します。

bash# 古いログファイルを個別に圧縮
find /var/log/application/ -type f -name "*.log" -mtime +30 -exec gzip {} \;

gzipコマンドは、元のファイルを圧縮して.gz拡張子を付けたファイルに置き換えます。元ファイルは自動的に削除されるでしょう。

ステップ 3:90 日以上前の圧縮ファイルを削除

さらに古い圧縮ファイルは削除して容量を空けます。

bash# 90日以上前の .gz ファイルを削除
find /var/log/application/ -type f -name "*.log.gz" -mtime +90 -delete

-deleteオプションを使うと、検索されたファイルが削除されます。

ステップ 4:統合スクリプトの作成

これらの処理を 1 つのスクリプトにまとめます。

bash#!/bin/bash

# ログアーカイブスクリプト
LOG_DIR="/var/log/application"
COMPRESS_DAYS=30
DELETE_DAYS=90

echo "ログファイルのアーカイブを開始します..."

次に、圧縮処理の部分を記述します。

bash# 30日以上前のログファイルを圧縮
echo "30日以上前のログファイルを圧縮中..."
find "$LOG_DIR" -type f -name "*.log" -mtime +${COMPRESS_DAYS} -exec gzip -v {} \;

続いて、古い圧縮ファイルの削除処理を追加します。

bash# 90日以上前の圧縮ファイルを削除
echo "90日以上前の圧縮ファイルを削除中..."
find "$LOG_DIR" -type f -name "*.log.gz" -mtime +${DELETE_DAYS} -delete

最後に、処理結果を表示します。

bash# 処理結果のサマリーを表示
TOTAL_LOGS=$(find "$LOG_DIR" -type f -name "*.log" | wc -l)
TOTAL_ARCHIVES=$(find "$LOG_DIR" -type f -name "*.log.gz" | wc -l)

echo "完了しました。"
echo "現在のログファイル数: ${TOTAL_LOGS}"
echo "圧縮済みファイル数: ${TOTAL_ARCHIVES}"

ステップ 5:cron で定期実行

このスクリプトを毎日自動実行するように設定します。

bash# crontab を編集
crontab -e

# 毎日午前3時に実行(以下を追加)
# 0 3 * * * /path/to/log-archive.sh >> /var/log/archive.log 2>&1

この設定により、毎日午前 3 時に自動的にログのアーカイブ処理が実行されます。実行結果は​/​var​/​log​/​archive.logに記録されるでしょう。

実践例 2:開発環境とステージング環境の同期

開発したコードをステージング環境にデプロイする際、rsync を使えば効率的に同期できます。差分のみを転送するため、全体をコピーするより高速ですね。

シナリオと要件

以下の条件で環境間の同期を行います。

  • ローカルの開発環境からステージングサーバーへデプロイ
  • node_modules.gitなどの不要なファイルは除外
  • バックアップを残しながら同期
  • ドライランで確認してから実行

以下の図は、同期フローを示しています。

mermaidflowchart LR
  local["ローカル環境<br/>開発コード"] -->|1. ドライラン確認| dryrun["rsync -n"]
  dryrun -->|2. 問題なし| sync["rsync 実行"]
  sync -->|除外処理| exclude["node_modules<br/>.git を除外"]
  exclude -->|3. 差分転送| staging["ステージング環境"]
  staging -->|4. バックアップ保存| backup[("バックアップ<br/>ディレクトリ")]

ステップ 1:除外ファイルのリスト作成

同期から除外するファイルやディレクトリをリスト化します。

text# rsync-exclude.txt
node_modules/
.git/
.env
.DS_Store
*.log
.cache/
dist/
build/

このファイルを作成することで、除外パターンを管理しやすくなります。

ステップ 2:ドライランで確認

実際に同期する前に、どのファイルが転送されるか確認します。

bash# ドライランで転送されるファイルを確認
rsync -avzn --delete \
  --exclude-from=rsync-exclude.txt \
  /local/project/ \
  user@staging.server.com:/var/www/app/

-nオプション(または--dry-run)により、実際には転送せずに動作をシミュレートできます。

ステップ 3:バックアップを保存しながら同期

上書きされるファイルのバックアップを日付付きディレクトリに保存します。

bash# バックアップディレクトリの日付を生成
BACKUP_DATE=$(date +%Y%m%d_%H%M%S)

次に、バックアップを残しながら同期を実行します。

bash# バックアップを保存しながら同期
rsync -avz --delete \
  --backup --backup-dir=/var/www/backups/${BACKUP_DATE} \
  --exclude-from=rsync-exclude.txt \
  /local/project/ \
  user@staging.server.com:/var/www/app/

このコマンドにより、上書きされるファイルは​/​var​/​www​/​backups​/​20250116_150000​/​のような形で保存されます。

ステップ 4:同期スクリプトの作成

これらの処理を自動化するスクリプトを作成しましょう。

bash#!/bin/bash

# デプロイ設定
LOCAL_DIR="/Users/developer/projects/myapp/"
REMOTE_USER="deploy"
REMOTE_HOST="staging.server.com"
REMOTE_DIR="/var/www/app/"
BACKUP_DIR="/var/www/backups"
EXCLUDE_FILE="rsync-exclude.txt"

次に、ドライラン実行の部分を記述します。

bash# ドライランで確認
echo "ドライランを実行中..."
rsync -avzn --delete \
  --exclude-from="${EXCLUDE_FILE}" \
  "${LOCAL_DIR}" \
  "${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_DIR}"

続いて、ユーザーに確認を求める処理を追加します。

bash# ユーザーに確認
echo ""
read -p "上記の内容で同期を実行しますか? (y/n): " answer

if [ "$answer" != "y" ]; then
  echo "同期をキャンセルしました。"
  exit 0
fi

最後に、実際の同期処理を実行します。

bash# バックアップディレクトリ名を生成
BACKUP_DATE=$(date +%Y%m%d_%H%M%S)

# 本番実行
echo "同期を実行中..."
rsync -avz --delete \
  --backup --backup-dir="${BACKUP_DIR}/${BACKUP_DATE}" \
  --exclude-from="${EXCLUDE_FILE}" \
  "${LOCAL_DIR}" \
  "${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_DIR}"

echo "同期が完了しました。"
echo "バックアップ: ${BACKUP_DIR}/${BACKUP_DATE}"

このスクリプトを実行すると、確認後に安全に同期が行われます。

ステップ 5:同期後の確認

同期が正しく行われたか確認するコマンドを追加することもできます。

bash# リモートでディレクトリの内容を確認
ssh ${REMOTE_USER}@${REMOTE_HOST} "ls -lah ${REMOTE_DIR}"

# リモートでアプリケーションを再起動(例)
ssh ${REMOTE_USER}@${REMOTE_HOST} "systemctl restart myapp"

これにより、デプロイ後の状態確認とサービスの再起動まで自動化できるでしょう。

実践例 3:プロジェクトの定期バックアップシステム

開発プロジェクト全体を定期的にバックアップすることで、データ損失のリスクを軽減できます。複数世代のバックアップを保持する実践的なシステムを構築してみましょう。

シナリオと要件

以下の条件でバックアップシステムを構築します。

  • プロジェクトディレクトリ全体を日次でバックアップ
  • node_modulesdistなどのビルド成果物は除外
  • 過去 7 日分のバックアップを保持(それ以前は削除)
  • バックアップファイル名に日付を含める
  • 完了時にメール通知(オプション)

以下の図は、バックアップシステムの処理フローを示しています。

mermaidflowchart TB
  start["バックアップ開始"] --> exclude["除外ファイル確認"]
  exclude --> create["tar.gz 作成"]
  create --> verify["ファイル整合性検証"]
  verify --> move["バックアップ保存"]
  move --> cleanup["古いバックアップ削除"]
  cleanup --> report["レポート生成"]
  report --> notify["通知送信"]
  notify --> finish["完了"]

ステップ 1:除外パターンの定義

バックアップから除外するファイルを定義します。

text# backup-exclude.txt
node_modules
.git
dist
build
.cache
*.log
.env.local
.DS_Store
coverage
.next

このリストにより、不要なファイルをバックアップから除外できます。

ステップ 2:日付付きバックアップの作成

日付をファイル名に含めたアーカイブを作成します。

bash# プロジェクト名と日付からファイル名を生成
PROJECT_NAME="myproject"
BACKUP_DATE=$(date +%Y%m%d)
BACKUP_FILE="${PROJECT_NAME}_${BACKUP_DATE}.tar.gz"

次に、実際にアーカイブを作成します。

bash# プロジェクトディレクトリを圧縮
tar -czf "/backups/${BACKUP_FILE}" \
  --exclude-from=backup-exclude.txt \
  -C /projects/ \
  myproject/

-Cオプションでディレクトリを移動してから圧縮することで、アーカイブ内のパス構造をシンプルにできますね。

ステップ 3:バックアップの整合性検証

作成したアーカイブが破損していないか確認します。

bash# アーカイブの内容を検証
if tar -tzf "/backups/${BACKUP_FILE}" > /dev/null 2>&1; then
  echo "バックアップの整合性検証: OK"
else
  echo "エラー: バックアップが破損しています"
  exit 1
fi

-tオプションで内容を一覧表示できれば、アーカイブは正常です。

ステップ 4:古いバックアップの削除

7 日以上前のバックアップファイルを削除します。

bash# 7日以上前のバックアップを削除
find /backups/ -type f -name "${PROJECT_NAME}_*.tar.gz" -mtime +7 -delete

これにより、ディスク容量を節約しながら必要な世代のバックアップを保持できます。

ステップ 5:統合バックアップスクリプト

すべての処理を統合したスクリプトを作成しましょう。

bash#!/bin/bash

# バックアップ設定
PROJECT_NAME="myproject"
PROJECT_DIR="/projects/myproject"
BACKUP_DIR="/backups"
EXCLUDE_FILE="backup-exclude.txt"
RETENTION_DAYS=7
LOG_FILE="/var/log/backup.log"

次に、ログ記録関数を定義します。

bash# ログ記録関数
log() {
  echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

log "バックアップを開始します: $PROJECT_NAME"

続いて、バックアップファイル名を生成し、アーカイブを作成します。

bash# バックアップファイル名を生成
BACKUP_DATE=$(date +%Y%m%d)
BACKUP_FILE="${PROJECT_NAME}_${BACKUP_DATE}.tar.gz"
BACKUP_PATH="${BACKUP_DIR}/${BACKUP_FILE}"

# ディレクトリが存在するか確認
if [ ! -d "$PROJECT_DIR" ]; then
  log "エラー: プロジェクトディレクトリが見つかりません: $PROJECT_DIR"
  exit 1
fi

# バックアップを作成
log "アーカイブを作成中: $BACKUP_FILE"
tar -czf "$BACKUP_PATH" \
  --exclude-from="$EXCLUDE_FILE" \
  -C "$(dirname $PROJECT_DIR)" \
  "$(basename $PROJECT_DIR)/"

次に、作成したバックアップの検証を行います。

bash# バックアップの整合性を検証
log "整合性を検証中..."
if tar -tzf "$BACKUP_PATH" > /dev/null 2>&1; then
  BACKUP_SIZE=$(du -h "$BACKUP_PATH" | cut -f1)
  log "バックアップ作成完了: $BACKUP_FILE (サイズ: $BACKUP_SIZE)"
else
  log "エラー: バックアップが破損しています"
  exit 1
fi

続いて、古いバックアップを削除します。

bash# 古いバックアップを削除
log "古いバックアップを削除中(保持期間: ${RETENTION_DAYS}日)..."
DELETED_COUNT=$(find "$BACKUP_DIR" -type f -name "${PROJECT_NAME}_*.tar.gz" -mtime +${RETENTION_DAYS} -delete -print | wc -l)
log "削除したバックアップ数: $DELETED_COUNT"

最後に、バックアップ状況のサマリーを表示します。

bash# バックアップ一覧を表示
log "現在のバックアップ一覧:"
find "$BACKUP_DIR" -type f -name "${PROJECT_NAME}_*.tar.gz" -printf "%TY-%Tm-%Td %TH:%TM  %s bytes  %f\n" | sort -r | tee -a "$LOG_FILE"

TOTAL_BACKUPS=$(find "$BACKUP_DIR" -type f -name "${PROJECT_NAME}_*.tar.gz" | wc -l)
TOTAL_SIZE=$(du -sh "$BACKUP_DIR" | cut -f1)

log "バックアップ完了: 保持数 $TOTAL_BACKUPS 個、合計サイズ $TOTAL_SIZE"

ステップ 6:cron で定期実行の設定

このスクリプトを毎日自動実行するように設定します。

bash# crontab を編集
crontab -e

# 毎日午前2時に実行(以下を追加)
# 0 2 * * * /path/to/backup.sh

これで、毎日午前 2 時に自動的にバックアップが作成されます。

ステップ 7:バックアップの復元方法

万が一の際に備えて、バックアップからの復元方法も確認しておきましょう。

bash# バックアップ一覧を表示
ls -lh /backups/myproject_*.tar.gz

# 特定の日付のバックアップを復元
tar -xzf /backups/myproject_20250110.tar.gz -C /restore/

# 復元内容を確認
ls -la /restore/myproject/

必要に応じて、復元先のディレクトリを変更してから本番環境に適用するとよいでしょう。

実践例 4:複数サーバーへの一括デプロイ

複数のサーバーに同じファイルを一括でデプロイする際、rsync とシェルスクリプトを組み合わせることで効率化できます。

シナリオと要件

以下の条件で複数サーバーへのデプロイを行います。

  • 3 台の Web サーバーに同じアプリケーションをデプロイ
  • サーバーリストはファイルで管理
  • 並列実行で時間を短縮
  • 各サーバーの実行結果をログに記録

ステップ 1:サーバーリストの作成

デプロイ対象のサーバー情報をファイルに記述します。

text# servers.txt
web1.example.com
web2.example.com
web3.example.com

サーバーが増えた場合も、このファイルに追記するだけで対応できます。

ステップ 2:単一サーバーへのデプロイ関数

1 台のサーバーへデプロイする処理を関数化します。

bash#!/bin/bash

# デプロイ関数
deploy_to_server() {
  local server=$1
  local local_dir=$2
  local remote_dir=$3

  echo "[$server] デプロイ開始..."

  # rsync でファイルを同期
  rsync -avz --delete \
    --exclude 'node_modules' \
    --exclude '.git' \
    "$local_dir" \
    "deploy@${server}:${remote_dir}" \
    && echo "[$server] デプロイ成功" \
    || echo "[$server] デプロイ失敗"
}

この関数により、サーバーごとのデプロイ処理を統一できます。

ステップ 3:並列デプロイの実装

複数サーバーへ並列でデプロイするメインスクリプトです。

bash# 設定
LOCAL_DIR="/projects/webapp/"
REMOTE_DIR="/var/www/app/"
SERVER_LIST="servers.txt"
LOG_DIR="/var/log/deploy"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)

次に、ログディレクトリを作成します。

bash# ログディレクトリの作成
mkdir -p "$LOG_DIR"

続いて、各サーバーへ並列でデプロイを実行します。

bash# 各サーバーへ並列デプロイ
while read -r server; do
  # コメント行とブランク行をスキップ
  [[ "$server" =~ ^#.*$ || -z "$server" ]] && continue

  # バックグラウンドで実行(並列化)
  {
    LOG_FILE="${LOG_DIR}/${server}_${TIMESTAMP}.log"
    deploy_to_server "$server" "$LOCAL_DIR" "$REMOTE_DIR" 2>&1 | tee "$LOG_FILE"
  } &

done < "$SERVER_LIST"

# すべてのバックグラウンドジョブの完了を待つ
wait

echo "すべてのサーバーへのデプロイが完了しました。"

&でバックグラウンド実行し、waitですべてのジョブの完了を待つことで、並列デプロイが実現できますね。

ステップ 4:デプロイ結果の集計

各サーバーのデプロイ結果をまとめて表示します。

bash# デプロイ結果のサマリー
echo "========================================="
echo "デプロイ結果サマリー"
echo "========================================="

SUCCESS_COUNT=0
FAIL_COUNT=0

while read -r server; do
  [[ "$server" =~ ^#.*$ || -z "$server" ]] && continue

  LOG_FILE="${LOG_DIR}/${server}_${TIMESTAMP}.log"

  if grep -q "デプロイ成功" "$LOG_FILE"; then
    echo "[$server] ✓ 成功"
    ((SUCCESS_COUNT++))
  else
    echo "[$server] ✗ 失敗"
    ((FAIL_COUNT++))
  fi
done < "$SERVER_LIST"

echo "========================================="
echo "成功: $SUCCESS_COUNT 台、失敗: $FAIL_COUNT 台"
echo "========================================="

この集計により、一目でデプロイ状況が把握できます。

まとめ

本記事では、Shell Script によるファイル操作の中核となる「find」「rsync」「圧縮/展開」の 3 つの技術を、基礎から実践まで体系的に解説しました。

find コマンドでは、名前・サイズ・日時・パーミッションなど多様な条件でファイルを検索し、-execオプションで自動処理を実行する方法を学びました。大量のファイルから必要なものだけを効率的に抽出できるようになったでしょう。

rsync コマンドでは、差分転送による高速なファイル同期を実現し、ローカル・リモート間でのデータ転送、除外パターンの指定、バックアップ保持など、実務で必須となる機能を習得しました。開発環境とステージング環境の同期も簡単に実現できますね。

圧縮と展開では、gzip・bzip2・xz といった圧縮形式の特徴と使い分け、tar コマンドとの組み合わせ、除外パターンの指定方法を理解しました。ストレージ容量の節約とバックアップの効率化に役立つはずです。

実践例として、ログファイルの自動アーカイブ、環境間の同期、定期バックアップシステム、複数サーバーへの一括デプロイという 4 つのシナリオを紹介しました。これらのスクリプトをベースにカスタマイズすれば、様々な現場のニーズに対応できるでしょう。

これらのコマンドとテクニックを組み合わせることで、日々の開発業務が大幅に効率化されます。手動で行っていた繰り返し作業を自動化し、より創造的な開発活動に時間を使えるようになることを願っています。

関連リンク