ftplib.FTP.rmd()

2025-06-06

どのような機能か?

このメソッドは、FTPサーバー上のディレクトリを削除するために使用されます。

使い方

FTP オブジェクトに接続した後、削除したいディレクトリのパスを引数として rmd() メソッドに渡します。

例:

from ftplib import FTP

try:
    # FTPサーバーに接続
    ftp = FTP('ftp.example.com')
    ftp.login('your_username', 'your_password')

    # 削除したいディレクトリ
    directory_to_remove = '古いディレクトリ'

    # ディレクトリを削除
    ftp.rmd(directory_to_remove)
    print(f"ディレクトリ '{directory_to_remove}' が削除されました。")

except Exception as e:
    print(f"エラーが発生しました: {e}")

finally:
    if 'ftp' in locals() and ftp.sock: # 接続が確立されている場合のみクローズ
        ftp.quit()
  • エラーハンドリング: ネットワークの問題や権限の問題など、さまざまな理由でディレクトリの削除が失敗する可能性があります。そのため、try-except ブロックを使用してエラーハンドリングを行うことが重要です。
  • ファイル削除との違い: ファイルを削除する場合は、ftplib.FTP.delete() メソッドを使用します。rmd() はディレクトリ専用です。
  • 空のディレクトリのみ削除可能: ほとんどのFTPサーバーでは、rmd() メソッドを使ってディレクトリを削除するには、そのディレクトリがである必要があります。ディレクトリ内にファイルやサブディレクトリが存在する場合、通常はエラーが発生します。もし中に何かがある場合は、まずそれらを削除してから rmd() を実行する必要があります。


ftplib.FTP.rmd() はFTPサーバー上のディレクトリを削除するためのメソッドですが、FTPプロトコルの特性上、いくつかの一般的なエラーが発生しやすいです。

ftplib.error_perm: 550 The directory is not empty. または類似のエラー

エラーの理由
これは最も一般的なエラーです。FTPプロトコルのRMD (Remove Directory) コマンドは、通常、空のディレクトリしか削除できません。削除しようとしているディレクトリ内にファイルやサブディレクトリが存在する場合、このエラーが発生します。FTPサーバーによってはエラーメッセージが異なる場合がありますが、内容は「ディレクトリが空ではない」というものです。

トラブルシューティング

ftplib.error_perm: 550 Permission denied. または類似のエラー

エラーの理由
FTPユーザーに、指定されたディレクトリを削除するための十分な権限がない場合に発生します。

トラブルシューティング

  • 正しい認証情報を使用しているか確認する
    ログインに使用しているユーザー名とパスワードが正しいことを再確認してください。
  • FTPユーザーの権限を確認する
    使用しているFTPアカウントが、対象のディレクトリを削除できる権限を持っているか、FTPサーバーの管理者またはホスティングプロバイダーに確認してください。

ftplib.error_perm: 550 No such file or directory. または類似のエラー

エラーの理由
指定されたパスにディレクトリが存在しない場合に発生します。パスのタイプミスや、すでに削除されているディレクトリを削除しようとしている可能性があります。

トラブルシューティング

  • ディレクトリの存在を確認する
    ftp.nlst()ftp.dir() などで、ディレクトリが存在するかどうか事前に確認するロジックを追加できます。
  • パスを確認する
    削除しようとしているディレクトリのパスが正確であることを確認してください。相対パスを使用している場合は、現在の作業ディレクトリ (CWD) からの相対パスが正しいか確認してください。ftp.pwd() で現在の作業ディレクトリを確認できます。

ftplib.all_errors (一般的なFTPエラー)

主なFTP応答コードの分類

  • 5xx (Permanent Negative Completion Reply)
    コマンドは受け入れられず、同じ方法で再試行しても成功しないことを示します。rmd() の場合、権限エラー (550 Permission denied) やディレクトリが存在しないエラー (550 No such file or directory) がこれに該当します。
  • 4xx (Transient Negative Completion Reply)
    コマンドは受け入れられなかったが、一時的なエラーであり、再試行すれば成功する可能性があることを示します。
  • 3xx (Positive Intermediate Reply)
    コマンドが受け入れられ、さらなる情報が必要であることを示します。
  • 2xx (Positive Completion Reply)
    要求されたアクションが正常に完了したことを示します。
  • 1xx (Positive Preliminary Reply)
    処理が開始され、さらなる入力が必要であることを示します。

