はじめに (対象読者・この記事でわかること)
本記事は、Linux/macOS のターミナルや Windows の PowerShell/WSL を日常的に利用しているエンジニア、もしくはスクリプト初心者を対象としています。
「特定の文字列をファイル名に含むものだけを探して別フォルダにコピーしたい」というニーズは、ログの整理や画像・動画の一括管理、コードベースのリファクタリングなど、さまざまな場面で発生します。
この記事を読むことで、以下が実現できるようになります。
- ファイル名に任意の文字列を含むファイルを効率的に検索する方法
- Bash、Python、PowerShell のいずれかを用いて検索結果を別ディレクトリへ自動コピーするスクリプトの作成手順
- 実装時に陥りやすいエラーとその対処法
この記事を書いたきっかけは、社内プロジェクトで大量のビルド成果物から特定のキーワードを含むものだけを別環境へ移行する作業を手作業で行っていたときに、手間とミスが目立ったためです。自動化の第一歩として、シンプルかつ汎用的な手法をご紹介します。
前提知識
この記事を読み進める上で、以下の知識があるとスムーズです。
- 基本的なシェルコマンド(
ls,cp,findなど)の理解 - 任意のプログラミング言語(Bash、Python、PowerShell)の基礎的な構文
- ファイルシステムのパス表記(絶対パス/相対パス)の概念
概要と背景
ファイルを手動で探してコピーする作業は、数百〜数千件規模になると時間がかかり、ヒューマンエラーのリスクが高まります。特に「リリース番号」や「日付」などの文字列がファイル名に埋め込まれているケースでは、正規表現やワイルドカードを使った自動検索が有効です。
本セクションでは、次の3点に焦点を当てます。
- 検索条件の定義
文字列(例:release_2025)がファイル名に含まれるかどうかを判定するロジック。正規表現を利用すれば、大小文字の違いも柔軟に扱えます。 - 検索対象ディレクトリのスコープ
再帰的にサブディレクトリまで検索するか、トップレベルだけに限定するかを選択可能にします。 - コピー先の整理
コピー先ディレクトリは予め作成しておくか、スクリプト内で自動生成するかを決めます。さらに、同名ファイルがすでに存在した場合の上書き・スキップ・リネーム方針も重要です。
これらを踏まえて、Bash、Python、PowerShell の3つの実装例を順に示し、共通のベストプラクティスと注意点をまとめます。
実装手順とサンプルコード
以下では、検索文字列 TARGET を引数として受け取り、検索元ディレクトリ SRC_DIR から コピー先ディレクトリ DST_DIR へ条件に合致したファイルだけをコピーするスクリプトをそれぞれの言語で実装します。
1. Bash(Unix 系)
スクリプト全文
Bash#!/usr/bin/env bash # usage: ./copy_by_keyword.sh "TARGET" "/path/to/src" "/path/to/dst" set -euo pipefail TARGET=$1 # 検索したい文字列 SRC_DIR=$2 # 検索元ディレクトリ(絶対パス推奨) DST_DIR=$3 # コピー先ディレクトリ # コピー先が無ければ作成 mkdir -p "$DST_DIR" # find で再帰検索、-type f でファイルのみ、-inameで大文字小文字を無視 while IFS= read -r -d '' file; do # basename でファイル名だけ取得し、TARGET が含まれるかチェック if [[ "$(basename "$file")" == *"$TARGET"* ]]; then cp -u "$file" "$DST_DIR"/ # -u は newer のみ上書き echo "Copied: $file" fi done < <(find "$SRC_DIR" -type f -print0) echo "All matching files have been copied to $DST_DIR."
ポイント解説
find … -print0とwhile IFS= read -r -d ''の組み合わせで、スペースや日本語を含むファイル名でも安全に扱える。[[ "$(basename "$file")" == *"$TARGET"* ]]でワイルドカードマッチング。*は任意の文字列を表すので、部分一致が簡潔に記述できる。cp -uは、コピー先に同名ファイルがあり、かつ新しければ上書き、古ければスキップするため、冗長なコピーを防げる。
ハマった点と解決策
| 現象 | 原因 | 対策 |
|---|---|---|
ファイル名に改行コードが含まれると find -print0 が正しく処理しない |
-print0 が NUL 区切りで出力するが、改行自体は NUL ではない |
実務上ほとんどあり得ないが、どうしても必要なら -exec オプションで直接 cp を呼び出す |
| 大文字小文字が混在しているとマッチしない | [[ … == *$TARGET* ]] はケースセンシティブ |
shopt -s nocasematch をスクリプト冒頭に追加、もしくは -iname で find の段階で無視 |
2. Python(クロスプラットフォーム)
スクリプト全文
Python#!/usr/bin/env python3 """ copy_by_keyword.py "TARGET" "/path/to/src" "/path/to/dst" 指定した文字列がファイル名に含まれる場合だけ、 src ディレクトリ以下を再帰的に走査し、dst にコピーします。 """ import sys import pathlib import shutil def main(): if len(sys.argv) != 4: print(__doc__) sys.exit(1) target = sys.argv[1] src_dir = pathlib.Path(sys.argv[2]).resolve() dst_dir = pathlib.Path(sys.argv[3]).resolve() dst_dir.mkdir(parents=True, exist_ok=True) for file_path in src_dir.rglob("*"): if file_path.is_file() and target in file_path.name: dest_path = dst_dir / file_path.name # 同名ファイルが既に存在し、かつサイズ・更新時刻が同じ場合はスキップ if dest_path.exists(): src_stat = file_path.stat() dst_stat = dest_path.stat() if (src_stat.st_size == dst_stat.st_size and src_stat.st_mtime <= dst_stat.st_mtime): print(f"skip (already up‑to‑date): {file_path}") continue shutil.copy2(file_path, dest_path) # メタ情報も保持 print(f"copied: {file_path} → {dest_path}") print(f"\nAll matching files have been copied to {dst_dir}") if __name__ == "__main__": main()
ポイント解説
pathlib.Path.rglob("*")で再帰的に全ファイルを走査。target in file_path.nameが部分一致判定。shutil.copy2はcp -pと同等で、アクセス権・タイムスタンプを保持する。- コピー前に
dest_path.exists()をチェックし、サイズと更新時刻で「既に新しい」かどうかを判定。これにより、無駄な I/O を削減できる。
ハマった点と解決策
| 現象 | 原因 | 対策 |
|---|---|---|
Windows 環境でパス文字列に \ がエスケープされる |
引数をそのまま sys.argv で受け取ると、シェルがエスケープ処理を行う |
コマンドプロンプトではパスをクオート("C:\path\to\src")して渡す |
| 大文字小文字が一致しない場合にマッチしない | target in file_path.name はケースセンシティブ |
target.lower() in file_path.name.lower() に置き換えて無視できる |
| シンボリックリンクが循環して無限ループになる | rglob がリンク先も対象になる |
if file_path.is_symlink(): continue を追加し、リンクは除外 |
3. PowerShell(Windows 向け)
スクリプト全文
Powershell<# .SYNOPSIS 指定文字列がファイル名に含まれるものだけを検索し、別フォルダへコピーする .PARAMETER Target 検索したい文字列(部分一致) .PARAMETER Source 検索元ディレクトリ(絶対パス) .PARAMETER Destination コピー先ディレクトリ .EXAMPLE .\Copy-ByKeyword.ps1 -Target "release_2025" -Source "C:\logs" -Destination "D:\archive" #> param( [Parameter(Mandatory=$true)][string]$Target, [Parameter(Mandatory=$true)][string]$Source, [Parameter(Mandatory=$true)][string]$Destination ) # コピー先ディレクトリが無ければ作成 if (-not (Test-Path -LiteralPath $Destination)) { New-Item -ItemType Directory -Path $Destination | Out-Null } # Get-ChildItem -Recurse で再帰検索、-File でファイルのみ取得 Get-ChildItem -Path $Source -Recurse -File | Where-Object { $_.Name -like "*$Target*" } | ForEach-Object { $destPath = Join-Path -Path $Destination -ChildPath $_.Name # 既に同名ファイルがあり、かつ更新日時が新しければスキップ if (Test-Path $destPath) { $srcTime = $_.LastWriteTimeUtc $dstTime = (Get-Item $destPath).LastWriteTimeUtc if ($srcTime -le $dstTime) { Write-Host "skip (up‑to‑date): $($_.FullName)" -ForegroundColor DarkGray return } } Copy-Item -Path $_.FullName -Destination $destPath -Force Write-Host "copied: $($_.FullName) → $destPath" -ForegroundColor Green } Write-Host "`nAll matching files have been copied to $Destination" -ForegroundColor Cyan
ポイント解説
-like "*$Target*"により、ワイルドカードで部分一致検索を実現。PowerShell はデフォルトでケースインセンシティブなので、特別な設定は不要です。Copy-Item -Forceは上書きコピーを許可し、-Forceが無いと既存ファイルはスキップされますが、上記ロジックで「新しいかどうか」を先に判定しています。LastWriteTimeUtcを使うことで、タイムゾーンの違いによる誤判定を防ぎます。
ハマった点と解決策
| 現象 | 原因 | 対策 |
|---|---|---|
| ネットワークドライブ上でのコピーが遅い | Copy-Item がバッファサイズを自動調整できない |
-ToSession オプションでリモートセッションを利用するか、robocopy に委譲 |
ファイル名に [ や ] が含まれると -like が正しく評価されない |
ワイルドカードが正規表現的に解釈される | -match と正規表現 \Q$Target\E を組み合わせてエスケープする |
| 大量ファイルでメモリ使用量が一時的に上がる | Get-ChildItem -Recurse が全結果をメモリに保持しない |
パイプラインで逐次処理し、-Directory オプションでディレクトリだけ先に列挙する |
まとめ
本記事では、「文字列で絞り込んだファイルだけを自動で別フォルダにコピーする」 手法を、Bash、Python、PowerShell の3言語で実装例を交えて解説しました。
- 検索条件の定義:部分一致(
*TARGET*)や正規表現で柔軟に指定可能 - 再帰検索と安全な文字列処理:
find -print0、pathlib.rglob、Get-ChildItem -Recurseの活用でスペースや日本語も問題なく扱える - コピー時の上書きポリシー:
cp -u、shutil.copy2、Copy-Item -Forceに加え、タイムスタンプ比較で「既に新しい」ファイルはスキップ
これらを取り入れることで、手作業でのファイル整理に比べて 作業時間が数十倍に短縮 され、ヒューマンエラーも大幅に削減できます。今後は、ファイルタイプ別にフィルタリングする、複数文字列の OR 条件を付加する、ログ出力を CSV にして後処理しやすくする といった拡張テーマについても記事化していく予定です。
参考資料
