Shell
这一页以 Linux 发布、排障、同步 为主,目标是复制后改变量名就能执行。
脚本骨架
sh
#!/usr/bin/env bash
set -Eeuo pipefail
APP_NAME="demo"
BASE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LOG_DIR="${BASE_DIR}/logs"
mkdir -p "${LOG_DIR}"
log() {
printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"
}
die() {
log "ERROR: $*"
exit 1
}
trap 'die "line ${LINENO}: command failed"' ERR
main() {
log "${APP_NAME} start"
}
main "$@"参数解析模板
sh
#!/usr/bin/env bash
set -Eeuo pipefail
ENV="dev"
PORT="8080"
usage() {
cat <<'EOF'
Usage:
./deploy.sh -e prod -p 8080
Options:
-e environment: dev/test/prod
-p server port
-h show help
EOF
}
while getopts ":e:p:h" opt; do
case "${opt}" in
e) ENV="${OPTARG}" ;;
p) PORT="${OPTARG}" ;;
h) usage; exit 0 ;;
:) echo "option -${OPTARG} requires an argument"; exit 1 ;;
?) echo "unknown option -${OPTARG}"; usage; exit 1 ;;
esac
done发布 Jar 模板
sh
#!/usr/bin/env bash
set -Eeuo pipefail
APP_NAME="demo"
APP_JAR="/opt/app/demo.jar"
APP_PORT="8080"
LOG_FILE="/opt/app/logs/demo.log"
PID_FILE="/opt/app/demo.pid"
JAVA_OPTS="-Xms512m -Xmx512m -XX:+UseG1GC"
mkdir -p "$(dirname "${LOG_FILE}")"
is_running() {
[[ -f "${PID_FILE}" ]] && kill -0 "$(cat "${PID_FILE}")" >/dev/null 2>&1
}
start() {
if is_running; then
echo "${APP_NAME} is already running, pid=$(cat "${PID_FILE}")"
exit 0
fi
nohup java ${JAVA_OPTS} -jar "${APP_JAR}" --server.port="${APP_PORT}" >> "${LOG_FILE}" 2>&1 &
echo $! > "${PID_FILE}"
echo "${APP_NAME} started, pid=$(cat "${PID_FILE}")"
}
stop() {
if ! is_running; then
echo "${APP_NAME} is not running"
return
fi
pid="$(cat "${PID_FILE}")"
kill "${pid}"
for _ in $(seq 1 30); do
kill -0 "${pid}" >/dev/null 2>&1 || break
sleep 1
done
if kill -0 "${pid}" >/dev/null 2>&1; then
kill -9 "${pid}"
fi
rm -f "${PID_FILE}"
echo "${APP_NAME} stopped"
}
status() {
if is_running; then
echo "${APP_NAME} running, pid=$(cat "${PID_FILE}")"
else
echo "${APP_NAME} stopped"
fi
}
case "${1:-status}" in
start) start ;;
stop) stop ;;
restart) stop; start ;;
status) status ;;
*) echo "Usage: $0 {start|stop|restart|status}"; exit 1 ;;
esac健康检查和重试模板
sh
check_health() {
local url="$1"
local max_retry="${2:-30}"
for i in $(seq 1 "${max_retry}"); do
if curl -fsS --connect-timeout 2 --max-time 5 "${url}" >/dev/null; then
echo "health check passed"
return 0
fi
echo "waiting health check... ${i}/${max_retry}"
sleep 2
done
echo "health check failed: ${url}"
return 1
}
retry() {
local max_attempts="$1"
local delay="$2"
shift 2
local attempt=1
until "$@"; do
if [[ "${attempt}" -ge "${max_attempts}" ]]; then
echo "command failed after ${attempt} attempts: $*"
return 1
fi
echo "retry ${attempt}/${max_attempts}: $*"
attempt=$((attempt + 1))
sleep "${delay}"
done
}
check_health "http://127.0.0.1:8080/actuator/health" 30
retry 3 2 curl -fsS http://127.0.0.1:8080/actuator/health远程发布模板
sh
SERVER="user@192.168.1.10"
APP_DIR="/opt/app/demo"
# 上传
scp ./target/demo.jar "${SERVER}:${APP_DIR}/demo.jar"
# 执行重启
ssh "${SERVER}" "cd ${APP_DIR} && ./app.sh restart"
# 同步目录,删除远端多余文件
rsync -avz --delete ./dist/ "${SERVER}:${APP_DIR}/dist/"日志和文件模板
sh
# 查看最近 200 行
tail -n 200 /opt/app/logs/app.log
# 实时日志
tail -f /opt/app/logs/app.log
# 查关键字
grep -Rni --include='*.log' 'orderId=1001' /opt/app/logs
# 高亮 ERROR / WARN
tail -n 200 /opt/app/logs/app.log | grep --color=auto -E 'ERROR|WARN|Exception'
# 清理 30 天前日志
find /opt/app/logs -type f -name '*.log' -mtime +30 -delete进程和端口模板
sh
# 查 Java 进程
ps aux | grep '[j]ava'
pgrep -af 'demo.jar'
# 端口占用
lsof -i :8080
ss -lntp | grep ':8080'
# 资源占用
top -Hp "$(pgrep -f 'demo.jar' | head -n 1)"系统排障模板
sh
# 磁盘
df -h
du -sh ./*
du -h --max-depth=1 /opt | sort -hr
# 内存和负载
free -h
uptime
top
# 系统信息
uname -a
cat /etc/os-releasecurl 调试模板
sh
# GET
curl -fsS 'http://127.0.0.1:8080/api/users/1'
# POST JSON
curl -fsS -X POST 'http://127.0.0.1:8080/api/users' \
-H 'Content-Type: application/json' \
-d '{"name":"demo"}'
# 打印响应头
curl -i 'http://127.0.0.1:8080/actuator/health'
# 只看状态码
curl -o /dev/null -s -w '%{http_code}\n' 'http://127.0.0.1:8080/actuator/health'