トラブルシューティング

  • デバッグレベルの設定
    ftp.set_debuglevel(1) または ftp.set_debuglevel(2) を呼び出すことで、FTPのやり取りを詳細に出力させることができます。これにより、サーバーからの応答コードやメッセージを正確に確認でき、問題の特定に役立ちます。
    from ftplib import FTP
    
    ftp = FTP('ftp.example.com')
    ftp.set_debuglevel(2) # デバッグ出力を有効にする (0:なし, 1:簡易, 2:詳細)
    # ... ログイン、rmd() などの操作 ...
    ftp.quit()
    
  • エラーメッセージを読み解く
    エラーが発生した場合、ftplib.all_errors で捕捉される例外オブジェクトには、FTPサーバーからの具体的な応答コードとメッセージが含まれています。このメッセージがトラブルシューティングの鍵となります。
    from ftplib import FTP, all_errors
    
    try:
        ftp = FTP('ftp.example.com')
        ftp.login('your_username', 'your_password')
        ftp.rmd('non_existent_directory')
    except all_errors as e:
        print(f"FTP操作中にエラーが発生しました: {e}")
        # 例: e の値が "550 No such file or directory." のようになる
        # これを元に原因を特定する
    finally:
        if 'ftp' in locals() and ftp.sock:
            ftp.quit()
    

ネットワーク関連のエラー (ConnectionRefusedError, TimeoutError など)

エラーの理由
FTPサーバーに接続できない、または接続が途中で切断された場合に発生します。

  • タイムアウト値の調整
    FTP() オブジェクトの作成時や connect() メソッドで timeout パラメータを設定し、接続や操作のタイムアウト時間を調整してみてください。
    ftp = FTP('ftp.example.com', timeout=30) # 30秒のタイムアウトを設定
    
  • ファイアウォールの設定
    クライアント側(Pythonスクリプトを実行しているマシン)またはサーバー側のファイアウォールがFTP接続をブロックしていないか確認してください。
  • ホスト名/IPアドレスとポート番号を確認
    接続しようとしているホスト名またはIPアドレス、およびポート番号(通常は21)が正しいか確認してください。
  • FTPサーバーの稼働状況を確認
    サーバーがオンラインであり、FTPサービスが稼働していることを確認してください。


ftplib.FTP.rmd() はFTPサーバー上の空のディレクトリを削除するために使われます。最も一般的な使用例と、関連するエラーハンドリング、そして少し高度な再帰的な削除の例も見ていきましょう。

例1: 空のディレクトリを削除する基本的な例

この例では、指定されたFTPサーバーに接続し、既存の空のディレクトリを削除します。

from ftplib import FTP, all_errors # エラーを捕捉するために all_errors をインポート

# FTPサーバーの接続情報
FTP_HOST = 'your_ftp_server.com'
FTP_USER = 'your_username'
FTP_PASS = 'your_password'
DIRECTORY_TO_REMOVE = 'empty_test_directory' # 削除したい空のディレクトリ名

ftp = None # finally ブロックで確実にクローズするために初期化

