命令行参数解析
命令行参数是脚本与用户交互的重要方式。本章介绍 Shell 脚本中解析命令行参数的各种方法,包括位置参数、getopts 内置命令以及手动解析技术。
位置参数基础
Shell 脚本通过位置参数接收命令行参数。
基本用法
#!/bin/bash
# 文件名:args.sh
echo "脚本名称: $0"
echo "第一个参数: $1"
echo "第二个参数: $2"
echo "参数个数: $#"
echo "所有参数: $@"
执行 ./args.sh hello world 123 输出:
脚本名称: ./args.sh
第一个参数: hello
第二个参数: world
参数个数: 3
所有参数: hello world 123
位置参数的特殊变量
| 变量 | 含义 |
|---|---|
$0 | 脚本名称或路径 |
$1-$9 | 第 1 到第 9 个参数 |
${10} | 第 10 个及之后的参数(需要花括号) |
$# | 参数个数 |
$@ | 所有参数(作为独立字符串) |
$* | 所有参数(作为单个字符串) |
$? | 上一个命令的退出状态 |
$$ | 当前进程 PID |
shift 命令
shift 命令将位置参数向左移动:
#!/bin/bash
echo "原始参数: $@"
echo "第一个参数: $1"
shift
echo "shift 后: $@"
echo "第一个参数: $1"
shift 2
echo "shift 2 后: $@"
执行 ./shift.sh a b c d e 输出:
原始参数: a b c d e
第一个参数: a
shift 后: b c d e
第一个参数: b
shift 2 后: d e
shift 常用于遍历所有参数:
#!/bin/bash
while [[ $# -gt 0 ]]; do
echo "处理参数: $1"
shift
done
参数默认值
#!/bin/bash
# 使用默认值
name="${1:-World}"
count="${2:-1}"
echo "Hello, $name!"
echo "Count: $count"
执行:
./default.sh # 输出:Hello, World! Count: 1
./default.sh Alice 5 # 输出:Hello, Alice! Count: 5
检查参数是否存在
#!/bin/bash
# 检查参数个数
if [[ $# -lt 2 ]]; then
echo "用法: $0 <name> <count>"
exit 1
fi
# 检查参数是否为空
if [[ -z "$1" ]]; then
echo "错误: name 不能为空"
exit 1
fi
getopts 内置命令
getopts 是 Shell 内置的命令行选项解析工具,用于解析短选项(如 -a、-b value)。
基本语法
getopts optstring name [arg...]
optstring:定义有效的选项字母,选项后跟:表示需要参数name:存储当前选项的变量名arg:可选,指定要解析的参数(默认解析位置参数)
简单示例
#!/bin/bash
while getopts "ab" opt; do
case $opt in
a) echo "选项 -a 被设置" ;;
b) echo "选项 -b 被设置" ;;
\?) echo "无效选项: -$OPTARG" >&2; exit 1 ;;
esac
done
执行:
./getopts1.sh -a # 输出:选项 -a 被设置
./getopts1.sh -ab # 输出:选项 -a 被设置 / 选项 -b 被设置
./getopts1.sh -c # 输出:无效选项: -c
带参数的选项
在选项字母后加 : 表示该选项需要参数:
#!/bin/bash
while getopts "f:o:" opt; do
case $opt in
f) file="$OPTARG"; echo "文件: $file" ;;
o) output="$OPTARG"; echo "输出: $output" ;;
\?) echo "无效选项: -$OPTARG" >&2; exit 1 ;;
:) echo "选项 -$OPTARG 需要参数" >&2; exit 1 ;;
esac
done
执行:
./getopts2.sh -f input.txt -o output.txt
# 输出:
# 文件: input.txt
# 输出: output.txt
./getopts2.sh -f
# 输出:选项 -f 需要参数
getopts 的特殊变量
| 变量 | 含义 |
|---|---|
OPTIND | 下一个要处理的参数索引(初始值为 1) |
OPTARG | 当前选项的参数值 |
OPTERR | 是否显示错误信息(设为 0 禁用) |
OPTIND 的作用
OPTIND 记录处理到哪个参数。处理完选项后,$OPTIND 指向第一个非选项参数:
#!/bin/bash
while getopts "a:b:c" opt; do
case $opt in
a) arg_a="$OPTARG" ;;
b) arg_b="$OPTARG" ;;
c) arg_c=1 ;;
esac
done
# 移动到非选项参数
shift $((OPTIND-1))
echo "选项 a: ${arg_a:-未设置}"
echo "选项 b: ${arg_b:-未设置}"
echo "选项 c: ${arg_c:-未设置}"
echo "剩余参数: $@"
执行:
./getopts3.sh -a hello -b world -c extra1 extra2
# 输出:
# 选项 a: hello
# 选项 b: world
# 选项 c: 1
# 剩余参数: extra1 extra2
静默模式
默认情况下,getopts 遇到无效选项会打印错误信息。在 optstring 开头加 : 可以进入静默模式:
#!/bin/bash
# 静默模式(开头加冒号)
while getopts ":a:b:" opt; do
case $opt in
a) echo "选项 a: $OPTARG" ;;
b) echo "选项 b: $OPTARG" ;;
\?)
echo "错误: 无效选项 -$OPTARG" >&2
exit 1
;;
:)
echo "错误: 选项 -$OPTARG 需要参数" >&2
exit 1
;;
esac
done
静默模式下:
- 无效选项:
$opt设为?,$OPTARG包含无效选项字符 - 缺少参数:
$opt设为:,$OPTARG包含选项字符
完整的选项解析示例
#!/bin/bash
# 默认值
verbose=0
output=""
input=""
# 使用帮助
usage() {
cat << EOF
用法: $0 [选项] 文件...
选项:
-i FILE 输入文件
-o FILE 输出文件
-v 详细模式
-h 显示帮助信息
示例:
$0 -i input.txt -o output.txt
$0 -v -o result.txt data.txt
EOF
exit 0
}
# 解析选项
while getopts ":i:o:vh" opt; do
case $opt in
i) input="$OPTARG" ;;
o) output="$OPTARG" ;;
v) verbose=1 ;;
h) usage ;;
\?)
echo "错误: 无效选项 -$OPTARG" >&2
usage
;;
:)
echo "错误: 选项 -$OPTARG 需要参数" >&2
exit 1
;;
esac
done
# 移动到非选项参数
shift $((OPTIND-1))
# 处理参数
if [[ -n "$input" ]]; then
[[ $verbose -eq 1 ]] && echo "输入文件: $input"
fi
if [[ -n "$output" ]]; then
[[ $verbose -eq 1 ]] && echo "输出文件: $output"
fi
if [[ $# -gt 0 ]]; then
[[ $verbose -eq 1 ]] && echo "其他文件: $@"
fi
[[ $verbose -eq 1 ]] && echo "处理完成"
多次调用 getopts
如果需要多次解析参数,必须重置 OPTIND:
#!/bin/bash
echo "第一次解析:"
while getopts "ab" opt; do
case $opt in
a) echo "选项 a" ;;
b) echo "选项 b" ;;
esac
done
# 重置 OPTIND
OPTIND=1
echo "第二次解析:"
while getopts "cd" opt; do
case $opt in
c) echo "选项 c" ;;
d) echo "选项 d" ;;
esac
done
长选项解析
getopts 只支持短选项(单字母)。如需解析长选项(如 --file),需要手动处理。
手动解析长选项
#!/bin/bash
verbose=0
output=""
input=""
while [[ $# -gt 0 ]]; do
case $1 in
-i|--input)
input="$2"
shift 2
;;
-o|--output)
output="$2"
shift 2
;;
-v|--verbose)
verbose=1
shift
;;
-h|--help)
echo "用法: $0 [-i|--input FILE] [-o|--output FILE] [-v|--verbose]"
exit 0
;;
-*)
echo "错误: 未知选项 $1" >&2
exit 1
;;
*)
# 非选项参数
break
;;
esac
done
echo "输入: $input"
echo "输出: $output"
echo "详细模式: $verbose"
echo "剩余参数: $@"
同时支持短选项和长选项
#!/bin/bash
usage() {
cat << EOF
用法: $0 [选项]
选项:
-f, --file FILE 指定文件
-o, --output FILE 指定输出文件
-v, --verbose 详细模式
-q, --quiet 静默模式
-h, --help 显示帮助
EOF
}
file=""
output=""
verbose=0
while [[ $# -gt 0 ]]; do
case $1 in
-f|--file)
file="$2"
shift 2
;;
-o|--output)
output="$2"
shift 2
;;
-v|--verbose)
verbose=1
shift
;;
-q|--quiet)
verbose=0
shift
;;
-h|--help)
usage
exit 0
;;
--)
shift
break
;;
-*)
echo "错误: 未知选项 $1" >&2
usage
exit 1
;;
*)
break
;;
esac
done
echo "文件: $file"
echo "输出: $output"
echo "详细: $verbose"
echo "其他: $@"
带 = 的长选项
#!/bin/bash
while [[ $# -gt 0 ]]; do
case $1 in
--file=*)
file="${1#*=}"
shift
;;
--output=*)
output="${1#*=}"
shift
;;
--verbose)
verbose=1
shift
;;
*)
echo "未知选项: $1"
exit 1
;;
esac
done
echo "文件: $file"
echo "输出: $output"
参数验证
验证必填参数
#!/bin/bash
input=""
output=""
while getopts "i:o:" opt; do
case $opt in
i) input="$OPTARG" ;;
o) output="$OPTARG" ;;
esac
done
# 验证必填参数
if [[ -z "$input" ]]; then
echo "错误: 必须指定输入文件 (-i)" >&2
exit 1
fi
if [[ -z "$output" ]]; then
echo "错误: 必须指定输出文件 (-o)" >&2
exit 1
fi
echo "处理 $input -> $output"
验证文件存在性
#!/bin/bash
validate_file() {
local file=$1
if [[ ! -e "$file" ]]; then
echo "错误: 文件不存在: $file" >&2
return 1
fi
if [[ ! -f "$file" ]]; then
echo "错误: 不是普通文件: $file" >&2
return 1
fi
if [[ ! -r "$file" ]]; then
echo "错误: 文件不可读: $file" >&2
return 1
fi
return 0
}
input=""
while getopts "i:" opt; do
case $opt in
i) input="$OPTARG" ;;
esac
done
if [[ -n "$input" ]]; then
validate_file "$input" || exit 1
fi
验证数值参数
#!/bin/bash
is_number() {
[[ "$1" =~ ^[0-9]+$ ]]
}
count=1
while getopts "n:" opt; do
case $opt in
n)
if is_number "$OPTARG"; then
count="$OPTARG"
else
echo "错误: -n 需要数值参数,得到: $OPTARG" >&2
exit 1
fi
;;
esac
done
echo "计数: $count"
验证枚举值
#!/bin/bash
mode="default"
while getopts "m:" opt; do
case $opt in
m)
case $OPTARG in
fast|normal|slow)
mode="$OPTARG"
;;
*)
echo "错误: 模式必须是 fast、normal 或 slow" >&2
exit 1
;;
esac
;;
esac
done
echo "模式: $mode"
实用示例
完整的命令行工具模板
#!/bin/bash
set -e
# 配置
readonly SCRIPT_NAME=$(basename "$0")
readonly SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
# 默认值
VERBOSE=0
DRY_RUN=0
OUTPUT=""
INPUT=""
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
NC='\033[0m'
log_info() {
[[ $VERBOSE -eq 1 ]] && echo -e "${GREEN}[INFO]${NC} $*"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $*" >&2
}
log_error() {
echo -e "${RED}[ERROR]${NC} $*" >&2
}
# 使用帮助
usage() {
cat << EOF
用法: $SCRIPT_NAME [选项] <输入文件>
描述:
这是一个示例脚本,展示命令行参数解析的最佳实践。
选项:
-i, --input FILE 输入文件(必需)
-o, --output FILE 输出文件(默认: stdout)
-v, --verbose 详细输出
-n, --dry-run 模拟运行,不执行实际操作
-h, --help 显示此帮助信息
示例:
$SCRIPT_NAME -i data.txt -o result.txt
$SCRIPT_NAME --verbose --input data.txt
$SCRIPT_NAME -n -i data.txt
作者:
Your Name <[email protected]>
EOF
}
# 解析短选项
parse_short_options() {
while getopts ":i:o:vnh" opt; do
case $opt in
i) INPUT="$OPTARG" ;;
o) OUTPUT="$OPTARG" ;;
v) VERBOSE=1 ;;
n) DRY_RUN=1 ;;
h) usage; exit 0 ;;
\?)
log_error "无效选项: -$OPTARG"
usage
exit 1
;;
:)
log_error "选项 -$OPTARG 需要参数"
exit 1
;;
esac
done
}
# 解析长选项
parse_long_options() {
while [[ $# -gt 0 ]]; do
case $1 in
--input)
INPUT="$2"
shift 2
;;
--output)
OUTPUT="$2"
shift 2
;;
--verbose)
VERBOSE=1
shift
;;
--dry-run)
DRY_RUN=1
shift
;;
--help)
usage
exit 0
;;
--)
shift
break
;;
-*)
log_error "未知选项: $1"
usage
exit 1
;;
*)
break
;;
esac
done
}
# 验证参数
validate_args() {
if [[ -z "$INPUT" ]]; then
log_error "必须指定输入文件"
usage
exit 1
fi
if [[ ! -f "$INPUT" ]]; then
log_error "输入文件不存在: $INPUT"
exit 1
fi
if [[ ! -r "$INPUT" ]]; then
log_error "输入文件不可读: $INPUT"
exit 1
fi
}
# 主逻辑
main() {
log_info "开始处理"
log_info "输入文件: $INPUT"
log_info "输出文件: ${OUTPUT:-stdout}"
if [[ $DRY_RUN -eq 1 ]]; then
log_warn "模拟运行模式,不执行实际操作"
return 0
fi
# 实际处理逻辑
if [[ -n "$OUTPUT" ]]; then
cat "$INPUT" > "$OUTPUT"
log_info "已写入: $OUTPUT"
else
cat "$INPUT"
fi
log_info "处理完成"
}
# 入口
if [[ $# -eq 0 ]]; then
usage
exit 0
fi
# 判断是否有长选项
if [[ "$*" == *--* ]]; then
parse_long_options "$@"
else
parse_short_options "$@"
shift $((OPTIND-1))
fi
validate_args
main "$@"
带子命令的脚本
#!/bin/bash
usage() {
cat << EOF
用法: $SCRIPT_NAME <命令> [选项]
命令:
add 添加项目
remove 删除项目
list 列出项目
help 显示帮助
全局选项:
-v, --verbose 详细输出
-h, --help 显示帮助
运行 '$SCRIPT_NAME <命令> --help' 获取命令详情。
EOF
}
SCRIPT_NAME=$(basename "$0")
VERBOSE=0
# 全局选项
while [[ $# -gt 0 ]]; do
case $1 in
-v|--verbose)
VERBOSE=1
shift
;;
-h|--help)
usage
exit 0
;;
-*)
echo "错误: 未知选项 $1" >&2
exit 1
;;
*)
break
;;
esac
done
# 获取子命令
COMMAND="${1:-}"
shift || true
# 子命令处理
case $COMMAND in
add)
name=""
while [[ $# -gt 0 ]]; do
case $1 in
-n|--name)
name="$2"
shift 2
;;
-h|--help)
echo "用法: $SCRIPT_NAME add -n <name>"
exit 0
;;
esac
done
[[ -z "$name" ]] && { echo "错误: 必须指定名称"; exit 1; }
echo "添加项目: $name"
;;
remove)
id=""
while [[ $# -gt 0 ]]; do
case $1 in
-i|--id)
id="$2"
shift 2
;;
-h|--help)
echo "用法: $SCRIPT_NAME remove -i <id>"
exit 0
;;
esac
done
[[ -z "$id" ]] && { echo "错误: 必须指定 ID"; exit 1; }
echo "删除项目: $id"
;;
list)
echo "列出所有项目"
;;
help|"")
usage
;;
*)
echo "错误: 未知命令 '$COMMAND'" >&2
usage
exit 1
;;
esac
小结
本章介绍了 Shell 脚本的命令行参数解析:
- 位置参数:
$1、$@、$#等特殊变量,shift命令 - getopts:内置的短选项解析工具,支持
-a和-a value格式 - 长选项:手动解析
--option和--option=value格式 - 参数验证:检查必填参数、文件存在性、数值有效性
- 完整模板:包含帮助信息、错误处理、详细模式的脚本模板
良好的参数解析设计可以让脚本更易用、更健壮。下一章将介绍脚本安全与最佳实践。