Skip to content

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-release

curl 调试模板

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'
最近更新