Shell脚本入门:从Hello World到变量的灵活运用
Shell脚本入门从Hello World到变量的灵活运用最近团队里有个需求每天凌晨3点自动备份数据库备份完成后发一封邮件通知。听起来不复杂对吧但如果纯手工操作每天凌晨爬起来敲命令谁受得了这时候Shell脚本就派上用场了。今天我们从零开始聊聊Shell脚本的基础知识。一、Shell是什么简单来说Shell就是操作系统和用户之间的翻译官。你在终端敲的每一条命令都是Shell帮你解释给操作系统听的。Linux系统默认的Shell是bashBourne Again Shell你可以通过以下命令确认echo$SHELL# 输出/bin/bash# 查看系统支持哪些Shellcat/etc/shells常见的Shell类型有sh、bash、zsh等其中bash是最通用的也是我们编写脚本时最常用的。二、第一个Shell脚本2.1 脚本的基本结构创建一个文件hello.sh#!/bin/bash# 功能第一个Shell脚本# 作者hengjun# 日期2024-01-01echoHello, Shell!echo当前时间是:$(date)echo当前用户是:$(whoami)几个要点第一行#!/bin/bash叫做Shebang它告诉系统用哪个解释器来执行这个脚本#开头的行是注释除了第一行的Shebang脚本文件名建议以.sh结尾方便识别2.2 执行脚本的几种方式# 方式1用bash命令执行推荐不需要执行权限/bin/bash hello.sh# 方式2用source执行在当前Shell环境中执行会影响当前环境变量sourcehello.sh# 简写形式.hello.sh# 方式3给执行权限后直接运行chmodx hello.sh ./hello.sh注意方式1和方式2的区别很重要。方式1会在一个子Shell中执行脚本中的变量不会影响当前Shell方式2会在当前Shell中执行脚本中定义的变量会保留。在加载环境配置文件时我们通常用source。2.3 脚本调试脚本写错了怎么办bash提供了调试选项# 检查语法错误不执行bash-nhello.sh# 显示执行过程每条命令执行前打印出来bash-xhello.shbash -x在排查问题时特别有用它会把每一行命令展开后显示出来方便你看到变量的实际值。三、Shell变量脚本的灵魂3.1 变量的定义和使用Shell是弱类型语言变量不需要声明类型直接赋值即可# 定义变量等号两边不能有空格这是最常见的错误namezhangsanage25# 使用变量echo姓名:$name, 年龄:$age# 或者用花括号明确边界echo姓名:${name}, 年龄:${age}变量命名规范只能使用字母、数字和下划线不能以数字开头不能使用Shell关键字建议使用有意义的名称如user_name而不是un3.2 三种引号的区别这是初学者最容易混淆的地方也是面试常考的点namezhangsan# 单引号原样输出不做任何变量替换echoHello, $name# 输出Hello, $name# 双引号会解析变量echoHello,$name# 输出Hello, zhangsan# 反引号或$()执行命令并获取输出echo当前目录:$(pwd)# 输出当前目录: /root经验法则数字不加引号字符串默认加双引号需要原样输出时用单引号。3.3 命令变量除了直接赋值我们还经常需要把命令的执行结果赋给变量# 方式1$()推荐可嵌套current_date$(date%F)file_count$(ls-la/tmp|wc-l)ip_addr$(ifconfigeth0|grep-winet|awk{print $2})# 方式2反引号功能相同但不支持嵌套current_datedate%Fecho日期:$current_dateecho文件数:$file_countechoIP地址:$ip_addr3.4 全局变量与局部变量# 局部变量只在当前Shell有效local_varI am local# 全局变量所有Shell都能访问exportGLOBAL_VARI am global# 查看所有全局变量env|grepGLOBAL_VAR# 删除变量unsetlocal_var环境变量文件体系/etc/profile # 系统级所有用户登录时加载 /etc/profile.d/*.sh # 被/etc/profile调用 ~/.bash_profile # 用户级用户登录时加载 ~/.bashrc # 用户级每次打开新终端加载修改这些文件后用source命令使其生效# 比如配置Java环境变量echoexport JAVA_HOME/opt/java/etc/profile.d/java.shechoexport PATH$JAVA_HOME/bin:$PATH/etc/profile.d/java.shsource/etc/profile.d/java.sh四、Shell内置变量Shell提供了一些特殊的内置变量在编写脚本时非常有用#!/bin/bash# 演示常用内置变量echo脚本名称:$0echo第一个参数:$1echo第二个参数:$2echo参数总数:$#echo所有参数:$echo所有参数(单字符串):$*echo当前进程PID:$$echo上一条命令的返回值:$?执行效果/bin/bash demo.sh arg1 arg2 arg3# 脚本名称: demo.sh# 第一个参数: arg1# 第二个参数: arg2# 参数总数: 3# 所有参数: arg1 arg2 arg3# 当前进程PID: 12345# 上一条命令的返回值: 0$?特别重要它表示上一条命令的执行状态。0表示成功非0表示失败。在脚本中做条件判断时经常用到。ls/tmpecho$?# 0成功ls/not_exist_direcho$?# 2失败五、数据格式化输出5.1 echo命令echo是最基本的输出命令但配合一些选项可以实现格式化# 不换行输出echo-nHelloechoWorld# 输出HelloWorld# 解析转义字符echo-e第一行\n第二行# 输出# 第一行# 第二行# 制表符echo-e姓名\t年龄\t城市echo-e张三\t25\t北京# 带颜色输出运维脚本中常用echo-e\033[31m红色文字\033[0mecho-e\033[32m绿色文字\033[0mecho-e\033[33m黄色文字\033[0m颜色代码速查30黑、31红、32绿、33黄、34蓝、35紫、36青、37白。5.2 printf命令printf比echo更强大支持C语言风格的格式化输出# 基本格式化printf姓名: %s, 年龄: %d\n张三25# 数字补零printf编号: %05d\n42# 输出编号: 00042# 浮点数精度控制printf价格: %.2f元\n99.5# 输出价格: 99.50元# 左对齐负号表示左对齐printf%-10s %-5d %-10s\n张三25北京printf%-10s %-5d %-10s\n李四30上海实战格式化输出系统信息#!/bin/bash# 系统信息采集脚本hostname$(hostname)ip_addr$(hostname-I|awk{print $1})os_info$(cat/etc/redhat-release2/dev/null||cat/etc/os-release|grepPRETTY_NAME|cut-d-f2)mem_total$(free-m|awk/Mem/{print $2})mem_used$(free-m|awk/Mem/{print $3})echo-e\033[32m 系统信息 \033[0mprintf主机名: %-20s\n$hostnameprintfIP地址: %-20s\n$ip_addrprintf操作系统: %-20s\n$os_infoprintf内存总量: %-20s M\n$mem_totalprintf内存使用: %-20s M\n$mem_usedecho-e\033[32m\033[0m六、数据格式化输入6.1 重定向重定向是Shell中非常重要的概念它控制着命令的输入输出走向# 标准输出重定向覆盖echohellofile.txt# 标准输出重定向追加echoworldfile.txt# 标准错误重定向ls/not_exist2error.log# 标准输出和错误都重定向commandall.log21# 等价于commandall.log# 输入重定向wc-l/etc/passwd6.2 管道符管道符|把前一个命令的输出作为后一个命令的输入这是Linux命令组合的核心# 统计当前登录用户数who|wc-l# 查看占用内存最多的5个进程psaux|sort-k4-rn|head-5# 查看日志中最近的ERRORtail-100app.log|grepERROR6.3 EOF多行文本输入在编写脚本时经常需要生成配置文件。EOF可以方便地实现多行文本的输入# 生成Nginx配置文件cat/etc/nginx/conf.d/app.confEOF server { listen 80; server_name www.example.com; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host \$host; } } EOF注意如果配置内容中包含$等特殊字符需要在EOF加引号或者用转义符# 加引号防止变量替换catconfig.txtEOF HOME$HOME USER$USER EOF七、子Shell与环境隔离理解子Shell的概念对编写复杂脚本很重要# () 在子Shell中执行不影响当前环境(cd /tmp;pwd)# 切换到/tmp并打印pwd# 仍在原目录# {} 在当前Shell中执行会影响当前环境{cd/tmp;pwd;}# 切换到/tmp并打印pwd# 现在在/tmp了实际应用场景在脚本中临时切换目录做操作但不想影响后续代码#!/bin/bash# 备份脚本# 用()隔离备份完成后自动回到原目录(cd/opt/apptar-czf/backup/app-$(date%F).tar.gz.)# 这里仍在脚本原来的目录echo备份完成