ftplib.FTP.mkd()だけじゃない!PythonでのFTP/SFTPディレクトリ作成術

2025-06-06

目的
FTP サーバー上に新しいディレクトリを作成します。

引数

  • dirname: (必須) 作成したいディレクトリの名前を文字列で指定します。この名前は、FTP サーバー上での絶対パスまたはカレントディレクトリからの相対パスのいずれでも構いません。

戻り値

  • 通常、ディレクトリが正常に作成されたことを示すサーバーからの応答文字列(多くの場合、ステータスコードとメッセージを含む)を返します。

動作
mkd() メソッドは、FTP の MKD コマンドをサーバーに送信します。サーバーは指定された dirname で新しいディレクトリを作成しようとします。

使用例

from ftplib import FTP

try:
    # FTPサーバーに接続
    ftp = FTP('ftp.example.com') # ご自身のFTPサーバーのアドレスに置き換えてください
    ftp.login('username', 'password') # ご自身のユーザー名とパスワードに置き換えてください

    # 新しいディレクトリを作成
    new_directory_name = 'my_new_folder'
    response = ftp.mkd(new_directory_name)
    print(f"ディレクトリ作成の応答: {response}")

    # 作成したディレクトリに移動することも可能
    # ftp.cwd(new_directory_name)

    # 接続を閉じる
    ftp.quit()
    print(f"ディレクトリ '{new_directory_name}' が正常に作成されました。")

except Exception as e:
    print(f"エラーが発生しました: {e}")
  • パスの指定
    • 相対パス: ftp.mkd('my_new_folder') はカレントディレクトリ(ftp.cwd() で変更できる)に my_new_folder を作成します。
    • 絶対パス: ftp.mkd('/path/to/another/location/my_new_folder') は指定された絶対パスにディレクトリを作成します。ただし、そのパスの親ディレクトリが既に存在している必要があります。
  • 既存のディレクトリ
    指定された名前のディレクトリがすでに存在する場合、サーバーはエラーを返すことがあります。この場合も、ftplib.Error などの例外が発生します。
  • エラー処理
    ネットワークの問題、無効なディレクトリ名、または権限不足など、さまざまな理由でディレクトリ作成が失敗する可能性があります。そのため、try...except ブロックを使用して適切にエラー処理を行うことが重要です。
  • 権限
    mkd() を使用してディレクトリを作成するには、FTP サーバー上で適切な書き込み権限が必要です。権限がない場合、ftplib.Error または ftplib.all_errors (Python 3.3以降では ftplib.FTPPermErrorftplib.FTPDirError など、より具体的な例外が定義されています) のような例外が発生します。


ftplib.FTP.mkd() は FTP サーバーと通信してディレクトリを作成するため、ネットワーク、サーバー設定、権限など、さまざまな要因によってエラーが発生する可能性があります。

主に発生するエラーは、FTP サーバーからの応答コードに基づいた ftplib の例外として現れます。

ftplib.error_perm: 550 (Permission denied / ディレクトリ作成不可)

原因

  • ファイル名/ディレクトリ名の競合
    同じ名前のファイルやディレクトリが既に存在する場合。
  • 無効なパス
    指定したパスが無効であるか、存在しない親ディレクトリの中に作成しようとしている場合です。例えば、/nonexistent_dir/new_folder のように、/nonexistent_dir が存在しない場合に発生します。FTPでは通常、ネストされたディレクトリを一括で作成することはできません。
  • 権限不足
    FTP ユーザーが指定されたパスにディレクトリを作成する権限を持っていない場合に最もよく発生します。FTP サーバーの設定で、そのユーザーの書き込み権限が制限されている可能性があります。

トラブルシューティング

  • 既存のディレクトリとの競合
    mkd() を呼び出す前に、ftp.nlst() などで同じ名前のディレクトリが既に存在しないか確認し、存在する場合はスキップするか、別名を試すようにロジックを追加します。
  • ディレクトリ名の確認
    無効な文字が含まれていないか、予約語(... 以外)を使用していないか確認します。
  • FTP ユーザーの権限を確認
    FTP サーバーの管理画面や設定ファイルで、使用している FTP ユーザーにディレクトリ作成(書き込み)権限が付与されているか確認してください。必要であれば、権限を付与または変更してください。

