脚本基础
Shell 脚本是一个包含命令的文本文件,Shell 会按顺序执行这些命令。第一行应该是一个shebang,告诉系统使用哪个解释器。#!/bin/bash#!/usr/bin/env bash
chmod+x script.sh./script.sh
在脚本顶部使用set选项以尽早捕获错误:-e在第一个错误时退出,-u将未设置变量视为错误,-o pipefail使管道中任一命令失败时整个管道失败。变量
变量赋值时=两侧不能有空格。使用双引号包裹变量引用以防止单词分割。| 表达式 | 说明 |
|---|
VAR=value | 赋值 |
VAR=$(command) | 捕获命令输出(命令替换) |
$VAR | 读取值 |
${VAR} | 带明确边界的读取 |
"$VAR" | 安全引用读取 |
${VAR:-default} | 若 VAR 未设置或为空,使用默认值 |
${VAR:=default} | 若 VAR 未设置或为空,赋默认值 |
${VAR:+alternate} | 若 VAR 已设置且非空,使用替代值 |
${VAR:?error msg} | 若 VAR 未设置或为空,报错退出 |
${#VAR} | 值的长度 |
readonly VAR | 设为只读变量 |
export VAR | 使变量对子进程可用 |
引号
引号控制 Shell 的扩展行为。不确定时,使用双引号。| 表达式 | 说明 |
|---|
'text' | 单引号:所有内容原样保留 |
"text" | 双引号:$变量 和 $(命令) 会被扩展 |
\$HOME | 反斜杠转义单个字符 |
始终引用变量引用:"$VAR" 能处理值中的空格,未引用的 $VAR 会被拆分为多个单词。特殊变量
| 变量 | 说明 |
|---|
$0 | 脚本名称 |
$1..$9 | 位置参数(参数) |
${10} | 第 10 个及之后的位置参数 |
$# | 参数个数 |
$@ | 所有参数,每个作为独立单词 |
$* | 所有参数,作为一个字符串 |
$? | 上一条命令的退出状态 |
$ | 当前 Shell 的 PID |
$! | 最后一个后台命令的 PID |
$_ | 上一条命令的最后一个参数 |
将参数传递给另一个命令时优先使用 "$@":它保留每个参数作为独立单词。字符串操作
| 表达式 | 说明 |
|---|
${VAR#pattern} | 从开头删除最短匹配 |
${VAR##pattern} | 从开头删除最长匹配 |
${VAR%pattern} | 从末尾删除最短匹配 |
${VAR%%pattern} | 从末尾删除最长匹配 |
${VAR/old/new} | 替换第一次出现 |
${VAR//old/new} | 替换所有出现 |
${VAR:offset} | 从偏移量开始的子串 |
${VAR:offset:length} | 从偏移量开始、指定长度的子串 |
${VAR^} | 首字符大写 |
${VAR^^} | 全部大写 |
${VAR,} | 首字符小写 |
${VAR,,} | 全部小写 |
大小写转换(${VAR^^}、${VAR,,})需要 bash 4 或更高版本。条件判断
在 bash 脚本中使用 [[ ]] 进行条件判断。它支持模式匹配和正则匹配,比旧的 [ ] 形式更安全。括号内的空格是必需的。if [[ -f"file.txt" ]]; thenecho"exists"; fi
| 运算符 | 说明 |
|---|
-f file | 文件存在且为普通文件 |
-d file | 文件存在且为目录 |
-e file | 文件存在(任意类型) |
-r file | 文件可读 |
-w file | 文件可写 |
-x file | 文件可执行 |
-s file | 文件存在且非空 |
-z string | 字符串为空 |
-n string | 字符串非空 |
== | 字符串相等(右侧为 glob 模式) |
!= | 字符串不等 |
=~ | 正则匹配(在 [[ ]] 内) |
-eq | 数值相等 |
-ne | 数值不等 |
-lt | 数值小于 |
-le | 数值小于等于 |
-gt | 数值大于 |
-ge | 数值大于等于 |
if [[ "$count"-gt0 ]]; thenecho"positive"; elif [[ "$count"-eq0 ]]; thenecho"zero"; elseecho"negative"; fiif [[ "$input"=~ ^[0-9]+$ ]]; thenecho"is a number"; fi
case "$1"instart) echo"Starting";; stop) echo"Stopping";; *) echo"Usage: $0 {start|stop}";; esac
循环
for file in *.txt; doecho"$file"; donefor i in {1..10}; doecho"$i"; donefor ((i=0; i<10; i++)); doecho"$i"; done
while循环在条件为真时持续运行。这是逐行读取文件的安全方式。whileIFS= read -r line; doecho"$line"; done < file.txt
until [[ -f"ready.flag" ]]; dosleep1; done
使用break提前退出循环,continue跳过当前迭代进入下一次。函数
函数将可复用命令分组。函数体内通过$1、$2等访问参数。使用local将变量限定在函数作用域内。greet() { local name="$1"; echo"Hello, $name"; }greet "World"
函数返回其最后一条命令的退出状态,或使用return设置明确的退出码(0-255)。要返回字符串,使用命令替换。get_date() { date +%Y-%m-%d; }today=$(get_date)
数组
| 表达式 | 说明 |
|---|
arr=(a b c) | 声明数组 |
arr[0]=value | 按索引设置元素 |
${arr[0]} | 按索引访问元素 |
"${arr[@]}" | 所有元素,每个作为独立单词 |
${#arr[@]} | 元素数量 |
${arr[@]:1:2} | 切片:从索引 1 开始取 2 个元素 |
arr+=(d e) | 追加元素 |
${!arr[@]} | 所有索引 |
unset 'arr[1]' | 删除元素(索引保留间隙) |
for item in"${arr[@]}"; doecho"$item"; done
declare -A mapmap[key]="value"echo"${map[key]}"
算术运算
使用 $(( )) 进行算术表达式,使用 (( )) 进行算术语句。| 表达式 | 说明 |
|---|
$((a + b)) | 加法 |
$((a - b)) | 减法 |
$((a * b)) | 乘法 |
$((a / b)) | 整数除法 |
$((a % b)) | 取模 |
$((a ** b)) | 幂运算 |
$((a++)) | 后置自增 |
$((++a)) | 前置自增 |
((a += 5)) | 算术赋值 |
Shell 算术仅限整数。需要浮点数运算时通过管道传给bc。echo"scale=2; 10 / 3" | bc
输入与输出
read -p "Enter name: " nameread -s -p "Password: " passread -r -a items <<< "a b c"
使用printf进行格式化输出,它比echo更可移植、更可预测。printf "Name: %s, Age: %d\n""$name""$age"
here document将多行文本传递给命令的标准输入。$ cat <Hello, $USERYour home is $HOMEEOF
使用 <<'EOF'(带引号)阻止 here document 内部的变量扩展。退出码与陷阱
每个命令返回一个退出码:0表示成功,其他值表示失败。最后一条命令的退出码在$?中。使用exit设置脚本自身的退出码。command || exit1command && echo"succeeded"
使用trap在脚本退出或收到信号时运行清理代码。EXIT 陷阱在任何退出时运行,包括错误。tmpfile=$(mktemp)trap ' rm -f "$tmpfile"' EXITtrap ' echo "Interrupted"; exit 130' INT TERM
调试