try:
    # 1. FTPサーバーに接続
    print(f"FTPサーバー '{FTP_HOST}' に接続中...")
    ftp = FTP(FTP_HOST)
    
    # 2. ログイン
    print(f"ユーザー '{FTP_USER}' でログイン中...")
    ftp.login(FTP_USER, FTP_PASS)
    print("ログイン成功!")

    # 3. 削除したいディレクトリが存在するか確認(オプション)
    # ディレクトリが存在しない場合、rmd() はエラーを発生させます。
    # 削除前に存在確認を行うと、エラーメッセージをより分かりやすくできます。
    print(f"ディレクトリ '{DIRECTORY_TO_REMOVE}' の存在を確認中...")
    try:
        # nlst() はディレクトリ内のアイテムをリストします。
        # ディレクトリが存在しない場合、エラーが発生することが多いです。
        # ただし、FTPサーバーの実装によっては空リストを返すこともあります。
        # 最も確実なのは、一度 cwd() を試すことです。
        ftp.cwd(DIRECTORY_TO_REMOVE) # ディレクトリに移動できるか試す
        ftp.cwd('..') # 元のディレクトリに戻る
        print(f"ディレクトリ '{DIRECTORY_TO_REMOVE}' が存在します。")
    except all_errors as e:
        if "550" in str(e) and ("no such file or directory" in str(e).lower() or "not found" in str(e).lower()):
            print(f"エラー: ディレクトリ '{DIRECTORY_TO_REMOVE}' は存在しません。削除をスキップします。")
            ftp.quit() # 存在しないので、ここで終了
            exit()
        else:
            raise # その他のエラーは再スロー

    # 4. ディレクトリを削除
    print(f"ディレクトリ '{DIRECTORY_TO_REMOVE}' を削除中...")
    ftp.rmd(DIRECTORY_TO_REMOVE)
    print(f"ディレクトリ '{DIRECTORY_TO_REMOVE}' が正常に削除されました。")

except all_errors as e:
    # FTP関連のすべてのエラーを捕捉
    print(f"\nエラーが発生しました: {e}")
    if "550 The directory is not empty." in str(e):
        print("ヒント: ディレクトリは空ではありません。rmd() は空のディレクトリしか削除できません。")
    elif "550 Permission denied." in str(e):
        print("ヒント: ディレクトリを削除するための権限がありません。FTPユーザーの権限を確認してください。")
    elif "No such file or directory" in str(e):
        print("ヒント: 指定されたディレクトリが存在しません。パスを確認してください。")
    else:
        print("不明なFTPエラーです。エラーメッセージを確認してください。")
except Exception as e:
    # その他の予期せぬエラーを捕捉
    print(f"\n予期せぬエラーが発生しました: {e}")
finally:
    # 5. FTP接続をクローズ
    if ftp and ftp.sock: # ftp オブジェクトが存在し、ソケットがオープンしている場合のみ
        print("FTP接続をクローズ中...")
        ftp.quit()
        print("接続クローズ完了。")

このコードを実行する前の準備

  1. your_ftp_server.comyour_usernameyour_password を実際のFTPサーバー情報に置き換えてください。
  2. FTPサーバー上に empty_test_directory という名前の空のディレクトリを作成してください。

例2: 空でないディレクトリを削除しようとした場合のエラーハンドリング

rmd() は空のディレクトリしか削除できないため、空でないディレクトリを削除しようとするとエラーが発生します。この例ではそのエラーを捕捉します。

from ftplib import FTP, all_errors

FTP_HOST = 'your_ftp_server.com'
FTP_USER = 'your_username'
FTP_PASS = 'your_password'
NON_EMPTY_DIRECTORY = 'non_empty_test_directory' # ファイルがいくつか入っているディレクトリ

ftp = None

try:
    ftp = FTP(FTP_HOST)
    ftp.login(FTP_USER, FTP_PASS)
    print(f"ディレクトリ '{NON_EMPTY_DIRECTORY}' を削除しようとしています...")
    
    # 削除を試みる(このディレクトリにはファイルがあるので失敗するはず)
    ftp.rmd(NON_EMPTY_DIRECTORY)
    print(f"ディレクトリ '{NON_EMPTY_DIRECTORY}' が正常に削除されました。(通常はここまで来ない)")

except all_errors as e:
    print(f"\nエラーが発生しました: {e}")
    if "550 The directory is not empty." in str(e):
        print(f"想定通り: ディレクトリ '{NON_EMPTY_DIRECTORY}' は空ではないため、削除できませんでした。")
        print("先にディレクトリ内のすべてのファイルを削除する必要があります。")
    else:
        print("その他のFTPエラーが発生しました。")
except Exception as e:
    print(f"\n予期せぬエラーが発生しました: {e}")
finally:
    if ftp and ftp.sock:
        ftp.quit()