ftplib.error_temp: 4xx (一時的なエラー)

原因

  • 例えば 421 Service not available, closing control connection. のようなメッセージが出ることがあります。
  • サーバーが一時的に利用できない、過負荷である、または一時的なネットワーク問題が発生している可能性があります。

トラブルシューティング

  • タイムアウト設定の調整
    接続や操作のタイムアウトが短すぎる場合、サーバーの応答が遅いとエラーになることがあります。ftplib.FTP() コンストラクタや connect() メソッドの timeout パラメータを調整してみてください。
  • サーバーの状態を確認
    FTP サーバーが正常に稼働しているか、システム管理者やホスティングプロバイダーに確認してください。
  • 再試行
    しばらく待ってから操作を再試行してみてください。一時的な問題であれば解決することがあります。

原因

  • ログインしていない
    サーバーにログインせずに mkd() を呼び出した場合。
  • 接続が切れている
    mkd() を呼び出す前に FTP 接続が切断されていた場合。
  • 予期しないサーバー応答
    サーバーが FTP プロトコル仕様に準拠しない応答を返した場合。

トラブルシューティング

  • サーバーのログを確認
    FTP サーバーのログファイルに、より詳細なエラー情報が記録されている場合があります。サーバー管理者に協力を求めてください。
  • 接続状態の確認
    mkd() を呼び出す前に、ftp.login() が成功していることを確認してください。
  • デバッグレベルの設定
    ftp.set_debuglevel(2) を設定すると、FTP コマンドの送受信ログが表示され、サーバーからの具体的なエラーメッセージを確認できます。これにより、問題の特定が容易になります。
    import ftplib
    import sys
    
    ftp = ftplib.FTP('ftp.example.com')
    ftp.set_debuglevel(2) # デバッグレベルを2に設定 (詳細なログ出力)
    # ... ログインやmkd()の処理 ...
    ftp.set_debuglevel(0) # デバッグレベルを0に戻す
    

TypeError (引数の型が間違っている)

原因

  • mkd() に文字列以外の引数(例: None、数値、リストなど)を渡した場合。

トラブルシューティング

  • mkd() に渡す引数が必ず文字列であることを確認してください。

    # 悪い例: ディレクトリ名がNoneになっている可能性がある
    # dir_name = get_directory_name_from_somewhere()
    # ftp.mkd(dir_name)
    
    # 良い例: dir_nameが文字列であることを確認
    dir_name = "my_new_folder"
    if isinstance(dir_name, str):
        ftp.mkd(dir_name)
    else:
        print("Error: Directory name must be a string.")
    

全体のエラーハンドリングのベストプラクティス

ftplib の操作では、広範なエラーを捕捉するために ftplib.all_errors を使用するのが一般的です。

from ftplib import FTP, all_errors

ftp = None # ftpオブジェクトの初期化

try:
    ftp = FTP('ftp.example.com')
    ftp.login('username', 'password')

    new_directory = 'my_new_folder'
    response = ftp.mkd(new_directory)
    print(f"ディレクトリ作成の応答: {response}")

except all_errors as e:
    # ftplibに関するすべてのエラーをキャッチ
    print(f"FTP操作中にエラーが発生しました: {e}")
    # エラーの種類に応じて、より詳細な処理を行うことも可能
    if isinstance(e, ftplib.error_perm):
        print("権限の問題または無効なパスが原因かもしれません。")
    elif isinstance(e, ftplib.error_temp):
        print("サーバーが一時的に利用できない可能性があります。しばらくしてから再試行してください。")
    elif "550" in str(e): # 特定の応答コードを文字列で検索する
        print("ディレクトリ作成の権限がありません、またはパスが無効です。")
    # 必要に応じて、ここで接続をクローズするなどのクリーンアップを行う
    if ftp:
        try:
            ftp.quit() # エラー時にもクリーンに接続を閉じる試み
        except all_errors as quit_e:
            print(f"FTP切断中にエラーが発生しました: {quit_e}")
except Exception as e:
    # その他の予期せぬエラーをキャッチ
    print(f"予期せぬエラーが発生しました: {e}")
