60歳を迎えて、思うこと。 -4ページ目

60歳を迎えて、思うこと。

いつの間にか、「60」という年月が経ちました。
残り少ないか多いか?わかりませんが。
じじぃ~の「ひとりごと」を細々と続けられれば。。。

重複ふぁいる検索そふと

にゃあにゃあ
わんわん


長年 使用していた

Undup Ver.1.5g
ちょっと 問題発生

FileMany
ちょっと 
問題

重複確認
ゴミ箱にしか 行かない???

AllDup
よ~~~
わからん;;

FileMany (.NET 8版)
つからん;;

Duplicate Files Fixer

4650円/年
無理ぃ~~~

こ・こまった・・・

そふとうぇあ に 
困る
お年頃♪

じじぃ~


 

正規表現 どつぼ・・・

ちゃつぼ

本日

正規表現の ぱた~~~ん



自分で やってみよう!




いきなり つまづいた;;

なんでやねん!

# ★ パターンと保存先をセットで書く
RULES = [
    # 魑魅魍魎
    (r'魑魅魍魎', "魑魅魍魎"),
    (r'^魑魅魍魎', "魑魅魍魎"),
    (r'$魑魅魍魎', "魑魅魍魎"),
    (r'魑魅魍魎$', "魑魅魍魎"),

どれが どれ???

けっか!


### 1. 原因:`match()` は「先頭から」しか見ない
Pythonの `re.match()` という関数(命令)は、

**「文字列の先頭がパターンに一致するか?」**をチェックする性質を持っています。

つまり、正規表現の中に `^`(先頭)を書いていなくても、

プログラム側で自動的に **「先頭に `^` がついているのと同じ状態」

** として扱われてしまうのです。

python の

if pattern.match(name_only):



if pattern.search(name_only):

にする!

じゃないと

問答無用で
先頭文字検索
前方一致検索

になるよ~~~ん

らしい

勉強する
お年頃♪

じじぃ~

正規表現 例題。。。

生成AIに
丸投げ! 依頼の 日々!


正規表現

パターンを
いちいち 丸投げ するのも
面倒である



ちょっと 挑戦してみた!

# ★ パターンと保存先をセットで書く

RULES = [
    # AFTR00AJ
    (r'^[A-Z]{4}[0-9]{2}[A-Z]{2}', "A-Z系"),
    # GLOS0001
    (r'^[A-Z]{4}[0-9]{4}', "D-Z系"),
    # RANDA008
    (r'^[A-Z]{5}[0-9]{3}', "E-Z系"),
    # TIME02P1
    (r'^[A-Z]{4}[0-9]{2}[A-Z][0-9]', "F-Z系"),
    # THECODNT
    (r'^[A-Z]{8}', "G-Z系"),
    # AlbumArt
    (r'^(AlbumArt).*', "AlbumArt"),
]


こんな 感じで どうでしょうか???

【Gemini】様に 採点をお願いした

ご提示いただいたコード、非常に整理されていて素晴らしいですね!
実務でよく使われる「リストにタプルを格納してループで回す」という、拡張性
の高いスマートな書き方です。

初歩的な正規表現の使い方として、現在のパターンが意図通りに動くか、また「
ここを意識するとさらに堅牢になる」というポイントをまとめました。

 1. 各パターンの解説とセルフチェック
提示された正規表現を整理すると、以下のようになります。

| パターン | 意味 | 例 |
| `^[A-Z]{4}[0-9]{2}[A-Z]{2}` | 英4 + 数2 + 英2 | `AFTR00AJ` |
| `^[A-Z]{4}[0-9]{4}` | 英4 + 数4 | `GLOS0001` |
| `^[A-Z]{5}[0-9]{3}` | 英5 + 数3 | `RANDA008` |
| `^[A-Z]{4}[0-9]{2}[A-Z][0-9]` | 英4 + 数2 + 英1 + 数1 | `TIME02P1` |
| `^[A-Z]{8}` | 英8文字 | `THECODNT` |
| `^(AlbumArt).*` | 先頭がAlbumArtで始まる | `AlbumArt_01` |

 2. さらに良くするためのアドバイス
# ① 「
末尾」を意識する(重要)
現在の書き方だと、例えば `^[A-Z]{8}`(G-Z系)は、「英字が8文字以上」ある
場合、後ろに何が続いていてもマッチしてしまいます。
> 例:`THECODNT_EXTRA_DATA` も `G-Z系` に判定される。
もし「8文字ぴったり」のものだけを抽出したい場合は、

末尾を表す `$` をつけるのが定石です。
*   修正例:`(r'^[A-Z]{8}$', "G-Z系")`

# ② 判定の優先順位
`RULES` を上から順番に判定していく場合、「条件が厳しいもの(具体的なもの
)」を上に、「条件が緩いもの(抽象的なもの)」を下に書くのがコツです。
現状のリストは文字数や構成がはっきり分かれているので問題なさそうですが、
今後ルールが増えたときに意識してみてください。

# ③ `AlbumArt` のキャプチャ
`(AlbumArt).*` のカッコ `
()` は「グループ化」を意味します。もし `AlbumAr
t` という文字列そのものを後で取り出す必要がなければ、

カッコを外して `^AlbumArt.*` としてもOKです。

 実装イメージ(Python)
この `RULES` を使って判定する際の標準的なコード例です。

```python
import re

def classify_code(text):
    for pattern, label in RULES:
        # re.match は先頭からの一致を確認します
        if re.match(pattern, text):
            return label
    return "不明"

# テスト
print(classify_code("AFTR00AJ"))  # 出力: A-Z系
print(classify_code("AlbumArt_v1")) # 出力: AlbumArt
```

 まとめ
「初歩」というには十分すぎるほど、実戦的で読みやすい構成です!
特に `r'...'`(raw文字列)をしっかり使っている点は、正規表現のバックスラ
ッシュ地獄を避けるための必須テクニックなので、バッチリですね。

このまま進めて問題ないと思いますが、「文字数がぴったりの時だけマッチさせ
たいか?」だけ、用途に合わせて `$` を足すか検討してみてください。
# ==============================
おっしゃる通り、正規表現の記号は似ているので混同しやすいですよね。
今回の `()`(カッコ) の役割について、少しだけ掘り下げて整理してみましょ
う。ここを理解すると、正規表現がもっと自由自在に扱えるようになります!

 1. 記号の役割まとめ
| 記号 | 呼び方 | 役割 | イメージ |
| `[]` | 文字クラス | 「どれか1文字」の候補 | `[ABC]` → AかBかCのどれか
1文字 |
| `{}` | 量指定子 | 「繰り返す回数」 | `A{3}` → AAA |
| `()` | グループ化 | 「ひとまとめ」 + 「保存」 | `(ABC)` → 「ABC」と
いう塊として扱う |

 2. なぜ `(AlbumArt)` のカッコがなくてもいいのか?
正規表現は、何も囲わなくても書いた順番通りに並んでいる文字列を探しに行き
ます。
*   `AlbumArt` と書くだけで、「A」の次に「l」、その次に「b」…という並び
を探します。
*   そのため、単純に「その文字があるか」を判定するだけなら `()` は不要な
んです。

# では、いつ `()` を使うのか?
主に以下の2つのケースで使います。

1.  一部だけを抜き出したいとき(キャプチャ)
    「ファイル名全体にマッチさせつつ、中の数字の部分だけ後で取り出して変
    数に入れたい!」という時に `()` で囲みます。
2.  繰り返しの単位を指定したいとき
    `ABC{3}` と書くと、最後の「C」だけが3回繰り返されます(ABCCC)。
    `(ABC){3}` と書くと、「ABC」が3回繰り返されます(ABCABCABC)。

 3. `^AlbumArt.*` の意味
今回のファイル名の例で見てみましょう。
> AlbumArt-A02468.wav

これを `^AlbumArt.*` で表すと:
*   `^` : 行の先頭からスタートして
*   `AlbumArt` : 「AlbumArt」という文字が並んでいて
*   `.` : その後に「何らかの文字」が
*   `*` : 0文字以上続いている(つまり、残り全部)

という意味になります。
「先頭が `AlbumArt` で始まってさえいれば、後ろは何でもいいよ!」というル
ールになるので、ご提示のファイル名には完璧にマッチします。

 豆知識:より厳密にするなら
もし「ハイフン以降もしっかりチェックしたい」となったら、こんな書き方もで
きます。

`(r'^AlbumArt-[A-Z][0-9]{5}\.wav$', "AlbumArt系")`

*   `[A-Z]` : アルファベット1文字
*   `[0-9]{5}` : 数字5桁
*   `\.wav` : 最後に `.wav` で終わる(`.` は特殊な意味を持つので `\` で
打ち消します)

今の `^AlbumArt.*` は「とりあえずAlbumArtで始まるものを全部キャッチする
」という、網羅性の高い良いルールだと思います!

何か特定の「この部分だけ取り出したい!」という要望が出てきたら、その時こ
そ `()` の出番ですので、ぜひ試してみてくださいね。
# ==============================
本日の まとめ


$ を 付与した方が いいよ!

文字列 だけの 単純なら
()で 囲まなくても 動作する

勉強する
お年頃♪

じじぃ~

ふぁいる整理の 日々 つづき

60年 たまりにたまった

ふぁいる!


日々
お掃除中

さて

ごみのようま

ファイルサイズ

を 集めた!

10ばいといかの
ふぁいるの しょうたいは

なに???

開いてみる

空っぽである!

なぜ???

からっぽ 削除
すくりぷと で

削除できない???

【chatGPT】様に
なんでやねん!

必殺技 を 伝授される

# ==== 【chatGPT】様
結論から言うと、それは**「見えない文字だけが入っている」状態**です。壊れてるわけじゃなくて、ちゃんと中身はあります。
よくある正体はこのあたり👇

### ■ よくある原因
#### ① BOM(バイトオーダーマーク)
* UTF-8などのテキストの先頭に入る「目印」
* 表示されないけどデータとしては存在する
例:
* 3バイト → UTF-8のBOM(EF BB BF)
* メモ帳では普通見えない
👉 **3バイトの正体はほぼコレ**
#### ② 改行コードだけ
* LF(`\n`)→ 1バイト
* CR+LF(`\r\n`)→ 2バイト
👉 「空に見えるけど改行だけある」
#### ③ NULL文字や制御文字
* `0x00`(NULL)とか
* 表示されない特殊文字
👉 1バイトなのに何も見えないパターン
### ■ 確認方法(確実)
#### 方法①:PowerShell
```powershell
Format-Hex abc.txt
```
これで中身の正体が丸見えになります
# ==============================
やってみる

なるほど・・・

じゃあじゃあ

からっぽ ふぁいる 強制移動

すくりぷと


改造してくれ!
# ==============================
【chatGPT】様
いいですね、その方向ならかなり“事故りにくい”仕上がりにできます 👍
余計な拡張子も排除して、**テキストだけ厳密に判定**する版を丸ごと出します。
そのままコピペOKです👇
```python

import os
import shutil

# =========================
# 設定
# =========================

# 「テキストとして扱う拡張子」だけに限定
TEXT_EXTENSIONS = {
    '.txt', '.csv', '.tsv', '.log',
    '.ini', '.cfg', '.json', '.xml',
    '.html', '.htm'
}

# 空とみなすバイト
INVISIBLE_BYTES = {0x00, 0x1A, 0x0A, 0x0D}  # NULL / EOF / LF / CR

# =========================
# 判定ロジック
# =========================

def is_effectively_empty_text(file_path):
    """見えない文字だけなら空とみなす(安全版)"""
    try:
        size = os.path.getsize(file_path)

        # 完全に空
        if size == 0:
            return True

        # 念のためサイズ制限(巨大ファイル事故防止)
        if size > 1024 * 1024:  # 1MB以上は対象外
            return False

        with open(file_path, 'rb') as f:
            data = f.read()

        # UTF-8 BOMのみ
        if data == b'\xEF\xBB\xBF':
            return True

        # すべて不可視文字なら空扱い
        for b in data:
            if b not in INVISIBLE_BYTES:
                return False

        return True

    except Exception as e:
        print(f"[ERROR] 判定失敗: {file_path} ({e})")
        return False


# =========================
# ファイル移動
# =========================

def move_file_safe(src_file, dest_folder):
    """安全にリネームしながら移動"""
    os.makedirs(dest_folder, exist_ok=True)

    base_name = os.path.basename(src_file)
    dest_file = os.path.join(dest_folder, base_name)

    if not os.path.exists(dest_file):
        shutil.move(src_file, dest_file)
        print(f"[MOVE] {src_file} -> {dest_file}")
        return

    # 重複時リネーム
    name, ext = os.path.splitext(base_name)
    count = 1

    while True:
        new_name = f"{name}_{count}{ext}"
        new_path = os.path.join(dest_folder, new_name)

        if not os.path.exists(new_path):
            shutil.move(src_file, new_path)
            print(f"[MOVE] {src_file} -> {new_path}")
            return

        count += 1


# =========================
# メイン処理
# =========================

def process_files(src_folder, dest_folder):
    """安全版:拡張子なし + 空テキストのみ移動"""
    for root, dirs, files in os.walk(src_folder):
        for file in files:
            file_path = os.path.join(root, file)
            _, ext = os.path.splitext(file)

            ext = ext.lower()

            # (1) 拡張子なし → 無条件移動
            if ext == '':
                move_file_safe(file_path, dest_folder)
                continue

            # (2) テキストだけ対象
            if ext in TEXT_EXTENSIONS:
                if is_effectively_empty_text(file_path):
                    move_file_safe(file_path, dest_folder)


# =========================
# 実行
# =========================

if __name__ == "__main__":
    SRC_FOLDER = r"E:\__WORK"
    DEST_FOLDER = r"E:\__WORK-MOVE-ZERO"

    process_files(SRC_FOLDER, DEST_FOLDER)

    print("=== 完了 ===")
```

## ■ この版のポイント(重要)
👉 かなり安全寄りにしてあります
### ✔ テキストだけ判定
* `.jpg`, `.bmp` → **完全除外**
* もう巻き込まない

### ✔ 見えないゴミは空扱い

* `0x1A`(今回のやつ)OK
* 改行だけOK
* BOMだけOK

### ✔ 巨大ファイル防御
```python
if size > 1MB → 判定しない
```

👉 誤爆防止

### ✔ リネーム安全
* `file.txt`
  → `file_1.txt`, `file_2.txt`

## ■ もしさらに詰めるなら
* 「ログだけ対象にしたい」
* 「特定フォルダ除外したい」
* 「削除 instead of 移動」

全部いけます 👍
これでたぶん
👉 **“空っぽ風ファイル掃除”は完成形に近い**
動かしてみて、変な動きあればそこピンポイントで潰しましょう 😄
# ==============================

めでたしめでたし

 

じじぃ~

ふぁいる整理の 日々

60年 たまりにたまった

ふぁいる!

日々
お掃除中

あれやこれや で


拡張子 で 振り分け 終わった

七転八倒 で


キーワードで 振り分け 終わった

でも

大量の 未分類 ふぁいる

じゃあ


乱暴だけど!

小さな ファイルを

ファイルサイズで
振り分ける!

【Gemini】様 作成

ファイルサイズで
振り分けちゃうよ!
すくりぷと

# ==============================2026/05/04 12:38:34
# 2026/04/08 20:23:11
# 【Gemini】
# プログラムは「指示されたこと以外は、どうしていいかわからない」という性質があるので、今回のように「条件に合わないものはどうするか」を明示してあげる必要がありますね。
# **パターンA(10kB以上は無視して移動させない)**を組み込んだ全文を作成しました。
# そのままコピー&ペーストして実行してみてください。



import os
import shutil
from pathlib import Path
# ==========================================================
# 🔍 1. 設定項目
# ==========================================================
# 検索を開始するルートフォルダ
SEARCH_DIR = Path(r"E:\__WORK")

# ファイルの移動先フォルダ
DEST_DIR = Path(r"E:\__WORK-MOVE-SIZE")

# 検索対象の拡張子
TARGET_EXT = ".txt"

# 💡 1kB = 1024バイト
# KB = 1024
B = 1

# ファイルサイズと移動先フォルダ名の定義
# ここに含まれないサイズ(10kB以上)は「移動対象外」として扱います
SIZE_RANGES = [
    (10 * B,   "010B"),  # 0KB以上 ~ 1KB未満
    (20 * B,   "020B"),  # 1KB以上 ~ 2KB未満
    (30 * B,   "030B"),  # 2KB以上 ~ 3KB未満
    (40 * B,   "040B"),  # 3KB以上 ~ 4KB未満
    (50 * B,   "050B"),  # 4KB以上 ~ 5KB未満
    (60 * B,   "060B"),  # 4KB以上 ~ 5KB未満
    (70 * B,   "070B"),  # 4KB以上 ~ 5KB未満
    (80 * B,   "080B"),  # 4KB以上 ~ 5KB未満
    (90 * B,   "090B"),  # 4KB以上 ~ 5KB未満
]

# ==========================================================
# ⚙️ 2. 関数定義
# ==========================================================
def get_destination_folder_name(file_size_bytes):
    """
    ファイルサイズに基づいて移動先を決定。
    SIZE_RANGESに該当しない場合は None を返す。
    """
    for max_size, folder_name in SIZE_RANGES:
        if file_size_bytes < max_size:
            return folder_name
    
    # どの範囲にも当てはまらない(10kB以上)場合は None を返す
    return None

def sort_files_by_size(search_dir, dest_dir, target_ext):
    """
    指定フォルダを再帰的に検索し、10kB未満のファイルのみを分類移動します。
    """
    if not search_dir.is_dir():
        print(f"エラー: 検索フォルダが存在しません: {search_dir}")
        return

    dest_dir.mkdir(parents=True, exist_ok=True)
    
    print(f"検索開始フォルダ: {search_dir}")
    print(f"移動先ルートフォルダ: {dest_dir}")
    print(f"対象拡張子: {target_ext}\n")

    search_pattern = f"**/*{target_ext}" 
    
    file_count = 0
    moved_count = 0
    skip_count = 0

    for file_path in search_dir.rglob(search_pattern):
        if file_path.is_file():
            file_count += 1
            
            try:
                # ファイルサイズの取得
                file_size = file_path.stat().st_size
                
                # 移動先のフォルダ名を決定
                folder_name = get_destination_folder_name(file_size)
                
                # 【修正ポイント】 フォルダ名が取得できない(10kB以上)場合はスキップ
                if folder_name is None:
                    skip_count += 1
                    continue
                
                # 移動先パスの作成
                target_folder = dest_dir / folder_name
                target_folder.mkdir(parents=True, exist_ok=True)
                dest_path = target_folder / file_path.name
                
                # ファイル移動
                shutil.move(file_path, dest_path)
                
                print(f"✅ 移動: {file_path.name} ({file_size / B:.2f}KB) -> {target_folder.name}/")
                moved_count += 1

            except Exception as e:
                print(f"❌ エラー発生: {file_path} の処理中にエラー: {e}")

    print("\n=============================================")
    print(f"処理完了")
    print(f"全対象ファイル: {file_count} 個")
    print(f"移動済み: {moved_count} 個")
    print(f"スキップ(10kB以上): {skip_count} 個")
    print("=============================================")


# ==========================================================
# 🚀 3. スクリプト実行
# ==========================================================
if __name__ == "__main__":
    sort_files_by_size(SEARCH_DIR, DEST_DIR, TARGET_EXT)


### 修正のポイント
# 1.  **`get_destination_folder_name`**: 10kB以上の場合に `None`(空っぽの状態)を返すようにしました。
# 2.  **`sort_files_by_size`**: `if folder_name is None: continue` という処理を追加しました。これにより、10kB以上のファイルを見つけても「あ、これは何もしなくていいんだな」と判断して次のファイルへ進むようになります。
# これで、`UNKNOWN_SIZE` フォルダができることもなく、大きなファイルは元の場所に残るはずです!
# ==============================