このコードを実行する前の準備

  1. 実際のFTPサーバー情報に置き換えてください。
  2. FTPサーバー上に non_empty_test_directory という名前のディレクトリを作成し、その中にいくつかのファイルを入れてください。

例3: 再帰的にディレクトリとファイルを削除する(高度な例)

ftplib にはディレクトリを再帰的に削除する組み込み関数はありません。そのため、これは手動で実装する必要があります。この関数は、まずディレクトリ内のすべてのファイルとサブディレクトリを削除し、その後で空になったディレクトリを rmd() で削除します。

重要
この実装はFTPサーバーの挙動に依存します。特に nlst()delete() の挙動はサーバーによって微妙に異なる場合があります。また、大規模なディレクトリ構造ではパフォーマンスの問題が発生する可能性があります。

import os
from ftplib import FTP, all_errors

FTP_HOST = 'your_ftp_server.com'
FTP_USER = 'your_username'
FTP_PASS = 'your_password'
TARGET_DIRECTORY = 'directory_to_delete_recursively' # 再帰的に削除したいディレクトリ

def delete_ftp_directory_recursive(ftp_obj, path):
    """
    FTPサーバー上のディレクトリとその内容を再帰的に削除します。
    """
    print(f"--- '{path}' の内容を削除中 ---")
    
    try:
        # ディレクトリ内のアイテムをリストアップ
        # nlst() はファイルとディレクトリの両方を返すことが多いが、
        # FTPサーバーによってはディレクトリの場合に '/' を付与しないこともある。
        # MLSD (Machine Listing) はより正確だが、ftplibでは直接サポートされていない。
        items = []
        try:
            items = ftp_obj.nlst(path)
        except all_errors as e:
            if "550" in str(e) and "no such file or directory" in str(e).lower():
                print(f"警告: ディレクトリ '{path}' は存在しません。")
                return # 存在しないので何もしない
            else:
                raise # その他のエラーは再スロー

        if not items:
            print(f"ディレクトリ '{path}' は既に空です。")
            
        for item in items:
            # FTPパスは '/' で区切られるため、os.path.join は直接使えない場合がある。
            # ただし、os.path.basename はローカルパスでもFTPパスでも使える。
            # item は 'dirname/filename' の形式で返されることが多い。
            # base_name = os.path.basename(item)
            # full_item_path = f"{path}/{base_name}" if path else base_name
            # nlst() が返す item は通常既にフルパスなので、そのまま使う。
            full_item_path = item 

            # . と .. はスキップ(これらは親ディレクトリと現在のディレクトリを示す)
            if full_item_path.endswith('/.') or full_item_path.endswith('/..') or full_item_path == path:
                continue

            print(f"アイテム '{full_item_path}' を処理中...")
            try:
                # まずファイルとして削除を試みる
                ftp_obj.delete(full_item_path)
                print(f"ファイルを削除: {full_item_path}")
            except all_errors as e:
                # ファイルとして削除できなかった場合(通常はディレクトリの場合)
                if "550" in str(e) and ("not a plain file" in str(e).lower() or "directory" in str(e).lower()):
                    # ディレクトリと判断し、再帰的に削除
                    print(f"'{full_item_path}' はディレクトリのようです。再帰的に処理します。")
                    delete_ftp_directory_recursive(ftp_obj, full_item_path)
                else:
                    # その他のエラーは再スロー
                    print(f"アイテム '{full_item_path}' の処理中に不明なエラーが発生しました: {e}")
                    raise e
        
        # すべての内容が削除されたら、ディレクトリ自体を削除
        print(f"ディレクトリ '{path}' が空になりました。削除します。")
        ftp_obj.rmd(path)
        print(f"ディレクトリ '{path}' を削除しました。")

    except all_errors as e:
        print(f"エラー: ディレクトリ '{path}' の削除中に問題が発生しました: {e}")
        # 権限エラーや存在しないエラーなど、具体的なエラーに応じて処理を分岐
        if "550 Permission denied" in str(e):
            print(f"権限エラー: '{path}' を削除する権限がありません。")
        elif "550 No such file or directory" in str(e):
            print(f"エラー: '{path}' が存在しないか、既に削除されています。")
        else:
            print(f"不明なFTPエラー: {e}")
    except Exception as e:
        print(f"予期せぬエラー: {e}")