finally:
    # 処理の成功・失敗にかかわらず、最終的に接続を閉じる
    if ftp:
        try:
            ftp.quit()
        except all_errors as quit_e:
            print(f"最終的なFTP切断中にエラーが発生しました: {quit_e}")


ftplib.FTP.mkd() は FTP サーバー上に新しいディレクトリを作成するために使用されます。以下に、基本的な使い方から、エラーハンドリング、複数のディレクトリ作成、パスの指定まで、様々なシナリオでの例を示します。

前提
以下のコードを実行するには、有効な FTP サーバーのアドレス、ユーザー名、パスワードが必要です。'ftp.example.com', 'username', 'password' はご自身の情報に置き換えてください。

例1: 基本的なディレクトリ作成

最も基本的なディレクトリ作成の例です。

from ftplib import FTP

ftp = None # ftpオブジェクトをNoneで初期化

try:
    # 1. FTP サーバーに接続
    # ホスト名と、必要であればポート番号 (デフォルトは21) を指定します。
    # タイムアウトを秒単位で設定することも推奨されます。
    ftp = FTP('ftp.example.com', timeout=10)
    print("FTPサーバーに接続しました。")

    # 2. ログイン
    # ユーザー名とパスワードでログインします。
    ftp.login('username', 'password')
    print("FTPサーバーにログインしました。")

    # 3. 新しいディレクトリを作成
    new_directory_name = 'my_first_folder'
    print(f"ディレクトリ '{new_directory_name}' を作成しようとしています...")
    
    # mkd() メソッドを呼び出してディレクトリを作成します。
    # 成功すると、サーバーからの応答文字列が返されます。
    response = ftp.mkd(new_directory_name)
    print(f"ディレクトリ作成の応答: {response}")
    print(f"ディレクトリ '{new_directory_name}' が正常に作成されました。")

except Exception as e:
    # 接続、ログイン、ディレクトリ作成中のあらゆるエラーを捕捉します。
    print(f"エラーが発生しました: {e}")

finally:
    # 接続が確立されている場合、必ずクローズします。
    if ftp:
        try:
            ftp.quit() # 接続を正常に終了します
            print("FTP接続を閉じました。")
        except Exception as e:
            print(f"FTP接続のクローズ中にエラーが発生しました: {e}")

解説

  1. ftplib.FTP() で FTP オブジェクトを作成し、サーバーに接続します。timeout を設定することで、接続が長時間待機するのを防ぎます。
  2. ftp.login() でユーザー認証を行います。
  3. ftp.mkd('my_first_folder') でサーバーに 'my_first_folder' という名前の新しいディレクトリを作成するよう指示します。
  4. try...except...finally ブロックを使って、エラーが発生した場合でも安全に接続を閉じられるようにしています。

例2: エラーハンドリングの強化 (権限エラー、ディレクトリ存在チェック)

ディレクトリ作成で最もよくあるのが権限エラーや、既にディレクトリが存在する場合のエラーです。これらを適切に処理する方法を示します。

from ftplib import FTP, error_perm, all_errors # 特定の例外クラスをインポート

ftp = None

try:
    ftp = FTP('ftp.example.com', timeout=10)
    ftp.login('username', 'password')
    print("FTPサーバーに接続し、ログインしました。")

    directory_to_create = 'new_project_data'

    # ディレクトリが既に存在するかどうかを事前に確認する
    # ftp.nlst() はカレントディレクトリのファイルとディレクトリのリストを返します。
    current_items = []
    try:
        current_items = ftp.nlst()
    except error_perm as e:
        # 空のディレクトリの場合、nlst() がエラーを返すことがあるため、ここで捕捉
        if "550 No files found" in str(e): # サーバーの応答による
            print("現在のディレクトリは空です。")
        else:
            raise # その他の権限エラーは再スロー

    if directory_to_create in current_items:
        print(f"ディレクトリ '{directory_to_create}' は既に存在します。作成をスキップします。")
    else:
        print(f"ディレクトリ '{directory_to_create}' を作成しようとしています...")
        response = ftp.mkd(directory_to_create)
        print(f"ディレクトリ作成の応答: {response}")
        print(f"ディレクトリ '{directory_to_create}' が正常に作成されました。")

