18个脚本,从打印Hello World到自动化部署,覆盖提纲全部知识点
#!/bin/bash # 第一个Shell脚本:变量和输入输出 # 1. 定义变量(等号两边绝对不能有空格!) name="张三" age=20 # 2. 输出变量(用$引用) echo "姓名: $name" echo "年龄: $age" # 3. 读取用户输入 read -p "请输入你的城市: " city echo "你在 $city" # 4. 特殊变量 echo "脚本名: $0" echo "第一个参数: $1" echo "参数个数: $#" # 运行方式: # chmod +x hello.sh # ./hello.sh 参数1
修改脚本:让用户输入姓名和年龄,然后打印 "你好,XXX,你今年XX岁"
#!/bin/bash # if 条件判断练习 # 1. 判断文件是否存在 file="/etc/passwd" if [ -f "$file" ]; then echo "$file 存在" else echo "$file 不存在" fi # 2. 数字比较 score=75 if [ $score -ge 90 ]; then echo "优秀" elif [ $score -ge 60 ]; then echo "及格" else echo "不及格" fi # 3. 判断目录是否存在,不存在就创建 dir="/tmp/mytest" if [ ! -d "$dir" ]; then mkdir -p "$dir" echo "目录 $dir 创建成功" fi # 4. 检查上一条命令是否成功 ls /tmp > /dev/null 2&1 if [ $? -eq 0 ]; then echo "命令执行成功" fi
让用户输入一个数字,判断是正数、负数还是零,并输出结果
#!/bin/bash # 重定向和管道练习 logfile="/tmp/mylog.txt" # 1. > 覆盖写入 echo "=== 系统信息 ===" > $logfile # 2. >> 追加写入 date >> $logfile hostname >> $logfile echo "写入完成,查看文件:" cat $logfile # 3. 管道:统计/etc/passwd有多少用户 user_count=$(cat /etc/passwd | wc -l) echo "系统用户数: $user_count" # 4. 管道:找出包含root的行 cat /etc/passwd | grep "root" # 5. 2>&1 把错误也写入日志 ls /不存在的目录 >> $logfile 2&1 echo "(错误信息也被写入了日志)" # 6. 命令替换 $() 把命令输出变成变量 today=$(date +%Y-%m-%d) echo "今天是: $today"
写一个脚本,把当前目录的文件列表保存到 filelist.txt,并统计有多少个文件打印出来
#!/bin/bash # 算术运算和字符串操作 # 1. 算术运算(必须用$(()),不能直接写3+5) a=10 b=3 echo "加法: $(($a + $b))" echo "减法: $(($a - $b))" echo "乘法: $(($a * $b))" echo "除法: $(($a / $b))" # 整数除法 echo "取余: $(($a % $b))" # 2. 字符串长度 str="Hello World" echo "字符串: $str" echo "长度: ${#str}" # 3. 字符串截取 ${str:起始位置:长度} echo "截取前5个: ${str:0:5}" # Hello echo "从第6位截: ${str:6}" # World # 4. 字符串替换 echo "替换一个: ${str/l/L}" # 替换第一个l echo "替换全部: ${str//l/L}" # 替换所有l # 5. tr命令:大小写转换 echo "$str" | tr a-z A-Z # 转大写 echo "$str" | tr A-Z a-z # 转小写 # 6. 默认值:变量为空时用默认值 echo "${未定义变量:-这是默认值}"
让用户输入一个句子,输出:字符总数、全大写版本、把所有空格替换成下划线
#!/bin/bash # for循环各种用法 # 1. 遍历固定列表 echo "=== 水果列表 ===" for fruit in apple banana orange; do echo " 水果: $fruit" done # 2. 数字范围(seq生成序列) echo "=== 1到5 ===" for i in $(seq 1 5); do echo " 数字: $i" done # 3. 批量创建用户(模拟) echo "=== 批量操作 ===" for user in user1 user2 user3; do echo " 创建用户: $user" # useradd $user # 真实环境取消注释 done # 4. break:跳出循环 echo "=== break示例 ===" for i in $(seq 1 10); do if [ $i -eq 4 ]; then echo " 到4了,跳出!" break fi echo " $i" done # 5. continue:跳过本次,继续下次 echo "=== 跳过偶数 ===" for i in $(seq 1 6); do if [ $(( i % 2 )) -eq 0 ]; then continue # 偶数跳过 fi echo " 奇数: $i" done
用for循环计算 1+2+3+...+100 的结果(答案是5050),打印出来
#!/bin/bash # while循环实战 # 先创建一个测试文件 cat > /tmp/servers.txt << EOF 192.168.1.10 webserver 192.168.1.20 database 192.168.1.30 cache EOF # 1. while read 逐行读取文件 echo "=== 读取服务器列表 ===" while read line; do echo " 服务器: $line" done < /tmp/servers.txt # 2. 读取多列(按空格分隔) echo "=== 分别读取IP和名称 ===" while read ip name; do echo " IP: $ip 角色: $name" done < /tmp/servers.txt # 3. while 数字倒计时 echo "=== 倒计时 ===" count=3 while [ $count -gt 0 ]; do echo " $count..." count=$(( count - 1 )) done echo " 开始!"
创建一个包含5个同学姓名的文件,用while read逐行读取,给每个人打印 "同学XXX,加油!"
#!/bin/bash # 函数定义和使用 # 1. 基本函数(注意:定义要在调用之前) say_hello() { echo "你好,世界!" } say_hello # 调用函数,直接写函数名 # 2. 带参数的函数(用$1 $2接收参数) greet() { local name="$1" # local声明局部变量 local age="$2" echo "你好,$name,你今年$age岁" } greet "李四" 22 # 3. 有返回值的函数(用echo输出,$()捕获) add() { local result=$(( $1 + $2 )) echo $result # 用echo"返回"值 } sum=$(add 15 27) # 用$()捕获函数输出 echo "15 + 27 = $sum" # 4. 实用:带颜色的日志函数 log_info() { echo "[INFO] $1"; } log_ok() { echo "[ OK] $1"; } log_error() { echo "[FAIL] $1" >&2; } log_info "开始部署..." log_ok "nginx 安装成功" log_error "数据库连接失败"
写一个函数 is_root(),检查当前用户是否为root($UID==0),是则打印"有权限",否则打印"权限不足"
#!/bin/bash # case语句和trap # 1. trap:Ctrl+C时打印提示再退出 trap 'echo ""; echo "用户中断,退出!"; exit 0' SIGINT # 2. case 做菜单 echo "=== 服务管理 ===" echo "1) 启动服务" echo "2) 停止服务" echo "3) 查看状态" echo "q) 退出" read -p "请选择: " choice case $choice in 1) echo "正在启动服务..." # systemctl start nginx ;; 2) echo "正在停止服务..." # systemctl stop nginx ;; 3) echo "查看服务状态..." # systemctl status nginx ;; q|Q) # | 表示"或",q和Q都能匹配 echo "退出" exit 0 ;; *) # * 是默认情况,匹配所有其他输入 echo "无效输入!" ;; esac
把菜单放进while循环,让用户可以反复操作,直到选q才退出
#!/bin/bash # grep 和 sed 实战 # 准备测试文件 cat > /tmp/app.conf << EOF # 应用配置文件 server_port=8080 server_host=127.0.0.1 db_host=192.168.1.100 db_port=3306 debug=true log_level=INFO EOF echo "=== grep 搜索 ===" # 搜索包含"port"的行 grep "port" /tmp/app.conf echo "=== 反向过滤(去掉注释行) ===" # -v 反向:不包含#的行 grep -v "^#" /tmp/app.conf echo "=== 显示行号 ===" grep -n "db" /tmp/app.conf echo "=== sed 替换(不修改文件) ===" # 把8080替换成9090 sed 's/8080/9090/' /tmp/app.conf echo "=== sed 删除注释行 ===" # 删除以#开头的行 sed '/^#/d' /tmp/app.conf echo "=== sed -i 直接修改文件 ===" # 把debug=true改为debug=false(真实修改文件) sed -i 's/debug=true/debug=false/' /tmp/app.conf grep "debug" /tmp/app.conf
写脚本读取/etc/passwd,用grep找出UID为0的行,用sed把输出中的":"替换成" | " 再显示
#!/bin/bash # awk 实战:处理结构化数据 # 准备数据 cat > /tmp/scores.txt << EOF 张三 语文 88 李四 数学 95 王五 英语 72 张三 数学 91 李四 语文 83 EOF echo "=== 打印第1列(姓名) ===" awk '{print $1}' /tmp/scores.txt echo "=== 打印行号和整行 ===" awk '{print NR, $0}' /tmp/scores.txt echo "=== 过滤:只显示张三的成绩 ===" awk '$1=="张三" {print $0}' /tmp/scores.txt echo "=== 过滤:成绩>85的 ===" awk '$3 > 85 {print $1, "的", $2, "成绩:", $3}' /tmp/scores.txt echo "=== 统计:计算总分 ===" awk 'BEGIN{sum=0} {sum+=$3} END{print "总分:", sum}' /tmp/scores.txt echo "=== 处理/etc/passwd(冒号分隔) ===" # -F: 指定分隔符为冒号 awk -F: '{print "用户:", $1, "家目录:", $6}' /etc/passwd | head -5
用awk统计scores.txt中每位同学的平均分(需要用数组按姓名累加)
#!/bin/bash # 系统巡检脚本(综合练习) REPORT="/tmp/check_$(date +%Y%m%d).log" # 日志函数 log() { echo "$1" | tee -a $REPORT; } log "=============================" log "系统巡检报告: $(date)" log "主机名: $(hostname)" log "=============================" # 检查磁盘使用率 check_disk() { log "--- 磁盘使用率 ---" df -h | awk 'NR>1 { used=$5 gsub(/%/,"",used) if(used+0 > 80) print " ⚠ 警告: " $6 " 使用率 " $5 else print " ✓ 正常: " $6 " 使用率 " $5 }' | tee -a $REPORT } # 检查内存 check_mem() { log "--- 内存使用 ---" free -h | awk 'NR==2 {print " 总内存:", $2, " 已用:", $3, " 空闲:", $4}' \ | tee -a $REPORT } # 检查指定进程是否运行 check_process() { local proc="$1" if pgrep -x "$proc" > /dev/null 2&1; then log " ✓ $proc 运行中" else log " ✗ $proc 未运行" fi } # 执行检查 check_disk check_mem log "--- 关键进程 ---" for proc in sshd cron bash; do check_process "$proc" done log "=============================" echo "报告已保存到: $REPORT"
在脚本中增加一个 check_load() 函数,读取系统负载(uptime命令),如果超过CPU核心数就报警
# /etc/ansible/hosts 或自定义 inventory 文件 # 直接写IP(属于默认组ungrouped) 192.168.1.5 # 定义组 [webservers] 192.168.1.10 192.168.1.11 [databases] 192.168.1.20 ansible_port=2222 # 主机变量 # 组变量(对整组生效) [webservers:vars] ansible_user=root ansible_ssh_private_key_file=~/.ssh/id_rsa # 组的组 [all_servers:children] webservers databases
# 格式:ansible 主机/组 -m 模块名 -a "参数" # 测试连通性 ansible all -m ping # 执行shell命令(支持管道) ansible webservers -m shell -a "df -h | grep '/$'" # 复制文件到远程 ansible all -m copy -a "src=/tmp/test.txt dest=/tmp/test.txt mode=644" # 安装软件包 ansible webservers -m yum -a "name=nginx state=present" -b # 启动服务 ansible webservers -m service -a "name=nginx state=started enabled=yes" -b # -b 表示 become(sudo提权) # -i 指定inventory文件 # -v/-vv/-vvv 增加输出详细度
--- # Playbook:在webservers组部署nginx - name: 部署 Nginx Web服务器 hosts: webservers # 对应inventory里的组名 become: true # 用sudo执行 gather_facts: true # 收集主机信息(facts) vars: # 定义变量 http_port: 80 nginx_pkg: nginx tasks: # 任务1:只在RedHat系列执行 - name: 安装 nginx(RedHat) yum: name: "{{ nginx_pkg }}" # 引用变量 state: present when: ansible_os_family == "RedHat" # 任务2:只在Debian系列执行 - name: 安装 nginx(Debian) apt: name: "{{ nginx_pkg }}" state: present when: ansible_os_family == "Debian" # 任务3:复制配置文件,有变化就通知handler - name: 复制 nginx 配置 copy: src: files/nginx.conf dest: /etc/nginx/nginx.conf notify: 重启 nginx # 文件有变化才触发 # 任务4:启动并开机自启 - name: 启动 nginx service: name: nginx state: started enabled: yes handlers: # 被notify触发,所有task完成后执行 - name: 重启 nginx service: name: nginx state: restarted
在Playbook中增加一个任务:用shell模块获取nginx版本号,用register保存结果,再用debug模块打印出来
--- - name: 变量和循环演示 hosts: all become: true vars: packages: # 变量可以是列表 - vim - wget - curl web_user: webadmin tasks: # loop循环:批量安装软件包 - name: 批量安装软件包 yum: name: "{{ item }}" # item = 当前循环元素 state: present loop: "{{ packages }}" # 遍历packages列表 # register:捕获命令结果 - name: 获取系统时间 shell: date +%Y-%m-%d register: current_date # 结果存入变量 # debug:打印变量内容 - name: 打印时间 debug: msg: "当前日期: {{ current_date.stdout }}" # set_fact:运行时创建新变量 - name: 设置备份目录变量 set_fact: backup_dir: "/backup/{{ current_date.stdout }}" - name: 创建备份目录 file: path: "{{ backup_dir }}" state: directory mode: '0755'
# 1. 创建Role目录结构 ansible-galaxy init nginx_role # 生成的目录结构: nginx_role/ ├── tasks/main.yml # 主任务(必须有) ├── handlers/main.yml # 处理器 ├── vars/main.yml # 变量(高优先级) ├── defaults/main.yml # 默认变量(低优先级,可被覆盖) ├── files/ # 静态文件(copy模块用) ├── templates/ # Jinja2模板(template模块用) └── meta/main.yml # 元数据(依赖其他role)
--- # roles/nginx_role/tasks/main.yml - name: 安装 nginx yum: name: nginx state: present - name: 部署配置文件 template: # 用template渲染变量 src: nginx.conf.j2 # 在templates/目录下 dest: /etc/nginx/nginx.conf notify: restart nginx - name: 启动 nginx service: name: nginx state: started enabled: yes
--- - name: 部署Web服务器 hosts: webservers become: true roles: - nginx_role # 直接写role名称 - role: nginx_role # 或这种写法,可以传变量 vars: http_port: 8080
--- - name: 错误处理演示 hosts: all become: true tasks: # block/rescue/always = try/except/finally - block: # try: 正常任务 - name: 备份配置文件 copy: src: /etc/nginx/nginx.conf dest: /tmp/nginx.conf.bak remote_src: yes - name: 更新配置(可能失败) template: src: nginx.conf.j2 dest: /etc/nginx/nginx.conf notify: restart nginx rescue: # except: block失败时执行 - name: 恢复备份 copy: src: /tmp/nginx.conf.bak dest: /etc/nginx/nginx.conf remote_src: yes - name: 发送告警 debug: msg: "⚠ 部署失败,已回滚配置!" always: # finally: 无论成败都执行 - name: 清理临时文件 file: path: /tmp/nginx.conf.bak state: absent # ignore_errors: 失败也继续 - name: 检查可选服务 shell: systemctl status optional-service ignore_errors: true # 找不到也没关系
#!/bin/bash # 一键部署脚本:Shell做检查,Ansible做部署 set -e # 任何命令失败立即退出 trap 'log_error "部署异常中断!"; exit 1' ERR PLAYBOOK="site.yml" INVENTORY="inventory/hosts" LOG="/var/log/deploy_$(date +%Y%m%d_%H%M%S).log" log_info() { echo "[INFO] $1" | tee -a $LOG; } log_ok() { echo "[ OK] $1" | tee -a $LOG; } log_error() { echo "[ERR] $1" | tee -a $LOG >&2; } # 步骤1:环境检查 log_info "检查 Ansible 是否安装..." if ! command -v ansible-playbook >/dev/null 2&1; then log_error "Ansible 未安装!" exit 1 fi log_ok "Ansible 已安装" # 步骤2:检查文件存在 for f in $PLAYBOOK $INVENTORY; do if [ ! -f "$f" ]; then log_error "文件不存在: $f" exit 1 fi done log_ok "文件检查通过" # 步骤3:语法检查 log_info "检查 Playbook 语法..." ansible-playbook --syntax-check -i $INVENTORY $PLAYBOOK log_ok "语法检查通过" # 步骤4:执行部署 log_info "开始部署..." ansible-playbook -i $INVENTORY $PLAYBOOK \ -e "deploy_time=$(date +%Y%m%d%H%M%S)" \ --diff 2>&1 | tee -a $LOG # 步骤5:验证结果 if [ $? -eq 0 ]; then log_ok "部署成功!日志: $LOG" else log_error "部署失败!查看日志: $LOG" exit 1 fi
✅ 完成全部18个脚本练习,提纲知识点基本掌握
建议在Linux虚拟机上实际运行每个脚本,遇到报错是最好的学习机会 💪