昔はこういうのは、テキストエディタを立ち上げて、自分たちで書いていたんだよ!という年寄りの自慢話を若い人にする日は近い(というか今度来るインターンに自慢しよう)。
一覧表(テキストファイル)と実際のフォルダのファイルの内容を突合する仕組みを作って、とお願いして出てきたのが以下のスクリプト。内省(自己改善)という技を身につけたのが、大きいようで、最初はいきなりファイルを削除しようとしていたのが、表示だけのモードを追加したり、実際に削除する前に確認ができるようにしたりと、より安全に使えるような工夫を勝手に入れてくれます。
いやー便利。
写真は、あまり関係なくて、初めて生で見る分割キーボード。かっこいい…!音声入力も進化してるし、人間がタイプする量ってこれから減る方向にしか進まないかもしれないですが、そういうのと関係なくこういうのは拘りたいですよね(笑)と言いつつ、HHKB Liteで満足しちゃってます。体がキー配置を覚えてしまってるので、拘りというより固着と言うべきか(^^
#!/usr/bin/env ruby
# -*- coding: utf-8 -*-
require 'pathname'
require 'optparse'
class FileChecker
def initialize(files_list_path = 'files.txt', target_dir = '.', options = {})
@files_list_path = files_list_path
@target_dir = target_dir
@options = options
end
def run
puts "=" * 60
puts "ファイル突合チェック"
puts "=" * 60
puts "対象ディレクトリ: #{File.expand_path(@target_dir)}"
puts "ファイル一覧: #{@files_list_path}"
puts "モード: #{@options[:dry_run] ? 'ドライラン(削除は実行されません)' : '通常'}"
puts
# files.txtから期待されるファイル一覧を読み込み
expected_files = read_expected_files
# 実際に存在するファイル一覧を取得
actual_files = get_actual_files
# 突合結果を出力
check_missing_files(expected_files, actual_files)
check_extra_files(expected_files, actual_files)
# 自動削除モードの場合
if @options[:auto_delete] && !@options[:dry_run]
auto_delete_extra_files(expected_files, actual_files)
end
end
private
def read_expected_files
unless File.exist?(@files_list_path)
puts "エラー: #{@files_list_path} が見つかりません"
exit 1
end
files = []
File.readlines(@files_list_path, chomp: true).each do |line|
line = line.strip
next if line.empty? || line.start_with?('#')
files << line
end
puts "期待されるファイル数: #{files.size}"
files
end
def get_actual_files
files = []
# 現在のディレクトリ内のファイルを取得(ディレクトリは除外)
Dir.glob(File.join(@target_dir, '*')).each do |path|
if File.file?(path)
files << File.basename(path)
end
end
# サブディレクトリ内のファイルも取得
Dir.glob(File.join(@target_dir, '**/*')).each do |path|
if File.file?(path)
relative_path = Pathname.new(path).relative_path_from(Pathname.new(@target_dir)).to_s
files << relative_path unless files.include?(relative_path)
end
end
puts "実際に存在するファイル数: #{files.size}"
puts
files
end
def check_missing_files(expected_files, actual_files)
missing_files = expected_files - actual_files
puts "【1. files.txtにあるが実際には存在しないファイル】"
puts "-" * 50
if missing_files.empty?
puts "✅ なし(すべてのファイルが存在します)"
else
puts "⚠️ 見つからないファイル数: #{missing_files.size}"
missing_files.sort.each do |file|
puts " ❌ #{file}"
end
end
puts
end
def check_extra_files(expected_files, actual_files)
extra_files = actual_files - expected_files
# 自分自身とfiles.txtは除外
extra_files = extra_files.reject { |f| f == File.basename($0) || f == @files_list_path }
puts "【2. 実際に存在するがfiles.txtにないファイル】"
puts "-" * 50
if extra_files.empty?
puts "✅ なし(余分なファイルはありません)"
else
puts "⚠️ 余分なファイル数: #{extra_files.size}"
puts
if @options[:show_commands]
puts "📝 削除コマンド案:"
extra_files.sort.each do |file|
puts " rm '#{file}'"
end
puts
puts "📦 一括削除スクリプト:"
puts "cat << 'EOF' > delete_extra_files.sh"
puts "#!/bin/bash"
extra_files.sort.each do |file|
puts "rm '#{file}'"
end
puts "echo '削除完了'"
puts "EOF"
puts "chmod +x delete_extra_files.sh"
puts "# 実行前に内容を確認: cat delete_extra_files.sh"
puts "# 実行: ./delete_extra_files.sh"
else
puts "📝 余分なファイル一覧:"
extra_files.sort.each do |file|
puts " 🗂️ #{file}"
end
puts
puts "💡 削除コマンドを表示するには --show-commands オプションを使用してください"
end
end
puts
end
def auto_delete_extra_files(expected_files, actual_files)
extra_files = actual_files - expected_files
extra_files = extra_files.reject { |f| f == File.basename($0) || f == @files_list_path }
return if extra_files.empty?
puts "🗑️ 自動削除モードを実行中..."
deleted_count = 0
error_count = 0
extra_files.sort.each do |file|
file_path = File.join(@target_dir, file)
begin
File.delete(file_path)
puts " ✅ 削除: #{file}"
deleted_count += 1
rescue => e
puts " ❌ 削除失敗: #{file} (#{e.message})"
error_count += 1
end
end
puts
puts "📊 削除結果:"
puts " ✅ 削除成功: #{deleted_count}ファイル"
puts " ❌ 削除失敗: #{error_count}ファイル" if error_count > 0
end
end
# オプション解析
options = {}
OptionParser.new do |opts|
opts.banner = "使用法: #{$0} [オプション] [files.txt] [対象ディレクトリ]"
opts.on("-c", "--show-commands", "削除コマンドを表示") do
options[:show_commands] = true
end
opts.on("-d", "--dry-run", "ドライラン(実際の削除は行わない)") do
options[:dry_run] = true
end
opts.on("-a", "--auto-delete", "余分なファイルを自動削除(危険)") do
options[:auto_delete] = true
end
opts.on("-h", "--help", "このヘルプを表示") do
puts opts
exit
end
end.parse!
# メイン処理
if __FILE__ == $0
files_list = ARGV[0] || 'files.txt'
target_dir = ARGV[1] || '.'
if options[:auto_delete] && !options[:dry_run]
puts "⚠️ 警告: 自動削除モードが有効です。"
print "本当に余分なファイルを削除しますか? (yes/no): "
response = gets.chomp.downcase
unless response == 'yes'
puts "キャンセルしました。"
exit 0
end
end
checker = FileChecker.new(files_list, target_dir, options)
checker.run
end