except error_perm as e:
    # 権限エラーやパスエラーなど、FTPの永続的なエラーを捕捉
    print(f"権限エラーまたはディレクトリ作成エラーが発生しました: {e}")
    if "550" in str(e): # FTP 550 エラーコードは「ファイルが利用できません」などを意味し、権限不足や存在しないパスでよく使われます。
        print("ヒント: FTPユーザーに書き込み権限があるか、パスが正しいか確認してください。")
except all_errors as e:
    # ftplib が発行するその他の全てのエラーを捕捉
    print(f"FTP操作中に一般的なエラーが発生しました: {e}")
except Exception as e:
    # 予期せぬPythonの例外を捕捉
    print(f"予期せぬエラーが発生しました: {e}")
finally:
    if ftp:
        try:
            ftp.quit()
            print("FTP接続を閉じました。")
        except all_errors as e:
            print(f"FTP接続のクローズ中にエラーが発生しました: {e}")

解説

  1. ftplib.error_perm をインポートし、具体的な権限エラーを捕捉できるようにしています。
  2. ftp.nlst() を使って、同じ名前のディレクトリが既に存在するかどうかを事前にチェックしています。これにより、既に存在するディレクトリを重複して作成しようとした際のエラーを回避できます。
    • nlst() は空のディレクトリの場合に 550 No files found のエラーを返すことがあるため、その場合も考慮して try-except で囲んでいます。
  3. エラーメッセージに 550 が含まれている場合、権限の問題や無効なパスである可能性が高いことをユーザーに伝えています。

例3: 相対パスと絶対パスでのディレクトリ作成

mkd() は、カレントディレクトリからの相対パスと、サーバーのルートからの絶対パスの両方で動作します。

from ftplib import FTP, all_errors

ftp = None

try:
    ftp = FTP('ftp.example.com', timeout=10)
    ftp.login('username', 'password')
    print("FTPサーバーに接続し、ログインしました。")

    # 1. 相対パスでのディレクトリ作成
    # 現在の作業ディレクトリに 'relative_folder' を作成します。
    # ftp.cwd() でカレントディレクトリを変更できます。
    print("\n--- 相対パスでの作成 ---")
    relative_dir = 'relative_folder'
    try:
        response = ftp.mkd(relative_dir)
        print(f"'{relative_dir}' 作成応答: {response}")
    except all_errors as e:
        print(f"'{relative_dir}' 作成エラー: {e}")

    # 作成したディレクトリに移動
    try:
        ftp.cwd(relative_dir)
        print(f"カレントディレクトリを '{relative_dir}' に変更しました。現在のパス: {ftp.pwd()}")
    except all_errors as e:
        print(f"ディレクトリ移動エラー: {e}")

    # 2. 絶対パスでのディレクトリ作成
    # サーバーのルートから始まるパスで 'absolute_path_folder' を作成します。
    # 親ディレクトリが存在しないとエラーになるため、注意が必要です。
    print("\n--- 絶対パスでの作成 ---")
    # 例: '/public_html/absolute_path_folder' のように、サーバーの構造に合わせて変更してください
    absolute_dir = '/absolute_path_folder' 
    
    # 注意: ルートに直接作成できる権限がない場合があるため、テスト環境で確認してください
    # 一般的には、ユーザーのホームディレクトリからの相対パスか、その配下で絶対パスを使用します。
    # 例: user_home_dir = ftp.pwd()
    #     absolute_dir = f"{user_home_dir}/my_abs_folder"

    try:
        # mkdは親ディレクトリを自動で作成しないため、存在しない場合はエラーになります。
        response = ftp.mkd(absolute_dir)
        print(f"'{absolute_dir}' 作成応答: {response}")
    except all_errors as e:
        print(f"'{absolute_dir}' 作成エラー: {e}")

    # 最初のディレクトリに戻る (もし移動していれば)
    try:
        ftp.cwd('/') # ルートディレクトリに戻る
        print(f"カレントディレクトリをルートに戻しました。現在のパス: {ftp.pwd()}")
    except all_errors as e:
        print(f"ルートへの移動エラー: {e}")


except all_errors as e:
    print(f"FTP操作中にエラーが発生しました: {e}")
except Exception as e:
    print(f"予期せぬエラーが発生しました: {e}")
finally:
    if ftp:
        try:
            ftp.quit()
            print("FTP接続を閉じました。")
        except all_errors as e:
            print(f"FTP接続のクローズ中にエラーが発生しました: {e}")

