バッチファイルから sftp コマンドを実行したとき、明らかに転送に失敗しているのに ERRORLEVEL が 0 のまま……。ジョブ監視やバッチ設計では致命的な問題になります。本記事では、OpenSSH 系 sftp の終了コードの仕組みと、実務で安全に失敗検知するためのパターンを詳しく解説します。
SFTP の ERRORLEVEL(終了コード)はどう決まるのか
まず前提として、Windows バッチから SFTP を呼び出す典型的な例を整理します。
@echo off
rem SFTP コマンド例(OpenSSH 系)
sftp -b SFTPCMDS.txt %USER%@%HOST%
echo 終了コード=%ERRORLEVEL%
ここで問題になるのが、バッチモード(-b)で複数ファイルを転送しているときの挙動です。
- 存在するファイルだけを
put→ 正常に転送され、ERRORLEVEL=0 - あえて存在しないファイルを
put→ ログにはエラーが出ているのにERRORLEVEL=0のまま - 環境によっては、接続エラー等で
ERRORLEVEL=255になる場合もある
ここから分かるのは、SFTP の終了コードは「セッション全体」の成功/失敗を表しており、個々のファイル転送の成否までは反映されないことが多いという点です。
OpenSSH 系 sftp の基本的な考え方
OpenSSH の sftp は、もともと「対話型クライアント」です。バッチモード(-b)は、この対話操作をスクリプトファイルに置き換えたものに過ぎません。
そのため、以下のような違いが生まれます。
| レベル | 失敗例 | セッション継続 | 代表的な終了コード |
|---|---|---|---|
| 接続レベル | ホスト名誤り / ポート閉塞 / 認証失敗 | 継続不能 | 255(致命的エラー) |
| セッションレベル | プロトコルエラー / サーバから強制切断 | 継続不能 | 1〜255(実装依存) |
| コマンドレベル | 指定ファイルが存在しない / パーミッションエラー | 継続可能 | 0 のまま終わる場合がある |
特に問題になるのが「コマンドレベル」の失敗です。put や get が失敗してもセッション自体は継続できるため、プロセス終了コード(ERRORLEVEL)には反映されず 0 のままというケースが普通にあります。
ERRORLEVEL の値についての実務的な整理
実際の現場では、次のように理解しておくと安全です。
| ERRORLEVEL の値 | 意味の目安 | 注意点 |
|---|---|---|
| 0 | SFTP セッションとしては正常終了 | 一部ファイルの転送失敗が含まれていても 0 のことがある |
| 1〜254 | セッション内で何らかのエラーが発生 | 値の意味はビルドやバージョン依存。細かい意味をあてにしない |
| 255 | 接続・認証・プロトコルなどの致命的なエラー | ここだけは比較的安定して「致命的」と見なしてよい |
結論として、ERRORLEVEL だけを使って「どのファイルが転送に失敗したか」を判定することはできません。セッションが成立したかどうかの大まかな判定にしか使えないと考えるべきです。
実務でおすすめの対処方針
では、ジョブ設計や監視に耐えうる形で SFTP 転送の成否を判定するにはどうすればよいでしょうか。現実的には、以下の 3 パターンが主な選択肢になります。
| 方法 | 概要 | メリット | デメリット |
|---|---|---|---|
| ① ログ解析 | 標準出力/エラーをログに落とし、エラー文字列を検索 | 既存バッチの変更が最小限で済む | メッセージ語彙のメンテナンスが必要 |
| ② 1ファイルずつ実行 | ファイル単位で sftp を呼び出し、ログを個別に解析 | どのファイルが失敗したかを正確に把握できる | 接続回数・ログファイル数が増える |
| ③ クライアント切替 | WinSCP / psftp / ライブラリ等へ乗り換え | 終了コードやログ形式が整理されていて扱いやすい | 新ソフトの導入・検証コストがかかる |
① sftp の出力ログを解析して失敗を検知する(最小変更)
既存のバッチを大きく変えずに精度を上げる方法が、ログ解析です。ポイントは次の 2 つです。
- SFTP 実行結果を 標準出力・標準エラー込みでログにリダイレクトする
findstrなどでエラーっぽいメッセージを検索する
典型的な Windows バッチの例は以下の通りです。
@echo off
setlocal
rem --- SFTP 実行(標準出力+標準エラーをログへ) ---
sftp -b SFTPCMDS.txt -i "%SSHKEY%" %USER%@%HOST% 1> sftp.log 2>&1
rem SFTP コマンド自体の終了コードを退避
set "SFTP_RC=%ERRORLEVEL%"
rem --- ログからエラーメッセージを検出 ---
rem 必要に応じてパターンは追加していく
set "XFER_ERR="
findstr /R /C:"No such file or directory" ^
/C:"Failure" ^
/C:"Permission denied" ^
/C:"Could not open" sftp.log >nul
if %ERRORLEVEL%==0 set "XFER_ERR=1"
rem --- セッション自体のエラーを優先 ---
if not "%SFTP_RC%"=="0" (
echo [ERROR] SFTP セッションが異常終了しました。コード=%SFTP_RC%
exit /b %SFTP_RC%
)
rem --- 個別転送の失敗があった場合 ---
if defined XFER_ERR (
echo [ERROR] SFTP 転送の一部で失敗を検出しました。
exit /b 1
)
echo [INFO] SFTP 転送は全て成功しました。
exit /b 0
なぜ ERRORLEVEL を退避する必要があるのか
Windows バッチでは、コマンドが実行されるたびに %ERRORLEVEL% が上書きされるという仕様があります。つまり、以下のようなことが起きます。
- SFTP 実行 →
%ERRORLEVEL%に SFTP の終了コードが入る findstr実行 → 検索結果に応じて%ERRORLEVEL%が 0 or 1 に書き換えられてしまう
そのため、SFTP 実行直後に変数へ退避しておかないと、あとから「SFTP 自体の終了コード」が分からなくなります。
ログ解析でチェックすべき代表的なメッセージ
検索パターンは運用しながら増やしていくのが現実的ですが、初期値として次のようなものを含めておくと安心です。
| キーワード例 | 典型的な意味 | 想定される原因 |
|---|---|---|
| No such file or directory | 指定したローカル/リモートのパスが存在しない | ファイル名のタイプミス / 出力先フォルダ未作成 等 |
| Failure | ざっくりとした失敗通知(プロトコルエラー含む) | 多岐にわたるため、同時にログを確認する必要あり |
| Permission denied | 権限不足 | リモート側ディレクトリのパーミッション / アカウント設定 |
| Connection closed | サーバ側から接続を切られた | タイムアウト / サーバメンテナンス / セッション制限 |
| Broken pipe | 通信経路の異常終了 | ネットワーク切断 / ファイアウォール 等 |
運用開始後にログを眺めながら、失敗時にだけ現れる語彙を少しずつ追加していくと、検出精度が上がっていきます。
② 1 ファイルずつ個別に SFTP を実行して判定を厳密化
次に、ファイル単位で SFTP を呼び出すパターンです。転送対象が少ない場合や、どのファイルが失敗したかを明確にログに残したい場合に向いています。
@echo off
setlocal enabledelayedexpansion
set "RC=0"
for %%F in (file1.txt file2.txt file3.txt) do (
echo [INFO] 転送開始: %%F
rem 一時的な SFTP コマンドファイルを生成
> _sftp_cmds.tmp echo put "%%~fF" "/remote/path/"
rem SFTP 実行(ファイルごとにログを分ける)
sftp -b _sftp_cmds.tmp -i "%SSHKEY%" %USER%@%HOST% 1> "_sftp_%%~nF.log" 2>&1
set "S=!ERRORLEVEL!"
rem ログから失敗パターンを検出
findstr /R /C:"No such file" /C:"Failure" "_sftp_%%~nF.log" >nul
if !ERRORLEVEL!==0 set "S=1"
if not "!S!"=="0" (
echo [ERROR] 転送失敗: %%F
set "RC=1"
) else (
echo [INFO] 転送成功: %%F
)
)
exit /b %RC%
この方式のメリット・デメリットをもう少し噛み砕くと、次のようになります。
| 観点 | メリット | デメリット |
|---|---|---|
| 障害解析 | ファイルごとにログが分かれるので原因追跡が容易 | ログファイルが多くなりがち |
| リトライ制御 | 失敗したファイルだけ再送するロジックを組みやすい | バッチスクリプトがやや複雑になる |
| パフォーマンス | 1ファイル単位で制御できるため、重いファイルだけ別ジョブに分割可能 | 接続回数が増え、サーバ負荷や所要時間が増大する可能性 |
「毎日数百ファイルを転送する大量バッチ」にはやや不向きですが、「10 ファイル前後を確実に届けたいシステム間連携」などでは非常に有効です。
③ より厳密なクライアント/ライブラリに切り替える
長期的に見ると、終了コードとログが扱いやすい専用クライアントに乗り換えるのが最も安定します。代表例として、以下のような選択肢があります。
- WinSCP(
winscp.com) - PuTTY の
psftp.exe - Python + Paramiko 等のライブラリ
WinSCP を使った SFTP 自動転送の例
WinSCP は Windows 向けの SFTP/FTP クライアントで、スクリプト機能とログ機能が非常に充実しています。コマンドライン版(winscp.com)を使えば、バッチやタスクスケジューラから安全に制御できます。
まず、スクリプトファイル(例:sftpcmds.txt)を用意します。
; sftpcmds.txt
option batch abort
option confirm off
open sftp://USER@HOST/ -privatekey="C:\keys\id_rsa.ppk" -hostkey="ssh-ed25519 AAAA..."
; ローカルの CSV を一括転送
put "C:\data\*.csv" "/remote/path/"
exit
次に、バッチファイルから WinSCP を呼び出します。
@echo off
setlocal
winscp.com /ini=nul ^
/script="C:\scripts\sftpcmds.txt" ^
/log="C:\logs\winscp_%DATE:~0,10%.log"
set "RC=%ERRORLEVEL%"
if not "%RC%"=="0" (
echo [ERROR] WinSCP スクリプトがエラー終了しました。コード=%RC%
exit /b %RC%
)
echo [INFO] WinSCP による SFTP 転送が正常終了しました。
exit /b 0
WinSCP では終了コードの意味が文書化されており、たとえば以下のような目安で扱えます(バージョンによって異なる場合があります)。
| WinSCP 終了コード | 意味の目安 |
|---|---|
| 0 | 全て成功 |
| 1 | スクリプト内でエラー(ファイル転送失敗を含む) |
| 2 以上 | 引数不正・設定ファイルの問題など、より深刻なエラー |
また、XML 形式の詳細ログも出力できるため、必要であれば別ツールや PowerShell で解析して監視システムに連携する、といった高度な運用も簡単に実現できます。
PuTTY psftp.exe を使う場合
PuTTY に含まれる psftp.exe も、Windows 環境ではよく使われる SFTP クライアントです。コマンド構文は sftp と似ていますが、メッセージや終了コードの扱いが多少異なります。
- ログメッセージが比較的分かりやすく、失敗時に特徴的な文字列が出やすい
- OpenSSH sftp と混在させると挙動差でハマるため、「どちらかに統一」するのがおすすめ
基本的な設計方針は OpenSSH sftp と同じで、ログ解析+終了コードの組み合わせで判定精度を高めていきます。
Python / Paramiko でコードから制御する
さらなる柔軟性が必要な場合、アプリケーションコード側で SFTP を制御してしまう方法もあります。Python の Paramiko などのライブラリを使うと、
- ファイル単位の成功/失敗を 例外として受け取りやすい
- リトライ回数や待ち時間などを細かくチューニングできる
- 結果の集計を JSON 等で吐き出し、監視システムと連携しやすい
ただし、運用チームがバッチファイルしか触れない環境では導入ハードルが高くなるため、「新規システムや中〜大規模案件で採用を検討する」くらいの位置づけで考えるとよいでしょう。
PowerShell での ERRORLEVEL($LASTEXITCODE)とログ解析
Windows 10/11 環境では、バッチファイルではなく PowerShell で SFTP をラップするケースも増えています。PowerShell では、コマンドの終了コードは $LASTEXITCODE で取得します。
$logPath = "C:\logs\sftp_$(Get-Date -Format yyyyMMdd_HHmmss).log"
# SFTP 実行(標準出力+標準エラーをログへ)
sftp -b SFTPCMDS.txt "$env:USER@$env:HOST" 1> $logPath 2>&1
$rc = $LASTEXITCODE
# ログからエラーっぽい行を抽出
$errorPatterns = @(
"No such file or directory",
"Failure",
"Permission denied",
"Connection closed"
)
$hasXferError = Select-String -Path $logPath -Pattern $errorPatterns -SimpleMatch -Quiet
if ($rc -ne 0) {
Write-Host "[ERROR] SFTP セッションが異常終了しました。コード=$rc"
exit $rc
}
if ($hasXferError) {
Write-Host "[ERROR] SFTP 転送の一部で失敗を検出しました。"
exit 1
}
Write-Host "[INFO] SFTP 転送は全て成功しました。"
exit 0
PowerShell を使うと、配列や正規表現を自然に扱えるため、ログ解析ロジックをきれいに書けるのが大きな利点です。新規のジョブ開発であれば、バッチファイルより PowerShell を標準にするのも有力な選択肢です。
バッチ実装でハマりがちなポイント
SFTP 側の仕様だけでなく、Windows バッチそのものにもいくつか罠があります。代表的なものを挙げておきます。
ERRORLEVEL の上書きタイミング
- 先ほど触れたように、コマンド実行のたびに ERRORLEVEL は上書きされます
echoやsetで変わることは少ないですが、findstrやcopy等では普通に変わります- 判断材料として使う終了コードは、必ずすぐ変数に退避しておくのが鉄則です
パスの空白と引用符
Windows のファイルパスでは空白を含むことが多く、SFTP コマンドを書くときに次のようなミスが頻発します。
- ローカルパスの引用符が足りない → ファイル名が分割されて認識される
- リモートパスの末尾スラッシュの有無で動作が変わる
安全な書き方の一例を挙げておきます。
put "C:\work\send files\*.csv" "/remote/path/"
バッチ内でパスを展開するときも、"%~fF" や "%SFTP_DIR%" のように、変数ごと引用符で囲むのがおすすめです。
改行コード(CRLF / LF)の違い
Windows で作成した SFTP コマンドファイル(-b で指定するもの)は CRLF、UNIX/Linux 側で作ると LF になります。混在していても動くことが多いですが、環境によっては以下のようなトラブル要因になります。
- 末尾に余計な制御文字が付いて、サーバ側で想定外のコマンドとして解釈される
- 文字化けとの組み合わせで、ログ解析が難しくなる
特に Windows で Git を使っている場合、改行コードの自動変換設定(core.autocrlf)が SFTP コマンドファイルにも影響することがあるため、リポジトリ設計時に注意しておくと安心です。
ERRORLEVEL とログ解析を組み合わせた実装指針
ここまでの内容を、実務で使える「設計の型」としてまとめると次のようになります。
- SFTP セッションの成功/失敗は ERRORLEVEL で見る
→ 0 以外なら即エラー終了。 - ファイル単位の成功/失敗はログ解析で補完する
→ エラー語彙を運用しながらチューニング。 - JOB としての戻り値の設計
- 致命的エラー(接続不可など)…元の ERRORLEVEL をそのまま返す
- 一部ファイル失敗 … 通常 1 を返し、監視側で「要確認」扱いにする
- 全て成功 … 0 を返す
- 安定運用が必須ならクライアント切替を検討
→ WinSCP や psftp、Paramiko 等。 - 新規開発なら PowerShell ベースを優先
→ ログ解析やリトライロジックをきれいに実装できる。
まとめ:SFTP の ERRORLEVEL に頼りすぎない設計を
最後に、本記事のポイントを整理します。
- OpenSSH 系 sftp のバッチモードでは、個々のファイル転送の失敗が ERRORLEVEL に確実には反映されない
- ERRORLEVEL は、主に「セッション全体の成功/失敗」を表すと考えるべき
- 接続・認証エラーなどの致命的な問題では、非 0(多くは 255)になることが多い
- ファイルレベルの成否判定には、ログ解析や個別実行などの工夫が必須
- 長期運用や大規模ジョブでは、WinSCP や psftp、Paramiko 等への切り替えも有力な選択肢
- Windows 環境では、PowerShell による制御がログ解析や監視連携の面で有利
SFTP を「ただのファイル転送ツール」として扱うのではなく、「エラーの出方に癖があるコンポーネント」として理解しておくと、障害対応のストレスがぐっと減ります。ERRORLEVEL を過信せず、ログと組み合わせた堅実な設計を心がけてください。

コメント