シェルスクリプトでSFTPファイル転送を自動化する設計|バッチモードとSSH鍵認証で安全な自動転送を実装する方法

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)シェルスクリプト > シェルスクリプトでSFTPファイル転送を自動化する設計|バッチモードとSSH鍵認証で安全な自動転送を実装する方法
「SFTPでのファイル転送、毎回手作業でやっている」——そういうエンジニアは意外に多い。
パスワードを打ち込んでログインして、ファイルをputして、接続を閉じる。1回2分の作業でも、1日10回やれば20分の消費だ。cronで深夜に自動実行したくても、パスワード入力が必要では実現できない。

この記事では、SSH鍵認証とSFTPのバッチモードを組み合わせて、シェルスクリプトからSFTPファイル転送を完全自動化する設計を解説する。アップロード・ダウンロードの実装パターンから、エラー検知の落とし穴、ログ設計、cronとの組み合わせまで実践コード付きで紹介する。動作確認環境はRHEL 9.4 / Ubuntu 24.04 LTS。

この記事のポイント

・SSH鍵認証でパスワード不要のSFTP自動接続を確立できる
・sftp -b でバッチファイルを渡せばput/getを無人実行できる
・sftpはファイル未存在でもexit 0を返すので戻り値だけに頼らない設計が必要
・ログとcronを組み合わせれば深夜の定期転送が実現できる


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

SFTPをシェルスクリプトで自動化する前に知っておくこと

SFTPはSSHプロトコル上で動くファイル転送サブシステムだ。FTPと名前が似ているが別物で、通信は全てSSHセッション内で暗号化される。

シェルスクリプトからSFTP転送を自動化するには、次の2つが前提になる。

SSH鍵認証:パスワード入力なしでSFTP接続できる状態にする
バッチモード(-b オプション):コマンドをファイルに記述してsftpに渡す

対話モード(sftpを起動してコマンドを打つ)ではスクリプト自動実行ができない。バッチモードにすることで、cronやシェルスクリプトから呼び出せるようになる。

なお、SFTPはデフォルトでSSHポート(22番)を使う。ポート番号の開放状況を確認したい場合は Linux ポート確認の全コマンドが参考になる。

SSH鍵認証のセットアップ(パスワードなし接続の準備)

自動化の第一歩はSSH鍵認証の確立だ。鍵認証が通れば、以降のSFTP接続でパスワードを求められなくなる。

1. SSH鍵ペアを生成する

転送元サーバー(スクリプトを動かすサーバー)で鍵ペアを生成する。パスフレーズなし(Enterキーのみ)で作成することで、無人実行が可能になる。

# 転送元サーバーで実行 $ ssh-keygen -t ed25519 -f ~/.ssh/sftp_key -N "" Generating public/private ed25519 key pair. Your identification has been saved in /home/operator/.ssh/sftp_key Your public key has been saved in /home/operator/.ssh/sftp_key.pub The key fingerprint is: SHA256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx operator@transfer01

-N "" でパスフレーズを空にする。運用スクリプト専用の鍵を用途ごとに分けて管理するのが望ましい。既存の id_ed25519 と混在させないこと。

2. 公開鍵をSFTPサーバーへ登録する

生成した公開鍵(.pub)を接続先SFTPサーバーの authorized_keys に追記する。

# 転送先SFTPサーバーのユーザーホームに公開鍵をコピー $ ssh-copy-id -i ~/.ssh/sftp_key.pub -p 22 sftp-user@192.168.1.50 # または手動でauthorized_keysに追記(ssh-copy-idが使えない環境) $ cat ~/.ssh/sftp_key.pub | ssh sftp-user@192.168.1.50 "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"

authorized_keys のパーミッションが 600 でなければ、SSHサーバーが鍵を無視する。ここが原因で認証失敗するケースは多いので注意してほしい。

3. 接続確認

鍵認証でSFTPに接続できるか確認する。パスワードを一切求めずに接続できれば成功だ。

# 鍵を指定してSFTP接続テスト(パスワードなしで繋がるか確認) $ sftp -i ~/.ssh/sftp_key sftp-user@192.168.1.50 Connected to 192.168.1.50. sftp> pwd Remote working directory: /home/sftp-user sftp> exit

SFTPバッチモードで自動転送スクリプトを作る

SSH鍵認証が確立できたら、バッチファイルを使った自動転送の実装に入る。

1. SFTPバッチファイルの基本

sftp コマンドに -b オプションでコマンドファイルを渡すと、対話なしに実行できる。バッチファイルには sftp の対話コマンドをそのまま書く。

# バッチファイルの例(/tmp/sftp_batch.txt) cd /remote/upload put /local/data/report_20260829.csv bye