解説

  1. ftp.cwd() を使って現在の作業ディレクトリを変更できることを示しています。
  2. 相対パスで mkd() を呼び出すと、現在の作業ディレクトリに新しいディレクトリが作成されます。
  3. 絶対パスで mkd() を呼び出すと、サーバーのルートからの指定された場所にディレクトリが作成されます。
    • 重要
      絶対パスでディレクトリを作成する場合、そのパスの親ディレクトリがすべて存在している必要があります。mkd() は親ディレクトリを自動で作成しません。

例4: 複数の階層を持つディレクトリの作成 (再帰的作成)

mkd() は一度に一つのディレクトリしか作成できないため、dir1/dir2/dir3 のように複数の階層を持つディレクトリを作成するには、親ディレクトリから順番に作成していく必要があります。

from ftplib import FTP, all_errors
import os # パス操作のためにosモジュールを使用

def create_nested_ftp_dirs(ftp_obj, path):
    """
    FTPサーバー上に、ネストされた(階層的な)ディレクトリを作成します。
    既に存在するディレクトリはスキップします。
    """
    original_cwd = ftp_obj.pwd() # 現在の作業ディレクトリを保存

    # パスをディレクトリ名に分割
    # Windowsパス (C:\dir1\dir2) と Linuxパス (/dir1/dir2) の両方に対応
    # os.path.normpath でパスを正規化し、os.sep で区切る
    normalized_path = os.path.normpath(path)
    dirs_to_create = normalized_path.split(os.sep)

    print(f"\n--- 階層ディレクトリの作成: '{path}' ---")

    current_path_segment = ''
    for directory in dirs_to_create:
        if not directory: # 空の文字列(例: '/a/b' の最初の '/' など)はスキップ
            continue

        # 現在のパスセグメントを構築
        if current_path_segment:
            current_path_segment = os.path.join(current_path_segment, directory)
        else:
            current_path_segment = directory

        try:
            print(f"  作成を試行: '{current_path_segment}'")
            # FTPのカレントディレクトリを変更して、mkdが相対パスで動作するようにする
            # または、毎回絶対パスでmkd()を呼び出す
            # ここでは、現在のパスセグメントに対してmkd()を呼び出す
            
            # ディレクトリが存在するか確認し、存在しなければ作成
            # mkd() が既に存在するディレクトリに対してエラーを返す場合があるため、
            # エラーを捕捉してスキップする。
            
            # FTPサーバーの絶対パスでmkd()を呼び出すか、cwdとmkd()を組み合わせるか
            # ここでは、絶対パスでmkd()を呼び出す方法を採用
            
            # 注意: 一部のFTPサーバーでは、絶対パスでmkd()を呼び出す際に、
            # ルートからの絶対パスである必要がある場合があります。
            # 例: '/user/home/dir1/dir2'
            # その場合、ftp_obj.pwd() でホームディレクトリを取得し、それに連結する必要があります。
            # ここでは、渡されたpathが適切に処理されることを前提とします。
            
            # 簡単な方法: mkd()は相対パスでも絶対パスでも機能するので、そのまま呼び出す
            ftp_obj.mkd(current_path_segment)
            print(f"  '{current_path_segment}' を作成しました。")

        except error_perm as e:
            # 550エラーなど、権限エラーまたは既に存在する場合のエラーを捕捉
            if "File exists" in str(e) or "already exists" in str(e) or "550" in str(e):
                print(f"  '{current_path_segment}' は既に存在します。スキップします。")
            else:
                print(f"  '{current_path_segment}' の作成中に権限エラーが発生しました: {e}")
                raise # その他の深刻なエラーは再スロー
        except all_errors as e:
            print(f"  '{current_path_segment}' の作成中にエラーが発生しました: {e}")
            raise # その他のFTPエラーは再スロー

    # 処理後、元のディレクトリに戻る(オプション)
    try:
        ftp_obj.cwd(original_cwd)
        print(f"元のディレクトリ '{original_cwd}' に戻りました。")
    except all_errors as e:
        print(f"元のディレクトリへの移動中にエラーが発生しました: {e}")