# --- メイン処理 ---
ftp = None
try:
    ftp = FTP(FTP_HOST)
    ftp.login(FTP_USER, FTP_PASS)
    print(f"FTPサーバー '{FTP_HOST}' にログイン成功。")

    # 再帰的削除を実行
    delete_ftp_directory_recursive(ftp, TARGET_DIRECTORY)
    
except all_errors as e:
    print(f"\n全体的なFTPエラーが発生しました: {e}")
except Exception as e:
    print(f"\n予期せぬエラーが発生しました: {e}")
finally:
    if ftp and ftp.sock:
        print("FTP接続をクローズ中...")
        ftp.quit()
        print("接続クローズ完了。")

このコードを実行する前の準備

  1. 実際のFTPサーバー情報に置き換えてください。
  2. FTPサーバー上に directory_to_delete_recursively という名前のディレクトリを作成し、その中にいくつかのファイルやサブディレクトリ(さらにその中にファイル)を作成して、階層構造を持たせてください。
  • パフォーマンス
    大量のファイルや深くネストされたディレクトリを削除する場合、ネットワークオーバーヘッドにより処理が遅くなる可能性があります。
  • ファイルかディレクトリかの判別
    ftplib には、リモートパスがファイルかディレクトリかを直接判別する標準的な方法がありません。この例では、delete() を試してみて、それが失敗した場合(かつ、エラーメッセージがディレクトリであることを示唆する場合)に再帰呼び出しを行うというヒューリスティックな方法を使用しています。より堅牢なFTPクライアントライブラリ(例: paramiko for SFTP, pysftp など)では、ファイルシステムのメタデータを取得する機能があることが多いです。
  • パスの扱い
    nlst() が返すパスは、FTPサーバーによって相対パスだったりフルパスだったりします。この例では item がフルパスとして返されることを前提にしていますが、もし相対パスが返される場合は os.path.join の代わりに f"{path}/{item}" のようにパスを結合する必要があります。


ftplib.FTP.rmd() の代替方法

  1. SFTP/SCP を使用する (よりセキュアな代替手段)
  2. 外部コマンドを実行する (システム依存の代替手段)
  3. FTPサーバーの管理API/ツールを使用する (特定の環境)

SFTP/SCP を使用する (よりセキュアな代替手段)

FTPはデータが暗号化されないため、セキュリティ上の懸念があります。よりセキュアな方法として、SFTP (SSH File Transfer Protocol) や SCP (Secure Copy) の利用が推奨されます。これらはSSHプロトコル上で動作し、データ転送を暗号化します。PythonでSFTPを扱うには、paramiko ライブラリが最も一般的です。

特徴

  • 前提
    サーバー側でSSH/SFTPサービスが有効になっている必要があります。
  • エラー処理
    FTPに比べてエラーハンドリングが予測しやすい場合があります。
  • 機能
    ファイルやディレクトリの操作(作成、削除、アップロード、ダウンロードなど)が可能です。
  • セキュリティ
    全ての通信が暗号化されます。

Pythonでの例 (paramiko)

まず、paramiko をインストールします。

pip install paramiko

次に、SFTPでディレクトリを削除するコード例です。rmdir メソッドは、FTPの rmd と同様に、通常は空のディレクトリしか削除できません。非空のディレクトリを再帰的に削除するには、手動でその内容を先に削除する必要があります。

import paramiko
import stat # ファイルタイプを判別するために必要

# SFTPサーバーの接続情報
SFTP_HOST = 'your_sftp_server.com'
SFTP_PORT = 22 # 通常SFTPはポート22
SFTP_USER = 'your_username'
SFTP_PASS = 'your_password' # パスワード認証の場合
# または、キー認証の場合:
# PRIVATE_KEY_PATH = '/path/to/your/ssh/private_key' 

DIRECTORY_TO_REMOVE = 'sftp_test_directory' # 削除したいディレクトリ