# バッチファイルを指定してsftp実行 $ sftp -i ~/.ssh/sftp_key -b /tmp/sftp_batch.txt sftp-user@192.168.1.50 Uploading /local/data/report_20260829.csv to /remote/upload/report_20260829.csv /local/data/report_20260829.csv 100% 48KB 1.2MB/s 00:00

バッチファイルは毎回固定ファイルに上書きするか、mktemp で一時ファイルを生成するかのいずれかを選ぶ。複数プロセスが同時実行する可能性がある場合は必ず mktemp を使うこと。

2. アップロードスクリプトの実装

実際の運用に使えるアップロードスクリプトの例を示す。転送するファイルをループで処理し、全件完了後に一時ファイルを削除する設計にする。

#!/bin/bash set -euo pipefail SFTP_HOST="192.168.1.50" SFTP_USER="sftp-user" SFTP_KEY="/home/operator/.ssh/sftp_key" REMOTE_DIR="/remote/upload" LOCAL_DIR="/var/transfer/outbox" LOG_FILE="/var/log/sftp_upload.log" log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "${LOG_FILE}" } # 転送対象ファイルを確認 FILES=("${LOCAL_DIR}"/*.csv) if [[ ! -e "${FILES[0]}" ]]; then log "転送対象ファイルなし。終了します" exit 0 fi # バッチファイルを一時生成 BATCH_FILE=$(mktemp /tmp/sftp_batch.XXXXXX) trap 'rm -f "${BATCH_FILE}"' EXIT { echo "cd ${REMOTE_DIR}" for f in "${FILES[@]}"; do echo "put ${f}" done echo "bye" } > "${BATCH_FILE}" # SFTP実行 log "アップロード開始: ${#FILES[@]}件" if sftp -i "${SFTP_KEY}" -b "${BATCH_FILE}" "${SFTP_USER}@${SFTP_HOST}" >> "${LOG_FILE}" 2>&1; then log "アップロード完了: ${#FILES[@]}件" else log "ERROR: sftpコマンドがエラーを返しました" exit 1 fi

set -euo pipefail により、sftp の異常終了やパイプラインエラーでスクリプト全体が停止する。trap で EXIT シグナルに一時ファイルの削除を登録しておくことで、異常終了後もファイルが残らない設計にしている。

3. ダウンロードスクリプトの実装

リモートから特定パターンのファイルを取得するケースの実装例だ。

#!/bin/bash set -euo pipefail SFTP_HOST="192.168.1.50" SFTP_USER="sftp-user" SFTP_KEY="/home/operator/.ssh/sftp_key" REMOTE_DIR="/remote/download" LOCAL_DIR="/var/transfer/inbox" LOG_FILE="/var/log/sftp_download.log" log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "${LOG_FILE}" } BATCH_FILE=$(mktemp /tmp/sftp_batch.XXXXXX) trap 'rm -f "${BATCH_FILE}"' EXIT { echo "cd ${REMOTE_DIR}" echo "lcd ${LOCAL_DIR}" echo "mget *.csv" echo "bye" } > "${BATCH_FILE}" log "ダウンロード開始" if sftp -i "${SFTP_KEY}" -b "${BATCH_FILE}" "${SFTP_USER}@${SFTP_HOST}" >> "${LOG_FILE}" 2>&1; then log "ダウンロード完了" else log "ERROR: sftpコマンドがエラーを返しました" exit 1 fi

mget はワイルドカードで複数ファイルを一括取得できる。lcd はローカルの移動先ディレクトリを指定する。

エラー処理とログ設計

1. sftpの終了コードの落とし穴と対策

sftpコマンドには注意すべき挙動がある。put対象ファイルが存在しない場合でも、sftp は exit 0 を返すことがあるのだ。つまり、終了コードだけを見ていると「転送失敗が成功と判定されてしまう」という問題が起きる。

この問題を防ぐには、転送後にリモートで ls を実行してファイルが存在することを確認する方法が有効だ。

# 転送後にリモートのファイル存在を確認する例 VERIFY_BATCH=$(mktemp /tmp/sftp_verify.XXXXXX) trap 'rm -f "${VERIFY_BATCH}"' EXIT TARGET_FILE="report_$(date '+%Y%m%d').csv" { echo "cd ${REMOTE_DIR}" echo "ls ${TARGET_FILE}" echo "bye" } > "${VERIFY_BATCH}" if sftp -i "${SFTP_KEY}" -b "${VERIFY_BATCH}" "${SFTP_USER}@${SFTP_HOST}" 2>&1 | grep -q "${TARGET_FILE}"; then log "検証OK: ${TARGET_FILE} をリモートで確認" else log "ERROR: リモートにファイルが存在しない" exit 1 fi

sftp の出力を grep でパターンマッチすることで、ファイル存在を実質的に検証できる。「転送したつもり」のまま数日気づかない、という事故はこのひと手間で防げる。

2. ログ出力と転送件数の記録