ftp = None
try:
    ftp = FTP('ftp.example.com', timeout=10)
    ftp.login('username', 'password')
    print("FTPサーバーに接続し、ログインしました。")

    # 例1: ネストされたディレクトリを作成
    create_nested_ftp_dirs(ftp, 'my_app_data/logs/today')

    # 例2: 別のネストされたディレクトリを作成 (Windowsスタイルパスも可能)
    create_nested_ftp_dirs(ftp, 'backup\\2025\\june_data')

except all_errors as e:
    print(f"FTP操作中にエラーが発生しました: {e}")
except Exception as e:
    print(f"予期せぬエラーが発生しました: {e}")
finally:
    if ftp:
        try:
            ftp.quit()
            print("FTP接続を閉じました。")
        except all_errors as e:
            print(f"FTP接続のクローズ中にエラーが発生しました: {e}")

  1. create_nested_ftp_dirs というヘルパー関数を定義しています。
  2. この関数は、与えられたパス(例: 'dir1/dir2/dir3')を / または \ で分割し、各ディレクトリ名に対してループ処理を行います。
  3. os.path.join() を使用して、プラットフォームに依存しない形でパスを結合しています。
  4. 各階層のディレクトリが存在するかをチェックし、存在しない場合のみ ftp_obj.mkd() で作成します。
    • ftp_obj.mkd(current_path_segment) は、current_path_segment が相対パスとして評価されます。もし current_path_segment/ で始まる絶対パス形式であれば、そのまま絶対パスとして扱われます。
    • 重要な考慮点
      mkd() がすでに存在するディレクトリに対してエラーを返す場合があるため、error_perm を捕捉し、エラーメッセージから「既に存在する」という文字列を判別してスキップするようにしています。これはFTPサーバーの実装に依存する場合があります。
  5. 最後に、ftp.cwd() で元の作業ディレクトリに戻るようにしています。


主な代替方法は、大きく分けて以下のカテゴリに分類されます。

  1. 高レベル FTP ライブラリの使用
    ftplib は低レベルの FTP プロトコルを直接操作するため、より高レベルで使いやすい抽象化を提供するライブラリがあります。これらは内部で ftplib を使用していることがほとんどですが、エラー処理やパスの正規化などを自動で行ってくれるため、コードが簡潔になります。

  2. SSH/SFTP (Secure File Transfer Protocol) の利用
    セキュリティが非常に重要な場合や、FTP が許可されていない環境では、SFTP を使用するのが一般的です。SFTP は SSH プロトコル上で動作し、ファイル転送とディレクトリ操作を安全に行うことができます。Python では paramiko などのライブラリがこれに該当します。

  3. OS コマンドの実行 (非推奨だが選択肢として)
    非常に限定的なシナリオで、Python プログラムから FTP クライアントのコマンドラインツールを呼び出す方法です。これはセキュリティ上のリスクや移植性の問題があるため、ほとんどの場合で推奨されません。

高レベル FTP ライブラリの利用 (ftputil など)

ftputilftplib の上に構築された、より高レベルで使いやすい FTP/FTPS クライアントライブラリです。エラー処理やディレクトリの存在チェック、再帰的なディレクトリ作成など、一般的なタスクを簡素化します。

特徴

  • 例外処理がよりユーザーフレンドリー
  • 再帰的なディレクトリ作成をサポート
  • ディレクトリの存在チェックが容易
  • より直感的なオブジェクト指向インターフェース

インストール

pip install ftputil

ftputil でのディレクトリ作成例

import ftputil
from ftplib import all_errors # ftputilも内部でftplibの例外を使用することがある

# ネストされたディレクトリを作成する例
# ftplib.FTP.mkd() では 'parent/child/grandchild' を一度に作成できませんでしたが、
# ftputil.FTPHost.makedirs() では可能です。
NESTED_DIR_PATH = 'my_project_data/logs/2025/june'

