本文目录导读:

防止脚本重复运行的核心思路是在脚本启动时检测是否已有相同实例在运行,如果有则立即退出,以下是几种常见且有效的方法:
文件锁法(最通用、跨平台)
Shell 脚本
#!/bin/bash
LOCKFILE="/tmp/myscript.lock"
# 尝试创建锁文件
if ! mkdir "$LOCKFILE" 2>/dev/null; then
echo "脚本已在运行,退出。"
exit 1
fi
# 确保退出时清理锁
trap 'rm -rf "$LOCKFILE"' EXIT
# 脚本主体
echo "开始执行..."
sleep 30
echo "执行完毕。"
Python 脚本
import fcntl
import sys
import os
lock_file = '/tmp/myscript.lock'
def check_single_instance():
try:
fd = os.open(lock_file, os.O_CREAT | os.O_TRUNC | os.O_RDWR)
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
return fd # 返回fd供后续使用
except (IOError, BlockingIOError):
print("脚本已在运行,退出。")
sys.exit(1)
if __name__ == "__main__":
fd = check_single_instance()
try:
# 脚本主体
print("开始执行...")
# 你的代码
finally:
os.close(fd)
os.unlink(lock_file)
PID 文件法(适合守护进程)
Shell 脚本
#!/bin/bash
PIDFILE="/var/run/myscript.pid"
# 检查现有PID文件
if [ -f "$PIDFILE" ]; then
PID=$(cat "$PIDFILE")
if kill -0 "$PID" 2>/dev/null; then
echo "脚本已在运行,PID: $PID"
exit 1
fi
fi
# 写入当前PID
echo $$ > "$PIDFILE"
trap 'rm -f "$PIDFILE"' EXIT
# 脚本主体
echo "开始执行..."
sleep 30
Python 脚本
import os
import sys
import atexit
pid_file = '/tmp/myscript.pid'
def write_pid():
if os.path.exists(pid_file):
with open(pid_file, 'r') as f:
old_pid = f.read().strip()
if old_pid and os.path.exists(f'/proc/{old_pid}'):
print(f"脚本已在运行,PID: {old_pid}")
sys.exit(1)
with open(pid_file, 'w') as f:
f.write(str(os.getpid()))
def cleanup():
if os.path.exists(pid_file):
os.unlink(pid_file)
atexit.register(cleanup)
if __name__ == "__main__":
write_pid()
print("开始执行...")
# 你的代码
网络端口法(适合服务类脚本)
Shell 脚本
#!/bin/bash
PORT=9876
# 检查端口是否已被占用
if ss -tln | grep -q ":$PORT "; then
echo "脚本已在运行(端口被占用)"
exit 1
fi
# 启动监听端口
nc -l $PORT &
NC_PID=$!
trap "kill $NC_PID 2>/dev/null" EXIT
# 脚本主体
echo "开始执行..."
sleep 30
Python 脚本
import socket
import sys
def check_port(port=9876):
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('127.0.0.1', port))
sock.listen()
return sock # 保持socket打开
except socket.error:
print("脚本已在运行(端口被占用)")
sys.exit(1)
if __name__ == "__main__":
sock = check_port()
print("开始执行...")
try:
# 你的代码
input("按Enter退出...")
finally:
sock.close()
进程名/命令行匹配法(简单但不可靠)
Shell 脚本
#!/bin/bash
SCRIPT_NAME=$(basename "$0")
# 检查是否有相同脚本名的进程在运行(排除grep和自身)
PIDS=$(pgrep -f "$SCRIPT_NAME" | grep -v $$ | grep -v "grep")
if [ -n "$PIDS" ]; then
echo "脚本已在运行,PIDs: $PIDS"
exit 1
fi
echo "开始执行..."
sleep 30
Python 脚本
import psutil
import os
import sys
script_name = os.path.basename(__file__)
for proc in psutil.process_iter(['pid', 'cmdline']):
if proc.info['pid'] != os.getpid():
cmdline = ' '.join(proc.info['cmdline'] or [])
if script_name in cmdline:
print(f"脚本已在运行,PID: {proc.info['pid']}")
sys.exit(1)
print("开始执行...")
# 你的代码
跨语言通用方案(推荐)
创建一个简单的锁工具脚本 lock.sh:
#!/bin/bash
# 用法: ./lock.sh <锁名称> <要执行的命令>
LOCK_DIR="/tmp/locks"
mkdir -p "$LOCK_DIR"
LOCK_NAME="$1"
shift
CMD="$@"
LOCK_PATH="$LOCK_DIR/$LOCK_NAME.lock"
if ! mkdir "$LOCK_PATH" 2>/dev/null; then
echo "锁 '$LOCK_NAME' 已被占用,退出。"
exit 1
fi
trap 'rm -rf "$LOCK_PATH"' EXIT
# 执行实际命令
exec $CMD
使用:
# 确保只有1个备份脚本运行 ./lock.sh backup ./backup_script.sh # 在crontab中使用 */5 * * * * /path/to/lock.sh myscript /path/to/myscript.sh > /dev/null 2>&1
建议
- 优先使用文件锁:跨平台、易实现、可靠性高
- 注意异常退出处理:使用
trap或finally确保锁被清理 - crontab 环境:使用
flock命令更简单:*/5 * * * * flock -n /tmp/mylock.lock /path/to/script.sh
- 分布式环境:使用 Redis、ZooKeeper 等分布式锁
选择哪种方法取决于你的具体需求:单机环境推荐文件锁,需要跨主机控制推荐分布式锁。