長期運用ではログが命綱になる。転送件数・開始時刻・終了時刻・ファイル名を記録しておくと、トラブル発生時の調査が圧倒的に速くなる。

# ログ関数(タイムスタンプ付きでファイルと標準出力に二重出力) LOG_FILE="/var/log/sftp_transfer.log" log() { local level="$1" shift echo "[$(date '+%Y-%m-%d %H:%M:%S')] [${level}] $*" | tee -a "${LOG_FILE}" } # 使用例 log "INFO" "転送開始: ${COUNT}件" log "ERROR" "接続失敗: ${SFTP_HOST}" log "INFO" "転送完了: 所要時間 ${ELAPSED}秒"

ログファイルは /var/log/ 配下に置き、logrotate で世代管理するのが定石だ。スクリプト側ではサイズ管理をせず、logrotate に委ねることで設計をシンプルに保てる。

cronで定期自動転送を組み込む

スクリプトが完成したら、cronで定期実行に組み込む。crontab の設定例を示す。

# crontab -e で編集(operator ユーザー) # PATH と HOME を明示しないとスクリプト内のコマンドが見つからないことがある PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin HOME=/home/operator # 毎日 02:30 にアップロードを実行 30 2 * * * /home/operator/scripts/sftp_upload.sh # 毎時5分にダウンロードを確認 5 * * * * /home/operator/scripts/sftp_download.sh

cronからスクリプトを実行する場合、環境変数 PATH が通常のシェルと異なるため、SSH鍵のパスやコマンドパスは全て絶対パスで記述することを推奨する。cron実行時は HOME も /root になる場合があるので、鍵ファイルのパスは ~/ を使わず /home/operator/.ssh/sftp_key のように書いておくと安全だ。

スクリプト自体は実行ビットを立てておくこと。

$ chmod 700 /home/operator/scripts/sftp_upload.sh $ chmod 700 /home/operator/scripts/sftp_download.sh

「Permission denied」「No such file or directory」が出た時の対処法

「Permission denied (publickey)」が出る場合
SSH鍵認証の設定に問題がある。以下を順番に確認する。

・SFTPサーバー側の ~/.ssh/authorized_keys のパーミッションが 600 になっているか
・~/.ssh ディレクトリ自体のパーミッションが 700 になっているか
・sftp_key.pub の内容が authorized_keys に正確にコピーされているか(余分な改行や空白が入っていないか)
・/etc/ssh/sshd_config で PubkeyAuthentication yes になっているか

接続の詳細ログを確認するには sftp に -vvv オプションを付けて実行する。

# 詳細ログ出力で認証フローを確認する $ sftp -vvv -i ~/.ssh/sftp_key sftp-user@192.168.1.50

「No such file or directory」が put 時に出る場合
リモートの移動先ディレクトリが存在しない可能性が高い。バッチファイルの cd コマンドより前に、mkdir コマンドを追加して必要なディレクトリを作成する。

# バッチファイルの先頭でディレクトリを作成しておく -mkdir /remote/upload cd /remote/upload put /local/data/file.csv bye

mkdir の前にハイフン(-)を付けると、ディレクトリが既に存在してエラーになっても無視して処理を続ける。これはバッチモード特有の書き方だ。

まとめ

SFTPファイル転送のシェルスクリプト自動化を設計するポイントをまとめる。

やりたいこと コマンド・設定
SSH鍵ペア生成(パスフレーズなし) ssh-keygen -t ed25519 -f ~/.ssh/sftp_key -N ""
公開鍵をリモートへ登録 ssh-copy-id -i ~/.ssh/sftp_key.pub sftp-user@HOST
バッチモードでSFTP実行 sftp -i ~/.ssh/sftp_key -b batch.txt user@HOST
一時バッチファイル生成 mktemp /tmp/sftp_batch.XXXXXX
一時ファイルの自動削除 trap 'rm -f "${BATCH}"' EXIT
バッチ内でディレクトリ作成(エラー無視) バッチファイルに -mkdir /remote/dir と記述(エラー無視)
複数ファイルのアップロード put /local/path/file.csv(ループで複数行生成)
ワイルドカードでダウンロード mget *.csv
接続詳細ログ確認 sftp -vvv -i KEY user@HOST
SFTPは「sftp コマンド自体は成功してもファイル転送が失敗している」ケースがある点が最大の落とし穴だ。exit code だけに頼らず、転送後のリモートファイル存在確認まで設計に含めることで、サイレント障害を防げる。

SSH鍵認証の確立 → バッチファイルの生成 → sftp実行 → 事後検証 → ログ記録という一連の流れをシェルスクリプトにまとめれば、cronによる深夜の無人転送が実現できる。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、20年以上の運用経験を持つ現役エンジニアが基礎から教えます。
シェルスクリプト講座を見る >>

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。