try:
    # FTPHost コンテキストマネージャーを使用すると、接続と切断が自動的に処理されます
    with ftputil.FTPHost('ftp.example.com', 'username', 'password', timeout=10) as ftp_host:
        print("FTPサーバーに接続し、ログインしました。")

        print(f"ディレクトリ '{NESTED_DIR_PATH}' を作成しようとしています...")
        # `makedirs(path, recreate=True)` は、パスが存在しない場合は作成し、
        # 既に存在する場合は何もしません (recreate=False の場合)。
        # デフォルトでは recreate=False です。
        ftp_host.makedirs(NESTED_DIR_PATH) 
        
        print(f"ディレクトリ '{NESTED_DIR_PATH}' が正常に作成されました (既に存在する場合はスキップ)。")

        # ディレクトリが作成されたか確認
        if ftp_host.path.isdir(NESTED_DIR_PATH):
            print(f"確認: '{NESTED_DIR_PATH}' はFTPサーバー上に存在します。")
        else:
            print(f"確認: '{NESTED_DIR_PATH}' はFTPサーバー上に存在しません。")

except all_errors as e:
    print(f"FTP操作中にエラーが発生しました: {e}")
except Exception as e:
    print(f"予期せぬエラーが発生しました: {e}")

ftputil の利点

  • コンテキストマネージャー (with ステートメント) を使用して、接続と切断を自動化できるため、リソース管理が容易。
  • path.isdir() など、パス操作のための便利なメソッドが用意されている。
  • makedirs() メソッドにより、複数階層のディレクトリを一度に作成できる。

SSH/SFTP の利用 (paramiko など)

セキュリティ要件がある場合、または FTP がポートの問題などで使用できない場合は、SFTP が強力な代替手段となります。SFTP は SSH 接続上で動作するため、暗号化された安全な方法でファイル転送とディレクトリ操作が可能です。

Python ライブラリ
paramiko が SFTP クライアント機能を提供します。

インストール

pip install paramiko

paramiko (SFTP) でのディレクトリ作成例

import paramiko

HOSTNAME = 'your_sftp_host.com' # SFTP サーバーのホスト名
PORT = 22                      # SFTP のデフォルトポート (SSHと同じ)
USERNAME = 'your_sftp_username'
PASSWORD = 'your_sftp_password'
# またはプライベートキーを使用する場合
# PRIVATE_KEY_PATH = '/path/to/your/private_key.pem' 

SFTP_DIR_PATH = '/home/your_sftp_username/my_sftp_folder/new_dir' # SFTP サーバー上のパス

ssh_client = None
sftp_client = None

try:
    # 1. SSH クライアントを作成
    ssh_client = paramiko.SSHClient()
    ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # 初回接続時にホストキーを自動追加 (本番環境では推奨されない場合あり)

    # 2. SSH サーバーに接続
    print(f"SFTPサーバー '{HOSTNAME}' に接続しようとしています...")
    ssh_client.connect(hostname=HOSTNAME, port=PORT, username=USERNAME, password=PASSWORD)
    # またはプライベートキーで接続する場合:
    # ssh_client.connect(hostname=HOSTNAME, port=PORT, username=USERNAME, key_filename=PRIVATE_KEY_PATH)
    
    print("SFTPサーバーに接続しました。")

    # 3. SFTP クライアントを取得
    sftp_client = ssh_client.open_sftp()
    print("SFTPクライアントを開きました。")

    # 4. ディレクトリを作成 (makedirs は親ディレクトリも自動で作成します)
    print(f"ディレクトリ '{SFTP_DIR_PATH}' を作成しようとしています...")
    sftp_client.makedirs(SFTP_DIR_PATH) # SFTPではmakedirsで複数階層を一度に作成可能

    print(f"ディレクトリ '{SFTP_DIR_PATH}' が正常に作成されました。")

    # ディレクトリが作成されたか確認
    try:
        sftp_client.stat(SFTP_DIR_PATH) # ディレクトリの統計情報を取得できれば存在する
        print(f"確認: '{SFTP_DIR_PATH}' はSFTPサーバー上に存在します。")
    except FileNotFoundError:
        print(f"確認: '{SFTP_DIR_PATH}' はSFTPサーバー上に存在しません。")

except paramiko.AuthenticationException:
    print("認証に失敗しました。ユーザー名/パスワードまたはキーを確認してください。")
except paramiko.SSHException as e:
    print(f"SSH接続エラーが発生しました: {e}")
except Exception as e:
    print(f"予期せぬエラーが発生しました: {e}")
finally:
    if sftp_client:
        sftp_client.close()
        print("SFTPクライアントを閉じました。")
    if ssh_client:
        ssh_client.close()
        print("SSH接続を閉じました。")

