Linuxの flock コマンドは、ファイル(またはディレクトリ)に共有/排他ロックを取得し、そのロックを保持したままコマンドを実行できるユーティリティです。util-linux パッケージに含まれ、Debian/Ubuntu系では標準でインストールされています。スクリプトの多重起動防止(cronジョブの二重起動防止を含む)や、並列処理のクリティカルセクション保護に広く使われます。ロック対象が存在しない場合は自動で作成されます。(man7.org, EuroLinux)
ファイルロックとは
ファイルロックとは、複数のプロセスが同一ファイルや共有リソースに同時アクセスするときに排他制御を行う仕組みです。Linux では主に次の2種類のロックがあります。
- 排他ロック(Exclusive Lock / Write Lock):ロック保持中は他のプロセスが同じファイルのロックを取得できない。書き込み処理など「1プロセスのみ」にしたい場合に使う(
flockのデフォルト動作)。 - 共有ロック(Shared Lock / Read Lock):複数プロセスが同時に保持できる。読み取り処理を並列化する際に使う(排他ロックとは共存不可)。
-sオプションで取得。
flock のロックはアドバイザリロック(協調的ロック)です。ロックを使わないプロセスは無視してアクセスできるため、関係するすべてのスクリプト・プロセスが同じロック方式を使うことが前提となります。ロックはファイルディスクリプタ(FD)に紐付いており、プロセスが終了するとFDが閉じられ、ロックも自動的に解除されます。スクリプトが異常終了してもロックは自動解放されます。
構文(Syntax)
# 形式1: ロック対象を指定してコマンドを実行
flock [OPTIONS] FILE|DIR COMMAND [ARG...]
# 形式2: シェルの FD をロック(exec 等で FD を開いてから)
flock [OPTIONS] FD
# 便利形: 文字列でコマンドを渡す
flock [OPTIONS] FILE|DIR -c 'SHELL_COMMANDS'
- 既定は排他ロック。共有ロックは
-s。取得できない場合は待機します(-nで即時失敗)。(man7.org)
主なオプション一覧
| オプション | 説明 | 使用例 |
|---|---|---|
-s, --shared | 共有ロック(読取側など複数可) | flock -s /tmp/data.lock -c 'cat data' (man7.org) |
-x, --exclusive (-e 同義) | 排他ロック(書込側1つのみ、既定) | flock -x /var/lock/job.lock myjob (man7.org) |
-n, --nonblock | 取得できなければ即時失敗 | flock -n /tmp/backup.lock -c './backup' (man7.org) |
-w, --timeout SEC | タイムアウト秒を指定 | flock -w 30 /tmp/lock -c 'do_slow' (man7.org) |
-E, --conflict-exit-code N | 競合時の終了コードを N に | flock -n -E 100 /tmp/lock cmd (ManKier) |
-o, --close | コマンド実行前にロックFDを閉じる(子プロセスへロックを継承させない用途) | flock -o /tmp/lock -c 'spawn-children' (ManKier) |
-F, --no-fork | フォークせずに直接コマンドを実行(--closeと非両立) | flock -F /tmp/lock cmd (ManKier) |
-c 'CMD' | コマンド文字列をシェルに渡す | flock /tmp/l -c 'echo hi; sleep 1' (man7.org) |
-u, --unlock | 明示的にアンロック(FDモードで使用) | flock -u 200 (man7.org) |
-v, --verbose / -h, --help / -V, --version | 逐次ログ / ヘルプ / バージョン | flock -v -n /tmp/lock true (man7.org) |
-oは「実行前に FD を閉じる」ため、子プロセスにロックを持たせたくない場合に有用です。--no-fork(-F)と同時指定は不可です。(ManKier)
ロックファイルの置き場所
ロックファイルの置き場所は、実行環境と用途によって使い分けます。
| 場所 | 特徴 | 適した用途 |
|---|---|---|
/var/lock/ | OS 再起動時にクリアされる(tmpfs の場合) | システム全体で共有するデーモン・バッチ処理 |
/run/ または /var/run/ | systemd 管理の RAM ディスク。再起動でクリア。/var/lock より推奨 | システムサービス・init スクリプト |
/tmp/ | OS 再起動時にクリア。すべてのユーザーが書き込み可 | 一時的な処理・簡易スクリプト |
| アプリ専用ディレクトリ | 権限管理が明確。絶対パスで指定すること | アプリ固有のバッチ処理 |
絶対パスで指定することが重要です。flock ./myapp.lock のように相対パスで指定すると、カレントディレクトリが変わった瞬間に別のファイルをロックしてしまい、意図した排他制御ができなくなります。cron から実行するスクリプトでは特に注意してください。
# 悪い例:相対パス(cron 実行時はホームディレクトリになることがある)
flock ./myapp.lock -c './batch.sh'
# 良い例:常に絶対パスで指定
flock /var/lock/myapp.lock -c '/usr/local/bin/batch.sh'
実行例
多重起動を防ぐ(即時失敗)
説明: すでに動作中なら即終了(終了コード 1 など)。
コマンド:
flock -n /var/lock/myjob.lock -c '/usr/local/bin/myjob'
echo "exit=$?"
出力例(ロック獲得失敗時):
exit=1
(-E で任意コードに変更可) (man7.org, ManKier)
cron で“かぶり”を防止
説明: 1分おきでも重ならないように。
コマンド(crontab 行):
*/1 * * * * flock -n /tmp/backup.lock -c '/usr/local/sbin/backup'
(ex1-lab)
FD モードでクリティカルセクションを囲む(シェル)
説明: スクリプト全体でロックを保持。
コマンド:
exec 200>/var/lock/task.lock # FD=200 を開く(存在しなければ作成)
flock -n 200 || { echo "busy"; exit 1; } # 取れなければ即終了
# ここからクリティカルセクション
update_db
generate_report
flock -u 200 # 明示的にアンロック(終了時にFDが閉じられても解除されます)
共有ロックで“読み手”を並列許可
説明: 読み取り処理は並行、書き込みは別途排他ロックで制御。
コマンド:
flock -s /tmp/data.lock -c 'cat bigdata.csv | wc -l'
(man7.org)
ディレクトリをロック対象にする
説明: ファイルだけでなくディレクトリにもロックできます。
コマンド:
flock -w 10 /var/lock/myapp 'sh -c "touch /tmp/doing; sleep 2"'
エラー例:--no-fork と --close の併用
説明: 併用するとロックを保持するプロセスがなくなるためエラー。
コマンド:
flock -F -o /tmp/lock -c 'echo NG'
出力例(例): オプション不正のエラー
根拠: 両者は非両立です。(ManKier)
実務ユースケース
cronジョブの二重起動を完全に防ぐスクリプト
定期実行バッチで前回の処理が終わっていないのに次の起動が始まる「かぶり問題」を、flock を使ったスクリプトで解決できます。
#!/bin/bash
# /usr/local/sbin/daily-report.sh
LOCK_FILE="/var/lock/daily-report.lock"
LOG_FILE="/var/log/daily-report.log"
(
flock -n 200 || {
echo "[$(date '+%F %T')] 既に実行中のため終了" >> "$LOG_FILE"
exit 1
}
echo "[$(date '+%F %T')] ジョブ開始" >> "$LOG_FILE"
# メイン処理
/usr/local/bin/generate-report
echo "[$(date '+%F %T')] ジョブ完了" >> "$LOG_FILE"
) 200>"$LOCK_FILE"
crontab への登録例(毎日2時に実行):
0 2 * * * /usr/local/sbin/daily-report.sh
ポイント:() でサブシェルを作り、FD 200 番でロックを取得します。サブシェルが終了するとFDが自動で閉じられ、ロックも解除されます。
バッチ処理の共有リソースをロックで保護する
複数のワーカープロセスが並列で動く場合、共有リソース(DBへの書き込みや共有ファイルの更新)だけを flock で直列化し、スループットを落とさずに安全性を確保できます。
#!/bin/bash
# 共有リソースのみロック対象にするバッチワーカー
LOCK_FILE="/var/lock/batch-worker.lock"
RESULT_FILE="/tmp/batch-results.log"
process_item() {
local item="$1"
# 独立した処理は並列で実行(ロック不要)
local result
result=$(heavy_computation "$item")
# 共有リソース(結果ファイルへの追記)だけロック
flock -w 30 "$LOCK_FILE" bash -c "echo '$result' >> '$RESULT_FILE'"
if [ $? -ne 0 ]; then
echo "ERROR: ロック取得タイムアウト (item=$item)" >&2
return 1
fi
}
export -f process_item
# 4並列でCSVを処理
find /data/input -name "*.csv" | xargs -P 4 -I{} bash -c 'process_item "$@"' _ {}
よくある失敗例
失敗1:ロックファイルを消せばロックが解除されると誤解する
flock のロックはファイルの存在ではなくFD(ファイルディスクリプタ)に紐付いています。ロックファイルを削除しても、そのファイルを開いているプロセスが存在する間はロックは有効です。逆に、プロセスが終了していればファイルが残っていてもロックは既に解除済みです。
# NG: ロックファイルを削除してもロックは解除されない
rm /var/lock/myjob.lock # これではロックは解除されない
# 正しい対処: 保持プロセスを確認して終了させる
fuser /var/lock/myjob.lock # ロックを持つプロセスの PID を確認
kill <PID> # プロセスを終了させる
失敗2:スクリプト全体ではなく一部だけロックしてしまう
多重起動防止が目的なのに、スクリプトの一部処理だけを flock で囲むと、スクリプト全体は並行して実行されてしまいます。多重起動防止にはスクリプト先頭でロックを取得し、処理全体をロック保護してください。
#!/bin/bash
# 悪い例:一部の処理だけロック(スクリプト全体は多重起動する)
heavy_pre_process # ここは並行実行される
flock /var/lock/myapp.lock -c 'write_db' # DB への書き込みだけロック
heavy_post_process # ここも並行実行される
#!/bin/bash
# 良い例:スクリプト全体をロック(先頭でロックを取得)
(
flock -n 200 || { echo "既に実行中"; exit 1; }
heavy_pre_process
write_db
heavy_post_process
) 200>/var/lock/myapp.lock
失敗3:相対パスでロックファイルを指定する
相対パスで指定するとカレントディレクトリに依存し、実行環境によって異なるファイルをロックすることになります。cron から実行するスクリプトでは、カレントディレクトリがホームディレクトリなどになる場合があり、意図しないパスをロックしてしまいます。
# 悪い例:相対パス(cron 実行時にカレントディレクトリが変わると別ファイルをロック)
flock ./myapp.lock -c './batch.sh'
# 良い例:常に絶対パスで指定
flock /var/lock/myapp.lock -c '/usr/local/bin/batch.sh'
失敗4:権限不足でロックファイルを作れない
ロックファイルが存在しない場合、flock は自動でファイルを作成しようとします。そのディレクトリへの書き込み権限がない場合はエラーになります。
# エラー例:/var/lock/ に書き込み権限がない場合
$ flock /var/lock/myjob.lock -c 'true'
flock: /var/lock/myjob.lock: Permission denied
# 確認方法
ls -la /var/lock/
# 対処1: 実行ユーザーに書き込み権限を付与
sudo chown $(whoami) /var/lock/ # または適切なグループ設定
# 対処2: 権限のあるディレクトリに変更
flock /tmp/myjob.lock -c './job.sh'
トラブルシューティング
ロックが解除されない・ロックファイルが残った場合
まず確認すること:flock によるロックはプロセスが終了すると自動的に解除されます。ロックファイル自体が残っていても、保持プロセスがなければロックは取得できます。
# ロックを保持しているプロセスを確認する
fuser /var/lock/myjob.lock
# または lsof で詳細を確認
lsof /var/lock/myjob.lock
# プロセスが存在しない場合 → ロックは既に解除済み(取得可能)
# プロセスが存在する場合 → そのプロセスの終了を待つか強制終了する
kill -9 <PID>
ロックファイルの削除について:flock のロックはファイルの存在ではなくFDに紐付くため、ファイルを削除してもプロセスが生きている間はロックは有効です。逆に、プロセスが死んでいればファイルが残っていてもロックは解除済みです。ファイルは削除しても問題ありませんが、削除は根本解決にならない点に注意してください。
タイムアウト(-w)を使った安全な待機と失敗制御
-n(即時失敗)ではなく -w SEC(N秒待機)を使うと、一時的な競合でも処理が継続できます。タイムアウト後の処理は終了コードで分岐してください。
# 30秒待機してからあきらめる
flock -w 30 /var/lock/slow-job.lock -c './slow-process.sh'
EXIT_CODE=$?
case $EXIT_CODE in
0)
echo "正常完了"
;;
1)
echo "ロック取得失敗またはコマンドエラー" >&2
exit 1
;;
esac
タイムアウト時間の目安:通常処理時間の2〜3倍を設定すると、一時的な遅延は吸収しつつ異常な長時間ロックは検知できます。-E オプションでタイムアウト専用の終了コードを設定すれば、コマンドエラーとの区別も可能です。
# -E 100 でタイムアウト時の終了コードを 100 に設定
flock -w 30 -E 100 /var/lock/job.lock -c './job.sh'
case $? in
0) echo "成功" ;;
100) echo "タイムアウト(ロック競合)" >&2 ;;
*) echo "コマンドエラー" >&2 ;;
esac
flock と他のロック方法との違い
| 方法 | 特徴 | 適した場面 |
|---|---|---|
flock(Linux コマンド) | プロセス終了で自動解放。既存コマンドをラップできる | シェルスクリプト・cronジョブの多重起動防止 |
lockfile(procmail) | ファイルの存在でロック。自動解放なし(明示削除が必要) | 古いシステム・シンプルな用途 |
setlock(daemontools) | コマンドが終わるまでロック保持。daemontools 環境向け | daemontools 管理のサービス |
mkdir の原子作成 | 手軽だが解除漏れ検知・タイムアウトは自前実装が必要 | シンプルな用途・ポータビリティ重視 |
flock(2)(システムコール) | C プログラムから呼ぶカーネルAPI。NFS での挙動差あり | C/C++ アプリのロック制御 |
PHPの flock() との違い
PHP にも flock() 関数が存在しますが、Linux コマンドの flock とは異なります。
- PHPの
flock():PHPスクリプト内からファイルロックを取得する関数。fopen()で開いたファイルハンドルを引数に取る。ロック種別はLOCK_EX(排他)/LOCK_SH(共有)/LOCK_UN(解除)/LOCK_NB(ノンブロッキング)。 - Linuxコマンドの
flock:シェルから任意のコマンドをロック付きで実行するツール。PHPスクリプトから呼ぶこともできる(shell_exec('flock ...'))が、通常はPHPの組み込み関数を使う。
「flock」で検索すると PHP マニュアルの flock() ページも上位に表示されますが、本記事で扱っているのはLinuxコマンドラインの flock(util-linux パッケージ)です。
関連コマンド
flock(2): カーネルのアドバイザリロックAPI。NFS などでの挙動差の説明もこちらにあり。(man7.org)fcntl(2): POSIX レコードロック(NFS での相互運用に利用)。(man7.org)lockfile(procmail)/setlock(daemontools): ロックファイルを用いる別系統の直列化ツール。mkdirの原子作成を使う簡易ロック:手軽だが解除漏れ検知などは自前実装が必要。
備考
- アドバイザリ:
flockのロックは協調的(アドバイザリ)です。ロックを無視して I/O することも可能なので、協調する全プロセスでflockなど同一方式を用いてください。(man7.org) - NFS / ネットワークFS: Linux 2.6.12 以降の NFS クライアントでは、
flock()が fcntl のバイト範囲ロックでエミュレートされ、相互に干渉します(排他は書き込み用にオープンが必要)。古い環境・他実装では非対応/局所動作のことがあるため注意。(Arch Linux マニュアルページ) - ディスクリプタ継承:
forkで複製された子は同じロックを保持できます。子にロックを持たせたくない場合は--closeを検討。(ManKier, ウィキペディア) - ロック対象の種類: ファイル/ディレクトリどちらでも可。対象が無ければ作成されます。(EuroLinux)
- 終了コード: 競合で失敗した場合は既定で 1 を返します(
-Eで変更可)。-nなしでは待機します。(man7.org, ManKier)
関連記事
- バッチ運用テンプレ|日次ローテ / ロック / 通知:
flockを使ったロック処理を含むバッチスクリプトのテンプレートをまとめています。 - 並列実行パターン|xargs -P / BG+wait:並列処理と
flockによる共有リソース保護の組み合わせを解説しています。 - 定期バックアップ講座|rsync×世代管理×復元テスト:cron で定期実行するバックアップスクリプトの設計・二重起動対策について解説しています。
- 失敗検知メール/Slack通知の定型|bashとWebhookで最短実装:バッチ処理の失敗を検知してメール・Slack で通知する方法をまとめています。
flockと組み合わせると多重起動エラーも通知できます。
参考
- man7.org
flock(1)(util-linux): https://www.man7.org/linux/man-pages/man1/flock.1.html - util-linux
flock(1)(別ミラー): https://man.docs.euro-linux.com/EL%207/util-linux/flock.1.en.html - man7.org
flock(2)(NFS での挙動等): https://man7.org/linux/man-pages/man2/flock.2.html - Mankier
flock(1)(--no-fork/--closeの補足): https://www.mankier.com/1/flock - FreeBSD
flock(1)(BSD 実装の書式): https://man.freebsd.org/cgi/man.cgi?query=flock&sektion=1 - 日本語man(Linux JM): https://linuxjm.sourceforge.io/html/util-linux/man1/flock.1.html
- 使いどころの例(cron での多重起動防止): https://ex1.m-yabe.com/archives/7849

コメント