def sftp_rmdir_recursive(sftp_client, path):
    """
    SFTPサーバー上のディレクトリとその内容を再帰的に削除します。
    """
    try:
        # ディレクトリの内容をリストアップ
        # listdir_attr() はファイルやディレクトリの詳細な属性(is_dirなど)を返します。
        items = sftp_client.listdir_attr(path)
        
        for item in items:
            full_item_path = f"{path}/{item.filename}"
            if stat.S_ISDIR(item.st_mode): # ディレクトリの場合
                print(f"ディレクトリを再帰的に削除: {full_item_path}")
                sftp_rmdir_recursive(sftp_client, full_item_path)
            else: # ファイルの場合
                print(f"ファイルを削除: {full_item_path}")
                sftp_client.remove(full_item_path)
        
        # ディレクトリが空になったら削除
        print(f"ディレクトリを削除: {path}")
        sftp_client.rmdir(path)
        print(f"ディレクトリ '{path}' を正常に削除しました。")

    except FileNotFoundError:
        print(f"エラー: ディレクトリ '{path}' は存在しません。")
    except Exception as e:
        print(f"エラー: SFTP操作中に問題が発生しました: {e}")


# --- メイン処理 ---
transport = None
sftp = None
try:
    print(f"SFTPサーバー '{SFTP_HOST}:{SFTP_PORT}' に接続中...")
    transport = paramiko.Transport((SFTP_HOST, SFTP_PORT))
    
    # パスワード認証の場合
    transport.connect(username=SFTP_USER, password=SFTP_PASS)
    
    # キー認証の場合 (例)
    # private_key = paramiko.RSAKey.from_private_key_file(PRIVATE_KEY_PATH)
    # transport.connect(username=SFTP_USER, pkey=private_key)

    sftp = paramiko.SFTPClient.from_transport(transport)
    print("SFTP接続成功!")

    # 再帰的削除を実行
    sftp_rmdir_recursive(sftp, DIRECTORY_TO_REMOVE)

except paramiko.ssh_exception.AuthenticationException:
    print("認証失敗: ユーザー名またはパスワードが間違っています。")
except paramiko.ssh_exception.SSHException as e:
    print(f"SSH接続エラー: {e}")
except Exception as e:
    print(f"予期せぬエラー: {e}")
finally:
    if sftp:
        sftp.close()
        print("SFTPクライアントをクローズしました。")
    if transport:
        transport.close()
        print("SSHトランスポートをクローズしました。")

外部コマンドを実行する (システム依存の代替手段)

Pythonスクリプトが実行されるサーバー上で、ftp クライアントコマンドや curl などのツールが利用できる場合、subprocess モジュールを使ってこれらの外部コマンドを実行し、ディレクトリを削除することも可能です。

特徴

  • エラーハンドリング
    subprocess の出力(標準出力、標準エラー出力)を解析してエラーを判断する必要があります。
  • セキュリティ
    FTPコマンドラインツールを使用する場合、パスワードがコマンドライン引数として渡される可能性があり、プロセスリストから見えてしまうなどセキュリティリスクがあります。curl の場合は .netrc ファイルなどを使用することでセキュリティを向上できます。
  • システム依存
    スクリプトが実行される環境に、必要なコマンドラインツールがインストールされている必要があります。
  • 簡潔さ
    既存のシェルコマンドの知識を活用できます。

Pythonでの例 (subprocess と ftp コマンド - 非推奨)

注意
この方法はセキュリティリスクが高いため、本番環境での使用は推奨されません

import subprocess

FTP_HOST = 'your_ftp_server.com'
FTP_USER = 'your_username'
FTP_PASS = 'your_password'
DIRECTORY_TO_REMOVE = 'external_cmd_test_directory'

# FTPコマンドの実行(非推奨: パスワードが公開される可能性がある)
# 実際には、非対話モードで rm -rf を実行するために、
# FTPサーバー側でカスタムコマンドが提供されているか、
# もしくは SSH/SFTP 経由でリモートシェルコマンドを実行する必要があります。
# FTPクライアント自体に再帰削除コマンドは通常ありません。