SFTP の利点

  • makedirs() が再帰的なディレクトリ作成をサポートしている。
  • SSH との統合
    同じ接続でシェルコマンドの実行など、他の SSH 機能も利用できます。
  • セキュリティ
    通信が暗号化されるため、データが保護されます。

考慮点

  • FTP とは異なる認証情報やキー管理が必要です。
  • SFTP サーバーが稼働している必要があります(FTP サーバーとは別)。

OS コマンドの実行 (subprocess モジュール)

これは最も非推奨な方法であり、特殊な状況(例: 既存のレガシーな FTP クライアントツールしか利用できない環境)でのみ検討すべきです。Python からシステムのコマンドラインツール(例: ftp コマンド)を呼び出すことで、FTP 操作を行います。

問題点

  • インタラクティブ性
    FTP コマンドはインタラクティブなセッションを前提とすることが多いため、スクリプトからの自動化が難しい場合があります。
  • エラーハンドリング
    コマンドの終了コードや標準出力/エラー出力を解析する必要があり、複雑になります。
  • 移植性
    実行環境に特定の FTP クライアントツールがインストールされている必要があり、OS によってコマンドの構文が異なる場合があります。
  • セキュリティリスク
    パスワードがコマンドライン引数として渡される可能性があり、ログに残るなどして漏洩のリスクが高まります。

subprocess でのディレクトリ作成例 (非推奨)

import subprocess
import time

FTP_HOST = 'ftp.example.com'
FTP_USER = 'username'
FTP_PASS = 'password'
NEW_DIR = 'via_cmd_folder'

# 一般的な 'ftp' コマンドでは、インタラクティブなセッションを自動化するのが難しい
# 多くのシステムでは、ftp コマンドはコマンドラインで直接 mkdir をサポートしない
# この例は概念を示すためのものであり、実際にこの方法でmkdirがうまくいくとは限りません。
# ほとんどの場合、より高度なシェルスクリプトや Expect スクリプトが必要になります。

# あえて例を示すなら、ftpコマンドをスクリプトで自動化するツール(例: lftp, ncftp)を使うか、
# こちらのようにパイプでコマンドを渡す形になりますが、非常に脆いです。
ftp_commands = f"""
open {FTP_HOST}
user {FTP_USER} {FTP_PASS}
mkdir {NEW_DIR}
quit
"""

try:
    print(f"OSコマンドでディレクトリ '{NEW_DIR}' を作成しようとしています...")
    # ftp コマンドに標準入力をパイプで渡す
    process = subprocess.run(
        ['ftp', '-inv'], # -i: 対話型プロンプト無効, -n: 自動ログイン無効
        input=ftp_commands.encode('utf-8'),
        capture_output=True,
        check=True # ゼロ以外の終了コードでCalledProcessErrorを発生させる
    )

    print("STDOUT:")
    print(process.stdout.decode('utf-8'))
    print("STDERR:")
    print(process.stderr.decode('utf-8'))

    print(f"ディレクトリ '{NEW_DIR}' の作成コマンドを実行しました。")

except subprocess.CalledProcessError as e:
    print(f"コマンド実行エラーが発生しました: {e}")
    print(f"STDOUT: {e.stdout.decode('utf-8')}")
    print(f"STDERR: {e.stderr.decode('utf-8')}")
except FileNotFoundError:
    print("エラー: 'ftp' コマンドが見つかりません。システムにインストールされていますか?")
except Exception as e:
    print(f"予期せぬエラーが発生しました: {e}")

subprocess の考慮点

  • 複雑性
    コマンドの出力解析やエラーハンドリングが複雑で、プラットフォーム依存性が高いです。
  • 最終手段
    他のどの方法も使えない場合にのみ検討すべきです。
  • OS コマンド
    ほとんどの場合で非推奨ですが、非常に限定的なレガシー環境でのみ選択肢となります。
  • セキュリティが最優先、FTP が使用できない環境
    paramiko を使用した SFTP が最良の選択です。
  • より高レベルな FTP 操作、再帰的作成、容易なエラー処理
    ftputil が推奨されます。
  • 簡単な FTP 操作
    ftplib.FTP.mkd() で十分です。