在多个服务器上批量执行命令的shell脚本

发布于 2025年07月14日
#!/bin/bash
# 批量远程命令执行脚本(简化输出版)- 使用expect处理用户切换
# 功能:在预定义的服务器上以root用户执行指定命令,所有输出集中在一个文件
# 使用方法: ./batch_execute.sh "<命令>"
# 示例: ./batch_execute.sh "df -h"
# ====================== 配置区域 ======================
# 硬编码的用户名和密码
USERNAME="lmode"
PASSWORD="FfcsVrt123$%^"  # 普通用户密码(用于SSH登录)
ROOT_PASSWORD="Ffcs@2025"    # root用户密码(用于su切换)
SSH_PORT=22                  # 固定SSH端口
# 硬编码的服务器列表
SERVERS=(
    "10.188.21.1"
    "10.188.21.2"
)
# ====================== 配置结束 ======================

# 检查参数
if [ $# -lt 1 ]; then
  echo "使用方法: $0 \"<命令>\""
  echo "示例1: $0 \"uname -a\""
  echo "示例2: $0 \"yum update -y\""
  exit 1
fi
COMMAND="$1"

# 检查依赖
check_dependency() {
  if ! command -v "$1" &> /dev/null; then
    echo "错误: 需要安装 $1" >&2
    return 1
  fi
  return 0
}

# 检查必需工具
if ! check_dependency ssh || ! check_dependency sshpass || ! check_dependency expect; then
  echo "请安装缺失的工具后重试:"
  echo "  Ubuntu/Debian: sudo apt install ssh sshpass expect"
  echo "  CentOS/RHEL: sudo yum install ssh sshpass expect"
  exit 1
fi

# 创建输出文件
OUTPUT_FILE="batch_output_$(date +%Y%m%d_%H%M%S).log"
echo "" > "$OUTPUT_FILE"  # 创建空文件

# 输出文件头
{
  echo "=================================================="
  echo "批量执行报告 - $(date)"
  echo "命令: $COMMAND"
  echo "登录用户: $USERNAME"
  echo "执行用户: root"
  echo "SSH端口: $SSH_PORT"
  echo "服务器列表:"
  for server in "${SERVERS[@]}"; do
    echo "  - $server"
  done
  echo "=================================================="
  echo ""
} | tee -a "$OUTPUT_FILE"  # 同时显示在终端并写入文件

# 设置连接超时时间(秒)
SSH_TIMEOUT=15

# 计数器
success_count=0
fail_count=0
total_servers=${#SERVERS[@]}
current_server=0

echo "开始处理 $total_servers 台服务器..."
echo "所有输出将保存到: $OUTPUT_FILE"
echo "--------------------------------------------------" | tee -a "$OUTPUT_FILE"

# 创建临时expect脚本
EXPECT_SCRIPT=$(mktemp)
cat > "$EXPECT_SCRIPT" << EOF
#!/usr/bin/expect -f
# 设置超时时间
set timeout $SSH_TIMEOUT

# 获取参数
set username [lindex \$argv 0]
set password [lindex \$argv 1]
set root_password [lindex \$argv 2]
set ip [lindex \$argv 3]
set command [lindex \$argv 4]

# 不显示spawn命令
log_user 0

# 启动SSH连接
spawn ssh -p $SSH_PORT -o StrictHostKeyChecking=no \$username@\$ip

# 处理连接过程
expect {
  timeout {
    send_user "连接超时\n"
    exit 1
  }
  "Permission denied" {
    send_user "认证失败\n"
    exit 1
  }
  "assword:" {
    send "\$password\r"
  }
}

# 等待命令提示符
expect {
  timeout {
    send_user "等待提示符超时\n"
    exit 1
  }
  "*$ " {
    # 切换到root
    send "su - root\r"
    expect {
      timeout {
        send_user "等待root密码提示超时\n"
        exit 1
      }
      "assword:" {
        send "\$root_password\r"
      }
    }
    
    # 检查是否成功切换到root
    expect {
      timeout {
        send_user "等待root提示符超时\n"
        exit 1
      }
      "*# " {
        # 执行命令
        send "\$command\r"
        
        # 等待命令完成
        expect {
          timeout {
            send_user "命令执行超时\n"
            exit 1
          }
          -re {^(.*)\r\n.*# $} {
            # 捕获命令输出
            set command_output \$expect_out(1,string)
            send_user "\$command_output\n"
            
            # 退出root会话
            send "exit\r"
            expect "*$ " {
              # 退出SSH会话
              send "exit\r"
              expect eof
              exit 0
            }
          }
        }
      }
      "Authentication failure" {
        send_user "root密码错误\n"
        exit 1
      }
    }
  }
}
EOF

chmod +x "$EXPECT_SCRIPT"

# 遍历所有服务器
for ip in "${SERVERS[@]}"; do
  ((current_server++))
  
  # 输出服务器头信息
  {
    echo ""
    echo "=================================================="
    echo "服务器 [$current_server/$total_servers]: $ip"
    echo "开始时间: $(date)"
    echo "=================================================="
  } | tee -a "$OUTPUT_FILE"
  
  # 执行远程命令
  echo "执行命令: $COMMAND (作为root用户)" | tee -a "$OUTPUT_FILE"
  echo "--------------------------------------------------" | tee -a "$OUTPUT_FILE"
  
  # 使用expect脚本执行命令并捕获输出
  OUTPUT=$("$EXPECT_SCRIPT" "$USERNAME" "$PASSWORD" "$ROOT_PASSWORD" "$ip" "$COMMAND" 2>&1)
  STATUS=$?
  
  # 显示命令输出
  echo "$OUTPUT" | tee -a "$OUTPUT_FILE"
  
  # 检查执行状态
  if [ $STATUS -eq 0 ]; then
    result="SUCCESS"
    ((success_count++))
  else
    result="FAILED (退出码: $STATUS)"
    ((fail_count++))
  fi
  
  # 记录结束状态
  {
    echo "--------------------------------------------------"
    echo "结束时间: $(date)"
    echo "执行状态: $result"
    echo "=================================================="
  } | tee -a "$OUTPUT_FILE"
done

# 清理临时文件
rm -f "$EXPECT_SCRIPT"

# 最终摘要
{
  echo ""
  echo "=================================================="
  echo "执行摘要:"
  echo "总服务器数: $total_servers"
  echo "成功: $success_count"
  echo "失败: $fail_count"
  echo "开始时间: $(date -d @$(stat -c %Y "$OUTPUT_FILE"))"
  echo "结束时间: $(date)"
  echo "输出文件: $OUTPUT_FILE"
  echo "=================================================="
} | tee -a "$OUTPUT_FILE"

echo "操作完成! 输出文件: $OUTPUT_FILE"