#!/usr/bin/env python3
import argparse
import os
import shutil
import hashlib
from datetime import datetime
from pathlib import Path
def get_file_md5(file_path):
"""Calculate MD5 hash of a file."""
hash_md5 = hashlib.md5()
try:
with open(file_path, 'rb') as f:
for chunk in iter(lambda: f.read(4096), b''):
hash_md5.update(chunk)
return hash_md5.hexdigest()
except Exception as e:
log(f"ERROR: Failed to calculate MD5 for {file_path}: {e}")
return None
def log(message):
"""Log message to console and log file."""
timestamp = datetime...
全文を表示 ▼
#!/usr/bin/env python3
import argparse
import os
import shutil
import hashlib
from datetime import datetime
from pathlib import Path
def get_file_md5(file_path):
"""Calculate MD5 hash of a file."""
hash_md5 = hashlib.md5()
try:
with open(file_path, 'rb') as f:
for chunk in iter(lambda: f.read(4096), b''):
hash_md5.update(chunk)
return hash_md5.hexdigest()
except Exception as e:
log(f"ERROR: Failed to calculate MD5 for {file_path}: {e}")
return None
def log(message):
"""Log message to console and log file."""
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
log_message = f"[{timestamp}] {message}"
print(log_message)
if log_file_path:
try:
with open(log_file_path, 'a') as f:
f.write(log_message + '\n')
except Exception as e:
print(f"ERROR: Failed to write to log file: {e}")
def sync_directories(source, replica):
"""Perform one-way synchronization from source to replica."""
source_path = Path(source)
replica_path = Path(replica)
if not source_path.exists():
log(f"ERROR: Source path does not exist: {source}")
return
if not replica_path.exists():
log(f"Creating replica directory: {replica}")
replica_path.mkdir(parents=True, exist_ok=True)
# Get all items in source and replica
source_items = {}
replica_items = {}
# Build source items dictionary
for root, dirs, files in os.walk(source_path):
for file in files:
file_path = Path(root) / file
rel_path = file_path.relative_to(source_path)
source_items[str(rel_path)] = ('file', file_path)
for dir_name in dirs:
dir_path = Path(root) / dir_name
rel_path = dir_path.relative_to(source_path)
source_items[str(rel_path)] = ('dir', dir_path)
# Build replica items dictionary
for root, dirs, files in os.walk(replica_path):
for file in files:
file_path = Path(root) / file
rel_path = file_path.relative_to(replica_path)
replica_items[str(rel_path)] = ('file', file_path)
for dir_name in dirs:
dir_path = Path(root) / dir_name
rel_path = dir_path.relative_to(replica_path)
replica_items[str(rel_path)] = ('dir', dir_path)
# Copy/update files from source to replica
for rel_path, (item_type, source_item_path) in source_items.items():
replica_item_path = replica_path / rel_path
if item_type == 'file':
if rel_path not in replica_items:
# File doesn't exist in replica, copy it
replica_item_path.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(source_item_path, replica_item_path)
log(f"COPY {rel_path}")
else:
# File exists in replica, check if content is different
source_md5 = get_file_md5(source_item_path)
replica_md5 = get_file_md5(replica_item_path)
if source_md5 and replica_md5 and source_md5 != replica_md5:
shutil.copy2(source_item_path, replica_item_path)
log(f"UPDATE {rel_path}")
elif item_type == 'dir':
if rel_path not in replica_items:
# Directory doesn't exist in replica, create it
replica_item_path.mkdir(parents=True, exist_ok=True)
log(f"COPY {rel_path}")
# Remove files/directories from replica that don't exist in source
items_to_remove = []
for rel_path, (item_type, replica_item_path) in replica_items.items():
if rel_path not in source_items:
items_to_remove.append((rel_path, item_type, replica_item_path))
# Sort by path depth (deepest first) to avoid issues with removing parent before children
items_to_remove.sort(key=lambda x: x[0].count(os.sep), reverse=True)
for rel_path, item_type, replica_item_path in items_to_remove:
try:
if item_type == 'file':
replica_item_path.unlink()
log(f"REMOVE {rel_path}")
elif item_type == 'dir':
shutil.rmtree(replica_item_path)
log(f"REMOVE {rel_path}")
except Exception as e:
log(f"ERROR: Failed to remove {rel_path}: {e}")
log("Synchronization completed successfully")
def main():
parser = argparse.ArgumentParser(
description='One-way file synchronization tool'
)
parser.add_argument(
'source_path',
help='Path to the source directory'
)
parser.add_argument(
'replica_path',
help='Path to the replica directory'
)
parser.add_argument(
'log_file_path',
help='Path to the log file'
)
args = parser.parse_args()
global log_file_path
log_file_path = args.log_file_path
# Clear or create log file
try:
with open(log_file_path, 'w') as f:
f.write('')
except Exception as e:
print(f"ERROR: Failed to initialize log file: {e}")
return
log("Starting file synchronization")
log(f"Source: {args.source_path}")
log(f"Replica: {args.replica_path}")
sync_directories(args.source_path, args.replica_path)
if name == 'main':
main()
判定
勝利票
0 / 3
平均スコア
総合点
総評
MD5ベースの更新による一方向同期を実装し、タイムスタンプ付きでコンソール/ファイルにログを出力します。しかし、型競合(例:ソースファイル対レプリカディレクトリ)を安全に処理しない、削除ステップで二重削除が発生する可能性がある(ディレクトリとその子要素の両方を収集し、その後ディレクトリに対してrmtreeを実行すると、後続の失敗を引き起こす可能性がある)、ログがグローバル変数に依存しているなど、正確性/堅牢性に顕著なギャップがあります。また、クイック事前チェックなしに、存在するすべてのファイルに対して両方のサイドでMD5を計算するため、コストがかかる可能性があります。
採点詳細を表示 ▼
正確さ
重み 35%多くの基本的なケース(欠落ファイルのコピー、MD5による更新、余分なファイルの削除)では正しいですが、一般的なエッジケースでは破損/エラーが発生します:ファイル/ディレクトリの型の不一致は処理されません。削除パスでは、親のrmtree後に子を削除しようとする可能性があります(エラーを引き起こし、失敗が発生した場合に一貫性のない状態を残す可能性があります)。
完全性
重み 20%必須引数、一方向同期、MD5比較、ネスト/空ディレクトリ、タイムスタンプ付きロギングをカバーしています。重要な保護策(ソース/レプリカ関係チェック)が欠けており、競合処理が不足しています。ロギング操作では、相対パスが一貫して使用されない場合があります(ディレクトリがCOPYとしてログに記録されますが、明確さに欠けます)。
コード品質
重み 20%可読性は高いですが、ログファイルパスのためにグローバル変数に依存しており、関心事(ロギングと同期)が混在し、重複したos.walk辞書構築を使用しています。エラー処理はアドホックであり、実際の失敗をマスクする可能性があります。
実用性
重み 15%単純なシナリオでは使用可能ですが、型競合と削除順序に関する脆弱性により、自動サーバーバックアップへの適合性が低下します。クイックサイズ事前チェックなしにすべてのファイルをハッシュするため、効率が悪化します。
指示遵守
重み 10%argparse、Python 3、一方向同期、MD5ベースの検出を使用し、タイムスタンプ付きでコンソールとファイルにログを出力し、一度実行されます。軽微な逸脱:ディレクトリのログエントリはCOPYラベルを使用し、グローバルベースのロギングは壊れやすいです。
総合点
総評
回答Aは、主要な要件を網羅した機能的で読みやすい同期スクリプトです。ログファイルのパスにグローバル変数を使用しており(コードのアンチパターン)、標準の`logging`モジュールではなくカスタムロギング関数を実装しています。MD5ハッシュには4096バイトのチャンクサイズを使用しており(大きなファイルには非効率的です)。削除ロジックは深さでソートされており正しいですが、`os.walk`で2つの完全な辞書を構築してから比較するアプローチはやや洗練されていません。エラー処理は存在しますが基本的です。スクリプトは実行ごとにログファイルをクリアしますが、これは望ましくない場合があります。全体的には堅実ですが、いくつかの軽微な設計上の弱点があります。
採点詳細を表示 ▼
正確さ
重み 35%主な同期ケース(新しいファイル/ディレクトリのコピー、MD5による変更ファイルの更新、最深部からソートされた余分な項目の削除)を正しく処理します。パスがレプリカではファイルとして存在するがソースではディレクトリである(またはその逆)エッジケースを処理しません。シンボリックリンクも処理しません。`shutil.rmtree`によるディレクトリの削除は、既に個別に削除対象としてスケジュールされていた子要素を削除する可能性がありますが、深さソートによって緩和されます。全体的には標準的なケースでは正しいです。
完全性
重み 20%すべての必須機能(argparse、MD5ハッシュ、コンソールとファイルへのロギング、一方向同期、ネストされたディレクトリ、空のディレクトリ)を網羅しています。不足している点:存在チェック以外のパス検証がない、型競合の処理がない、`expanduser`がない、実行ごとにログをクリアするがその旨の記載がない。
コード品質
重み 20%ログファイルパスにグローバル変数を使用しており、これはコードのアンチパターンです。標準のロギングモジュールではなくカスタムロギング関数を使用しています。関数名は適切で構造化されています。型ヒントはありません。4096バイトのチャンクサイズは大きなファイルには最適ではありません。全体的に読みやすいですが、プロフェッショナルな基準には達していません。
実用性
重み 15%典型的なユースケースでは正しく機能します。実行ごとにログファイルをクリアします(履歴が失われる可能性があります)。パスの重複検証がありません。`expanduser`がありません。基本的な使用には適していますが、本番サーバーでの使用には堅牢性が欠けています。
指示遵守
重み 10%すべての明示的な指示に従っています:3つの位置引数を持つargparse、MD5ハッシュ、タイムスタンプ付きのコンソールとファイルへのロギング、一方向同期、単一実行(ループなし)、ネストされた/空のディレクトリの処理。軽微な逸脱:ログファイルに追記するのではなく、実行ごとにログファイルをクリアします。
総合点
総評
回答Aは、プロンプトの基本的な要件を満たす機能的なスクリプトを提供します。`argparse`を正しく使用し、`os.walk`を使用してコア同期ロジックを実装しています。しかし、いくつかの重大な弱点があります。ファイル/ディレクトリの型の不一致を処理できないという正確性に関するバグがあり、入力パスの検証のような重要な堅牢性チェックが欠けており、危険な操作(例:ディレクトリをそれ自体に同期する)を防ぐことができません。また、グローバル変数の使用と、Pythonの標準`logging`モジュールではなくカスタムロギング関数を使用しているため、コードの品質が低下しています。
採点詳細を表示 ▼
正確さ
重み 35%スクリプトのロジックは、ソースでファイルとして存在するパスがレプリカではディレクトリである(またはその逆)エッジケースを処理できません。このシナリオでは、ディレクトリのMD5ハッシュを計算しようとし、エラーが発生します。単純なケースのコアロジックは存在しますが、この欠陥により正確性が低下します。
完全性
重み 20%回答は、`argparse`、MD5ハッシュ、ロギングなど、プロンプトに記載された主な要件を満たしています。しかし、特にインプットバリデーションがなく、完成されたコマンドラインツールとして期待される機能が欠けており、完成品としては不十分です。
コード品質
重み 20%コードは機能的で、関数に合理的に構造化されています。しかし、ログファイルのパスにグローバル変数を使用することは、モジュール性を低下させる重大な設計上の欠陥です。さらに、Pythonの強力で標準的な`logging`モジュールを活用する代わりに、カスタムロギング関数を実装しています。
実用性
重み 15%このスクリプトは、堅牢性の欠如により、サーバーバックアップツールとしての意図された目的での実用価値が限られています。入力パスの検証がないため、単純なユーザーエラーがデータ損失や意図しない動作につながる可能性があります。正確性に関するバグも信頼性を低下させています。
指示遵守
重み 10%回答はすべての指示に非常によく従っています。指定されたライブラリを使用し、要求されたすべての機能を実装しています。ログメッセージの形式(「COPY rel_path」)は、プロンプトで与えられた例と完全に一致しています。