# 例: FTPサーバーでRMDが機能しないが、SSH接続が可能で、
# その上でrm -rfを実行できる場合(SSH経由の削除の方が一般的で安全)
# これはFTPの代替というより、より高度なサーバー操作方法になります。
# もしFTPサーバー自体に特殊なRMDコマンドがあるなら、ftplib.FTP.sendcmd() で送ることも考えられますが稀です。

# あえてFTP CLIツールを使う場合(セキュリティリスク大)
# 多くのFTPクライアントは、対話的な操作を前提としているため、
# 直接コマンドラインで再帰削除を行うのは困難です。
# 以下は概念的なもので、実際に動かすには複雑なパイプ処理やスクリプトが必要になります。
# 例: curl を使ってFTP経由で削除を試みる場合
# curl --user "user:pass" ftp://host/path/to/directory -X "RMD directory"
# これは空のディレクトリにのみ有効です。再帰削除は直接できません。

# より現実的なのは、SFTP経由でSSHコマンドを実行することです。
# 例: paramiko を使ってリモートで `rm -rf` を実行
try:
    print(f"リモートでディレクトリ '{DIRECTORY_TO_REMOVE}' を削除中 (SSH経由)...")
    # SSH接続情報 (SFTPと同じ)
    client = paramiko.SSHClient()
    client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # 本番では known_hosts を使うべき
    client.connect(SFTP_HOST, port=SFTP_PORT, username=SFTP_USER, password=SFTP_PASS)

    # `rm -rf` コマンドでディレクトリを再帰的に削除
    # このコマンドは強力なので、実行には十分な注意が必要です!
    command = f"rm -rf {DIRECTORY_TO_REMOVE}" 
    stdin, stdout, stderr = client.exec_command(command)

    output = stdout.read().decode().strip()
    error = stderr.read().decode().strip()

    if error:
        print(f"コマンド実行エラー: {error}")
    else:
        print(f"ディレクトリ '{DIRECTORY_TO_REMOVE}' が削除されました。(出力: {output})")

except paramiko.ssh_exception.AuthenticationException:
    print("認証失敗: SSHユーザー名またはパスワードが間違っています。")
except paramiko.ssh_exception.SSHException as e:
    print(f"SSH接続エラー: {e}")
except Exception as e:
    print(f"予期せぬエラー: {e}")
finally:
    if 'client' in locals() and client:
        client.close()

FTPサーバーの管理API/ツールを使用する (特定の環境)

FTPサーバーによっては、Webベースの管理インターフェースやAPIを提供している場合があります。例えば、cPanel、Pleskなどのホスティングコントロールパネルは、FTPアカウントの管理だけでなく、ファイルマネージャー機能を提供しており、そこからディレクトリを削除できます。

特徴

  • 機能
    提供される機能はAPIによって異なりますが、再帰的な削除をサポートしていることが多いです。
  • サーバー依存
    特定のホスティング環境やFTPサーバーソフトウェアに限定されます。汎用性が低い場合があります。
  • シンプルさ
    提供されているGUIやAPIを利用するだけで、複雑なプログラミングが不要な場合があります。

Pythonでの例
これは、特定のAPIが存在しない限り、一般的なコード例を示すことは困難です。もし利用しているFTPホスティングプロバイダーがAPIを提供していれば、そのドキュメントを参照してPythonの requests ライブラリなどでHTTPリクエストを送信して操作することになります。

  • ホスティングプロバイダーが特定の管理ツールを提供している場合
    そのツールやAPIの利用が最も簡単かもしれません。
  • スクリプトの実行環境が限定されており、特定のコマンドラインツールが確実に利用できる場合
    subprocess を使う方法も考えられますが、セキュリティとエラーハンドリングの複雑さを考慮する必要があります。
  • FTPのみが利用可能で、空でないディレクトリを削除したいが、再帰的削除ロジックを自分で書きたくない場合
    サーバー側でシェルアクセスが許可されており、rm -rf などのコマンドを実行できるなら、paramiko を使ってSSH経由でリモートコマンドを実行する方法を検討できます。ただし、これはFTPの代替というより、別のプロトコル(SSH)を使うことになります。
  • セキュリティが最優先なら
    paramiko を使ったSFTP/SCP が最も推奨されます。