《Bash 脚本技巧:循环、算术与参数处理》

《Bash 脚本技巧:循环、算术与参数处理》

写 Shell 脚本常需要循环、条件判断、算术运算、参数解析与模板化输出。本文汇总这些高频写法,并给出可直接套用的参考脚本。

1 循环

1.1 for

# 方式一:空格分隔列表 for i in 1 2 3 4 5; do echo "Welcome $i times"; done # 方式二:序列 {1..5} for i in {1..5}; do echo "Welcome $i times"; done # 方式三:seq 生成序列(起始 1,步长 2,到 20) for i in $(seq 1 2 20); do echo "Welcome $i times"; done # 方式四:C 风格 for (( c=1; c<=5; c++ )); do echo "Welcome $c times"; done

无限循环:

for (( ; ; )); do echo "press CTRL+C to stop"; sleep 1; done

1.2 while

x=1 while [ $x -le 5 ]; do echo "Welcome $x times" x=$((x+1)) if [ $x -eq 3 ]; then break; fi done

数值比较运算符:-eq / -ne / -lt / -le / -gt / -ge。字符串比较用 = 与 !=。

2 算术运算

Shell 本身不支持浮点数运算,需借助 bc/awk。整数运算有以下三种写法:

# 1) let let a=5+4; echo $a # 9 let "a = $1 + 30" # 支持引用命令行参数 # 2) expr(运算符两侧必须有空格,乘号需转义) expr 5 \* 4 # 20 a=$(expr 10 - 3) # 7 # 3) $((...)),推荐,最常用 a=$(( 4 + 5 )); echo $a # 9 b=$(( a + 3 )); echo $b # 11(引用变量可省略 $) (( b += 3 )); echo $b # 自增

浮点运算使用 bc:

n1=1; n2=3 # scale=3 表示保留 3 位小数 echo "scale=3; $n1 / $n2" | bc

字符串长度:

a='Hello World' echo ${#a} # 11

3 命令行参数处理

脚本中 $1、$2…$#(参数个数)、$0(脚本名)。判断参数个数:

if [ $# -ge 1 ]; then if [ "$1" = hello ]; then echo hello; fi fi if [ $# -ge 2 ]; then if [ "$2" = world ]; then echo world; fi fi

使用 getopts 风格的手动解析(支持长选项 -e/--extension):

POSITIONAL=() while [[ $# -gt 0 ]]; do case $key in -e|--extension) EXTENSION="$2"; shift; shift ;; -s|--searchpath) SEARCHPATH="$2"; shift; shift ;; -l|--lib) LIBPATH="$2"; shift; shift ;; --default) DEFAULT=YES; shift ;; *) POSITIONAL+=("$1") # 未知参数先存起来 shift ;; esac done set -- "${POSITIONAL[@]}" # 恢复位置参数 echo "EXTENSION=$EXTENSION SEARCHPATH=$SEARCHPATH"

4 生成随机字符串

使用 /dev/urandom 与 tr 组合生成指定长度、指定字符集的随机串(常用于生成密码、token、随机 ID):

# 输出末尾自动加换行 < /dev/urandom tr -dc a-z0-9- | head -c8; echo

参数说明:tr -dc a-z0-9- 表示只保留 a-z/数字/-(-d 删除 + -c 补集的意思,即删除字符集之外的所有字符);head -c 8 取前 8 个字节。

另一种组合生成密码:

date +%s | sha256sum | base64 | head -c 32 | tr A-Z a-z | head -c8; echo

配合关联数组生成指定 TLD 的随机域名:

declare -A tld tld=([0]=".cn" [1]=".org" [2]=".com") cnt=0 while [ $cnt -le 10 ]; do name=$(< /dev/urandom tr -dc a-z0-9 | head -c8; echo) zone=${name}${tld[$(( cnt % 3 ))]} echo "$zone" >> data.txt cnt=$((cnt+1)) done

提示:MacOS 的 bash 3.2 不支持关联数组,需升级到 bash 4 及以上(如 brew install bash)。

5 heredoc 多行 JSON 与变量替换

脚本中构造多行 JSON,同时支持变量替换,可用 here-doc:

zone=example.com zone_id=a115f71ab0398653f8f20001 data=$(cat << EOF { "zone": "${zone}", "enable": true, "zone_id": "${zone_id}" } EOF ) curl -X POST -d "$data" -H"Content-type: application/json" http://your.server:8089/v1/api

解析返回 JSON(利用 python):

curl -s 'https://api.example.com/users/lambda' | python -c "import sys, json; print(json.load(sys.stdin)['name'])"

6 多行注释与模板

用 : '…' 包裹多行注释:

echo hello : ' This is a multiline comment in bash '

一个通用服务启停脚本(start/stop/restart/status):

#!/bin/bash set -e CWD="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" APP_NAME="myapp" PID_FILE="${CWD}/${APP_NAME}.pid" get_pid() { cat "$PID_FILE" 2>/dev/null || true; } case "$1" in start) if [ -n "$(get_pid)" ] && kill -0 "$(get_pid)" 2>/dev/null; then echo "${APP_NAME} is running"; exit 1 fi nohup ./"${APP_NAME}" > info.log 2> error.log & echo $! > "$PID_FILE" echo "$APP_NAME started" ;; stop) pid=$(get_pid) [ -n "$pid" ] && kill -9 "$pid" && rm -f "$PID_FILE" || echo "no $APP_NAME running" ;; restart) "$0" stop; sleep 1; "$0" start ;; status) pid=$(get_pid) [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null && echo "running (pid $pid)" || echo "not running" ;; *) echo "Usage: $0 {start|stop|status|restart}" >&2; exit 1 ;; esac

7 Shell 格式化工具 shfmt

使用 shfmt 统一 bash 脚本格式,避免手工对齐(Go 编写,go get mvdan.cc/sh/cmd/shfmt):

shfmt -w hello.sh # 原地格式化 shfmt -d hello.sh # 输出差异 shfmt -i 4 hello.sh # 4 空格缩进(默认 tab)

8 Alias 与简化登录

用 alias + 函数简化带参数的别名(例如带密钥登录远程主机):

# ~/.bashrc alias sm='function _sm(){ ssh -i ~/.ssh/id_rsa user@$1; }; _sm' source ~/.bashrc # 之后即可 sm <ip> 直接登录到指定机器

9 常用变量与字符串操作

  • 取长度:${#var}
  • 默认值:${var:-default}(未定义时取默认值)
  • 删除前缀:${var#prefix_}、删除后缀:${var%_suffix}
  • 将分隔符替换为空格再转数组:arr=(${str//,/ })
阅读 — · 全站 —
🎸 我的歌单 0 首