Python ftplib.FTP.dir()徹底解説:FTPディレクトリ操作の基本
ftplib
は、Python の標準ライブラリで、FTP (File Transfer Protocol) クライアントを実装するためのモジュールです。このモジュールを使うと、FTP サーバーに接続し、ファイルのアップロード、ダウンロード、ディレクトリの操作などを行うことができます。
ftplib.FTP.dir()
メソッドは、FTP サーバー上のディレクトリの内容をリスト表示するためのものです。
主な機能と使い方
-
ディレクトリの内容のリスト表示
dir()
メソッドは、指定されたディレクトリ内のファイルやサブディレクトリの情報を、人間が読みやすい形式で返します。これは通常、UNIX のls -l
コマンドや DOS のdir
コマンドの出力に似ています。 -
引数
dirname
(オプション): リスト表示したいディレクトリのパスを指定します。この引数を省略した場合、現在の作業ディレクトリ (カレントディレクトリ) の内容がリスト表示されます。callback
(オプション): 各行のデータを受信するたびに呼び出されるコールバック関数を指定できます。このコールバック関数は、バイト文字列として各行を受け取ります。
-
戻り値
dir()
メソッド自体は、明示的な戻り値を持ちません(None
を返します)。リスト表示された内容は、通常、FTP サーバーからクライアントの標準出力に直接送信されるか、指定されたコールバック関数に渡されます。
具体的な使用例
from ftplib import FTP
ftp = None
try:
# FTPサーバーに接続
ftp = FTP('ftp.example.com') # ここを実際のFTPサーバーのアドレスに置き換えてください
ftp.login('your_username', 'your_password') # ログイン情報に置き換えてください
# 現在のディレクトリの内容をリスト表示
print("現在のディレクトリの内容:")
ftp.dir()
# 特定のディレクトリ (例: 'public_html') の内容をリスト表示
print("\n'public_html' ディレクトリの内容:")
ftp.dir('public_html')
# コールバック関数を使用して内容を取得する例
print("\nコールバック関数を使用した内容の取得:")
def print_line(line):
print(line.decode('utf-8')) # バイト文字列をデコードして表示
ftp.dir('public_html', print_line)
except Exception as e:
print(f"エラーが発生しました: {e}")
finally:
if ftp:
ftp.quit() # 接続を閉じる
- FTP サーバーによっては、
dir()
の出力形式が異なる場合があります。 - もし、ファイル名だけをリストアップしたい場合は、
ftplib.FTP.nlst()
メソッドを使用することができます。nlst()
はファイル名のリストを返します。 dir()
は通常、詳細な情報を表示します。ファイルサイズ、更新日時、パーミッションなどが含まれることが多いです。
ftplib.FTP.dir()
メソッドは、FTP サーバー上のディレクトリをリスト表示する際に非常に便利ですが、いくつかの一般的なエラーに遭遇することがあります。ここでは、それらのエラーとそれぞれのトラブルシューティング方法を説明します。
-
- 原因
ユーザー名またはパスワードが間違っています。あるいは、FTP サーバーが匿名アクセスを許可していないのに、匿名でログインしようとしている場合です。 - トラブルシューティング
- ユーザー名とパスワードの確認
最も一般的な原因です。大文字・小文字を含め、正確なユーザー名とパスワードを入力しているか再確認してください。 - 匿名ログインの確認
サーバーが匿名ログインを許可しているか確認してください。許可されていない場合は、有効なユーザー名とパスワードでログインする必要があります。 - FTPアカウントの状態
アカウントが有効であるか、ロックされていないか、期限切れになっていないかなど、FTPサーバーの管理者またはホスティングプロバイダーに確認してください。
- ユーザー名とパスワードの確認
- 原因
-
ftplib.all_errors: 550 Requested action not taken. File unavailable (e.g., file not found, no access).
(指定されたディレクトリが見つからない、またはアクセス権がない)- 原因
dir()
に渡したディレクトリパスが存在しない。- FTP ユーザーに、そのディレクトリへのリスト表示 (読み取り) 権限がない。
- パスの指定が誤っている(例: 相対パスと絶対パスの混同)。
- トラブルシューティング
- ディレクトリパスの確認
dir()
メソッドに渡しているディレクトリパス(ftp.dir('path/to/directory')
)が正しいか、FTP サーバー上に実際に存在するかを確認してください。 - アクセス権限の確認
ログインしているFTPユーザーが、そのディレクトリをリスト表示する権限を持っているか確認してください。通常、Webホスティングでは、public_html
やwww
などのディレクトリにアクセス権限があります。 - パスの種類
絶対パス (/home/user/public_html
) または相対パス (./subfolder
) のどちらを使用しているか確認し、サーバーに合わせた正しい形式を使用してください。通常は、ログイン後のルートディレクトリからの相対パスが便利です。
- ディレクトリパスの確認
- 原因
-
ftplib.all_errors: 421 Service not available, closing control connection.
(サービスが利用できない、または接続が切断された)- 原因
- FTP サーバーが一時的にダウンしているか、メンテナンス中。
- ファイアウォールやネットワーク設定が接続をブロックしている。
- アイドル状態が長く続き、サーバー側で接続がタイムアウトした。
- 同時に多くの接続があり、サーバーが負荷過多になっている。
- トラブルシューティング
- サーバーの稼働状況確認
FTP サーバーが正常に稼働しているか確認してください。他のFTPクライアント(FileZillaなど)で接続を試してみるのも良いでしょう。 - ファイアウォール/セキュリティソフトウェア
クライアント側のファイアウォールやアンチウイルスソフトウェアがFTP接続をブロックしていないか確認してください。 - 再接続
接続が切断された場合は、再度FTP()
オブジェクトを作成し、login()
からやり直してみてください。 - timeout パラメータ
FTP()
オブジェクトを初期化する際にtimeout
パラメータを設定することで、接続のタイムアウト時間を調整できます(例:FTP('ftp.example.com', timeout=30)
)。
- サーバーの稼働状況確認
- 原因
-
ftplib.error_perm: 500 Illegal PORT command.
またはftplib.error_perm: 501 Syntax error in parameters or arguments.
(PORT/PASV コマンドのエラー)- 原因
- FTP サーバーがアクティブモード (PORT) またはパッシブモード (PASV) のどちらかをサポートしていない、またはその設定に問題がある。
- ネットワーク環境(特にNATやファイアウォール)が、特定のFTPモードと相性が悪い。
- トラブルシューティング
- パッシブモードの使用 (推奨)
ftplib
はデフォルトでパッシブモードを使用しようとしますが、明示的にftp.set_pasv(True)
を呼び出してパッシブモードを強制してみてください。ほとんどの場合、パッシブモードの方がファイアウォールを通過しやすいため推奨されます。 - アクティブモードの使用 (非推奨、しかし場合によっては必要)
パッシブモードで問題が解決しない場合、ftp.set_pasv(False)
を呼び出してアクティブモードを試すこともできます。ただし、アクティブモードはクライアント側の特定のポート開放が必要となるため、通常はより多くのネットワーク設定の問題を引き起こします。 - FTP サーバーの設定確認
サーバー側がどのようなFTPモードをサポートしているか、管理者に確認してください。
- パッシブモードの使用 (推奨)
- 原因
-
socket.gaierror: [Errno 8] nodename nor servname provided, or not known
(ホスト名解決エラー)- 原因
FTP サーバーのホスト名(例:ftp.example.com
)が間違っているか、DNS で解決できない。 - トラブルシューティング
- ホスト名の確認
FTP サーバーのホスト名を再度確認し、タイプミスがないか確認してください。 - インターネット接続
インターネットに接続されているか確認してください。 - DNS解決
端末がDNSサーバーにアクセスできるか確認してください。ping ftp.example.com
のようにコマンドラインで試してみるのも有効です。
- ホスト名の確認
- 原因
一般的なデバッグのヒント
- 最小限のコードで再現
問題を切り分けるために、dir()
メソッドを呼び出すだけの最小限のスクリプトを作成し、エラーが再現するか確認してください。 - 別のFTPクライアントでテスト
FileZillaなどのGUIベースのFTPクライアントを使用して、同じFTPサーバー、同じユーザー名、同じパスで接続を試みてください。もし、別のクライアントで接続できるのであれば、Pythonコード側の問題である可能性が高いです。 - ログ出力の有効化
ftplib
はデバッグメッセージを出力することができます。ftp.set_debuglevel(2)
のように設定すると、FTPコマンドと応答の詳細なログが表示され、何が問題であるかを理解するのに非常に役立ちます。 - try...except ブロックの使用
常にftplib
の操作をtry...except ftplib.all_errors as e:
で囲み、エラーメッセージを捕捉して表示するようにしてください。これにより、問題の特定が容易になります。
ftplib.FTP.dir()
のプログラミング例 (Python)
ftplib.FTP.dir()
は、FTPサーバー上のディレクトリの内容をリスト表示するための非常に便利なメソッドです。ここでは、具体的なコード例を通じてその使い方を詳しく見ていきましょう。
基本的なディレクトリリスト表示
最も基本的な使い方は、現在の作業ディレクトリ (カレントディレクトリ) の内容をリスト表示することです。
from ftplib import FTP
# 接続情報 (ご自身のFTPサーバー情報に置き換えてください)
FTP_HOST = 'ftp.example.com'
FTP_USER = 'your_username'
FTP_PASS = 'your_password'
ftp = None # FTPオブジェクトの初期化
try:
# 1. FTPサーバーに接続
ftp = FTP(FTP_HOST)
print(f"FTPサーバー '{FTP_HOST}' に接続しました。")
# 2. ログイン
ftp.login(FTP_USER, FTP_PASS)
print(f"ユーザー '{FTP_USER}' でログインしました。")
# 3. 現在のディレクトリの内容をリスト表示
print("\n--- 現在の作業ディレクトリの内容 ---")
ftp.dir() # 引数なしで呼び出すと現在のディレクトリ
except Exception as e:
# エラーハンドリング
print(f"エラーが発生しました: {e}")
finally:
# 4. 接続を閉じる (重要)
if ftp:
ftp.quit()
print("FTP接続を閉じました。")
解説
ftp.dir()
を引数なしで呼び出すと、ログイン後、FTPサーバーのデフォルトの作業ディレクトリ (通常はユーザーのホームディレクトリ) の内容が標準出力に表示されます。出力形式は、UNIXのls -l
コマンドに似ています。ftp.login(FTP_USER, FTP_PASS)
でログインします。FTP(FTP_HOST)
で指定したFTPサーバーに接続します。
特定のディレクトリのリスト表示
特定のディレクトリの内容をリスト表示したい場合は、dir()
メソッドにそのパスを引数として渡します。
from ftplib import FTP
FTP_HOST = 'ftp.example.com'
FTP_USER = 'your_username'
FTP_PASS = 'your_password'
ftp = None
try:
ftp = FTP(FTP_HOST)
ftp.login(FTP_USER, FTP_PASS)
print(f"ユーザー '{FTP_USER}' でログインしました。")
# 特定のディレクトリの内容をリスト表示
# 例: 'public_html' ディレクトリ
target_directory = 'public_html' # リスト表示したいディレクトリのパス
print(f"\n--- ディレクトリ '{target_directory}' の内容 ---")
ftp.dir(target_directory)
# 例: 存在しないディレクトリを指定した場合のエラーハンドリング
non_existent_directory = 'non_existent_folder_123'
print(f"\n--- ディレクトリ '{non_existent_directory}' の内容 (エラー例) ---")
try:
ftp.dir(non_existent_directory)
except Exception as e:
print(f"ディレクトリ '{non_existent_directory}' のリスト表示中にエラー: {e}")
except Exception as e:
print(f"エラーが発生しました: {e}")
finally:
if ftp:
ftp.quit()
print("FTP接続を閉じました。")
解説
- 存在しないディレクトリやアクセス権のないディレクトリを指定すると、通常
ftplib.all_errors: 550
のようなエラーが発生します。これを適切にtry...except
で捕捉することが重要です。 ftp.dir('public_html')
のように、パスを文字列として渡すことで、そのディレクトリの内容をリスト表示できます。
コールバック関数を使用したリスト表示データの処理
ftp.dir()
は、通常、データを標準出力に直接表示しますが、各行のデータをプログラム内で処理したい場合があります。そのために、コールバック関数を使用できます。
from ftplib import FTP
FTP_HOST = 'ftp.example.com'
FTP_USER = 'your_username'
FTP_PASS = 'your_password'
# リスト表示された各行を処理するためのコールバック関数
def process_dir_line(line_bytes):
# FTPサーバーから受け取るデータはバイト文字列なので、デコードする必要がある
line_str = line_bytes.decode('utf-8', errors='ignore').strip()
if line_str: # 空行を除外
print(f"[処理済み行]: {line_str}")
# ここで、例えばファイル名やサイズなどをパースする処理を追加できます
# 例: ファイル名だけを抽出する (簡易版)
parts = line_str.split()
if len(parts) >= 9: # ls -l の出力形式を想定
filename = " ".join(parts[8:]) # ファイル名にはスペースが含まれる可能性あり
print(f" -> 検出されたファイル/ディレクトリ名: {filename}")
ftp = None
try:
ftp = FTP(FTP_HOST)
ftp.login(FTP_USER, FTP_PASS)
print(f"ユーザー '{FTP_USER}' でログインしました。")
print("\n--- コールバック関数を使用したディレクトリ内容の取得 ---")
# 'target_directory' の内容をコールバック関数で処理
target_directory = '/' # ルートディレクトリを例に
ftp.dir(target_directory, process_dir_line)
except Exception as e:
print(f"エラーが発生しました: {e}")
finally:
if ftp:
ftp.quit()
print("FTP接続を閉じました。")
解説
- 注意
FTPサーバーのdir
コマンドの出力形式はサーバーによって異なる場合があるため、パース処理はサーバーの出力形式に合わせて調整する必要があります。より堅牢な処理が必要な場合は、正規表現などを使用することも検討してください。 - コールバック関数内で
line_bytes.decode('utf-8', errors='ignore')
を使ってバイト文字列を適切なエンコーディングでデコードし、必要な処理を行います。この例では、単に表示するだけでなく、ファイル名を抽出する簡易的な処理も示しています。 ftp.dir()
は、リスト表示された各行のデータをバイト文字列としてこのコールバック関数に渡します。process_dir_line(line_bytes)
という関数を定義し、これをftp.dir()
の第2引数として渡します。
問題が発生した際に、ftplib
が内部でどのようなFTPコマンドを送受信しているかを確認すると、デバッグに非常に役立ちます。
from ftplib import FTP
FTP_HOST = 'ftp.example.com'
FTP_USER = 'your_username'
FTP_PASS = 'your_password'
ftp = None
try:
ftp = FTP(FTP_HOST)
# デバッグレベルを設定 (0:なし, 1:簡易, 2:詳細)
# 2 を設定すると、送受信されるすべてのFTPコマンドと応答が表示されます
ftp.set_debuglevel(2)
print(f"FTPサーバー '{FTP_HOST}' に接続しました。")
ftp.login(FTP_USER, FTP_PASS)
print(f"ユーザー '{FTP_USER}' でログインしました。")
print("\n--- デバッグレベル2でのディレクトリリスト表示 ---")
ftp.dir()
except Exception as e:
print(f"エラーが発生しました: {e}")
finally:
if ftp:
ftp.quit()
print("FTP接続を閉じました。")
- これにより、ログインが成功しているか、どのコマンドでエラーが発生しているか、サーバーがどのような応答を返しているかなどを正確に把握でき、トラブルシューティングに役立ちます。本番環境では、この設定をオフにするか、ログファイルに出力するように変更するのが一般的です。
ftp.set_debuglevel(2)
を呼び出すことで、ftplib
が内部的に実行するFTPコマンド(例:USER
,PASS
,CWD
,LIST
)と、それに対するサーバーからの応答コード(例:230 Login successful.
,226 Transfer complete.
) が詳細に表示されます。
ftplib.FTP.dir()
の代替メソッド
主に以下の2つの代替メソッドがよく使われます。
ftplib.FTP.nlst()
ftplib.FTP.mlsd()
(Python 3.3以降で推奨)ftplib.FTP.retrlines('LIST', callback)
を直接使用 (dir()
と同じ基盤)
それぞれのメソッドについて詳しく見ていきましょう。
ftplib.FTP.nlst() (Name List)
特徴
- サブディレクトリを再帰的にリスト表示する機能はない。
- 戻り値は Python の
list
オブジェクトなので、プログラムで扱いやすい。 - ファイル名とディレクトリ名のみのリストを返す。
使用例
from ftplib import FTP
FTP_HOST = 'ftp.example.com'
FTP_USER = 'your_username'
FTP_PASS = 'your_password'
ftp = None
try:
ftp = FTP(FTP_HOST)
ftp.login(FTP_USER, FTP_PASS)
print(f"ユーザー '{FTP_USER}' でログインしました。")
# 現在のディレクトリの内容をファイル名のみで取得
print("\n--- 現在の作業ディレクトリのファイル名リスト (nlst) ---")
file_names = ftp.nlst()
for name in file_names:
print(name)
# 特定のディレクトリのファイル名のみを取得
target_directory = 'public_html'
print(f"\n--- ディレクトリ '{target_directory}' のファイル名リスト (nlst) ---")
target_file_names = ftp.nlst(target_directory)
for name in target_file_names:
print(name)
except Exception as e:
print(f"エラーが発生しました: {e}")
finally:
if ftp:
ftp.quit()
print("FTP接続を閉じました。")
dir() との比較
機能 | ftp.dir() | ftp.nlst() |
---|---|---|
戻り値 | None (標準出力に表示またはコールバックに渡す) | list (ファイル名とディレクトリ名のリスト) |
情報の粒度 | 詳細 (パーミッション、サイズ、日時など) | 名前のみ |
使いやすさ | 人間が読むには良いが、プログラムでのパースが必要 | プログラムで処理しやすい |
ftplib.FTP.mlsd() (Machine List Directory)
mlsd()
メソッドは、Python 3.3以降で導入された、FTPサーバーからディレクトリの情報を構造化された形式で取得するための推奨される方法です。これは、RFC 3659 で定義された MLSD
コマンドを使用しており、より機械処理に適した形式でファイル名とメタデータ(タイプ、サイズ、更新日時など)のタプルのリストを返します。
特徴
- ただし、全てのFTPサーバーが
MLSD
コマンドをサポートしているわけではない。古いサーバーでは利用できない場合がある。 dir()
の出力のようにサーバー依存のパースが不要で、より信頼性が高い。- 戻り値は
(ファイル名, メタデータ辞書)
のタプルのイテレータ。 - ファイル名と、それに関連するメタデータ(辞書形式)のタプルのリストを返す。
使用例
from ftplib import FTP
FTP_HOST = 'ftp.example.com'
FTP_USER = 'your_username'
FTP_PASS = 'your_password'
ftp = None
try:
ftp = FTP(FTP_HOST)
ftp.login(FTP_USER, FTP_PASS)
print(f"ユーザー '{FTP_USER}' でログインしました。")
# 現在のディレクトリの内容をMLSDで取得
print("\n--- 現在の作業ディレクトリの内容 (mlsd) ---")
# mlsd()はイテレータを返すため、list()で全て取得
mlsd_entries = list(ftp.mlsd())
for name, facts in mlsd_entries:
print(f"名前: {name}")
print(f" タイプ: {facts.get('type')}")
print(f" サイズ: {facts.get('size')}")
print(f" 変更日時: {facts.get('modify')}")
print(f" パーミッション: {facts.get('perm')}")
print("-" * 20)
# 特定のディレクトリの内容をMLSDで取得
target_directory = 'public_html'
print(f"\n--- ディレクトリ '{target_directory}' の内容 (mlsd) ---")
target_mlsd_entries = list(ftp.mlsd(target_directory))
if not target_mlsd_entries:
print(f"ディレクトリ '{target_directory}' は空か、MLSDがサポートされていません。")
for name, facts in target_mlsd_entries:
print(f"名前: {name}, タイプ: {facts.get('type')}, サイズ: {facts.get('size')}")
except Exception as e:
print(f"エラーが発生しました: {e}")
finally:
if ftp:
ftp.quit()
print("FTP接続を閉じました。")
dir() と nlst() との比較 (MLSDの利点)
- ディレクトリとファイルを
type
ファクトで明確に区別できる(type=file
またはtype=dir
)。 nlst()
と異なり、ファイル名だけでなく詳細なメタデータも取得できる。dir()
のように出力形式に依存しないため、サーバー間の互換性が高い。
ftplib.FTP.retrlines('LIST', callback) を直接使用
ftplib.FTP.dir()
メソッドは、内部的に retrlines('LIST', callback)
を呼び出しています。つまり、dir()
で行っていることと同じことを、より低レベルで直接制御することも可能です。この方法を使うと、dir()
の標準出力への表示動作ではなく、すべての行をリストに収集するなどのカスタム処理が容易になります。
特徴
dir()
がNone
を返すのに対し、この方法ではデータを変数に格納できる。- コールバック関数を通じて各行のバイトデータを直接受け取るため、柔軟な処理が可能。
dir()
と同じLIST
コマンドを使用するため、同じ詳細なリスト形式のデータを取得する。
使用例
from ftplib import FTP
FTP_HOST = 'ftp.example.com'
FTP_USER = 'your_username'
FTP_PASS = 'your_password'
ftp = None
dir_listing_lines = [] # 取得したディレクトリリストの行を格納するリスト
def collect_lines(line_bytes):
# バイト文字列をデコードしてリストに追加
dir_listing_lines.append(line_bytes.decode('utf-8', errors='ignore').strip())
try:
ftp = FTP(FTP_HOST)
ftp.login(FTP_USER, FTP_PASS)
print(f"ユーザー '{FTP_USER}' でログインしました。")
print("\n--- retrlines('LIST', callback) を使用したディレクトリ内容の取得 ---")
# 'LIST' コマンドを実行し、各行を collect_lines 関数で処理
ftp.retrlines('LIST', collect_lines)
# 収集された行を表示
print("\n収集されたディレクトリリストの行:")
for line in dir_listing_lines:
print(line)
# 必要に応じて、ここで各行をパースする処理を追加
# 例: ファイル名だけを抽出する (簡易版)
print("\nパース例 (ファイル名のみ):")
for line in dir_listing_lines:
parts = line.split()
if len(parts) > 8: # 少なくともUNIX形式の ls -l の場合
# ファイル名部分はスペースを含む可能性があるため、最後の部分を結合
filename = " ".join(parts[8:])
print(f"- {filename}")
except Exception as e:
print(f"エラーが発生しました: {e}")
finally:
if ftp:
ftp.quit()
print("FTP接続を閉じました。")
dir()
を明示的に使わずとも、retrlines('LIST', ...)
を直接呼び出すことで、より低レベルな操作が可能であるという理解につながります。dir()
のコールバック引数を使用するのと同じ結果が得られますが、dir()
は標準出力にデフォルトで出力されるのに対し、この方法はコールバックによって完全に制御されます。
- mlsd() が利用できない古いサーバーで、かつ詳細なリスト情報が必要な場合
ftplib.FTP.dir()
を使用するか、またはftplib.FTP.retrlines('LIST', callback)
を直接使用して、その出力形式を自分でパースする必要があります。この場合、FTPサーバーの出力形式に注意し、堅牢なパースロジックを実装することが重要です。 - ファイル名だけが必要な場合
ftplib.FTP.nlst()
が最もシンプルで効率的です。