SFTP の ERRORLEVEL(終了コード)仕様と正しいエラー判定方法【Windowsバッチ対応】

バッチファイルから 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 の値意味の目安注意点
0SFTP セッションとしては正常終了一部ファイルの転送失敗が含まれていても 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% が上書きされるという仕様があります。つまり、以下のようなことが起きます。

  1. SFTP 実行 → %ERRORLEVEL% に SFTP の終了コードが入る
  2. 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 とログ解析を組み合わせた実装指針

ここまでの内容を、実務で使える「設計の型」としてまとめると次のようになります。

  1. SFTP セッションの成功/失敗は ERRORLEVEL で見る
    → 0 以外なら即エラー終了。
  2. ファイル単位の成功/失敗はログ解析で補完する
    → エラー語彙を運用しながらチューニング。
  3. JOB としての戻り値の設計
    • 致命的エラー(接続不可など)…元の ERRORLEVEL をそのまま返す
    • 一部ファイル失敗 … 通常 1 を返し、監視側で「要確認」扱いにする
    • 全て成功 … 0 を返す
  4. 安定運用が必須ならクライアント切替を検討
    → WinSCP や psftp、Paramiko 等。
  5. 新規開発なら PowerShell ベースを優先
    → ログ解析やリトライロジックをきれいに実装できる。

まとめ:SFTP の ERRORLEVEL に頼りすぎない設計を

最後に、本記事のポイントを整理します。

  • OpenSSH 系 sftp のバッチモードでは、個々のファイル転送の失敗が ERRORLEVEL に確実には反映されない
  • ERRORLEVEL は、主に「セッション全体の成功/失敗」を表すと考えるべき
  • 接続・認証エラーなどの致命的な問題では、非 0(多くは 255)になることが多い
  • ファイルレベルの成否判定には、ログ解析や個別実行などの工夫が必須
  • 長期運用や大規模ジョブでは、WinSCP や psftp、Paramiko 等への切り替えも有力な選択肢
  • Windows 環境では、PowerShell による制御がログ解析や監視連携の面で有利

SFTP を「ただのファイル転送ツール」として扱うのではなく、「エラーの出方に癖があるコンポーネント」として理解しておくと、障害対応のストレスがぐっと減ります。ERRORLEVEL を過信せず、ログと組み合わせた堅実な設計を心がけてください。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次