Linux Shell 用户输入完全指南:从命令行参数到交互读取
概述
在编写 Shell 脚本时,获取用户输入是实现 “交互性” 和 “灵活性” 的关键。常见的输入场景包括:
- 命令行参数:运行脚本时直接传入数据(如 ./script.sh 10 20);
- 命令行选项:通过 -a/-b 等符号改变脚本行为(如 ./script.sh -f log.txt);
- 交互读取:脚本运行中主动询问用户,等待键盘输入(如 “请输入用户名:”)。
Bash Shell 提供了一套完整的工具来处理这些场景,掌握它们能让脚本从 “固定逻辑” 升级为 “可配置、可交互” 的实用工具。
一、命令行参数:最基础的输入传递方式
命令行参数是运行脚本时紧跟在脚本名后的 “额外数据”,Shell 会自动将其分配给 “位置变量”,无需额外配置即可使用。
1. 位置变量:参数的 “专属容器”
Shell 用 $0~${n} 表示位置变量,每个变量对应一个命令行元素,规则如下:
- $0:脚本本身的名称(含路径,需用 basename 提取纯脚本名);
- $1~$9:第 1 到第 9 个命令行参数;
- ${10}~${n}:第 10 个及以后的参数(必须用大括号包裹,否则会被解析为 $1+0)。
示例 1:用位置变量计算阶乘
#!/bin/bash
# 脚本功能:计算 $1 对应的阶乘
if [ -n "$1" ]; then
factorial=1
# 循环从 1 到 $1,计算阶乘
for (( number=1; number<=$1; number++ )); do
factorial=$[ $factorial * $number ]
done
echo "The factorial of $1 is $factorial"
else
echo "请传入一个整数参数(如 ./factorial.sh 5)"
fi
执行效果:
./factorial.sh 5 # 传入参数 5
# The factorial of 5 is 120 # 输出 5 的阶乘
示例 2:处理 10 个以上的参数
#!/bin/bash
# 脚本功能:获取第 10、11 个参数并计算乘积
product=$[ ${10} * ${11} ]
echo "第 10 个参数:${10}"
echo "第 11 个参数:${11}"
echo "乘积:$product"
执行效果:
# 传入 12 个参数(1~12)
./param10+.sh 1 2 3 4 5 6 7 8 9 10 11 12
# 第 10 个参数:10
# 第 11 个参数:11
# 乘积:110
2. 参数中的空格:必须用引号包裹
若参数包含空格(如 “big world”),直接传递会被 Shell 解析为 “两个参数”,需用 单引号或双引号 将其包裹为 “单个整体”。
示例:传递含空格的字符串参数
#!/bin/bash
# 脚本功能:打印传入的字符串
echo "Hello $1, glad to meet you."
错误与正确执行对比:
# 错误:未加引号,“big world”被拆分为两个参数,$1 仅为“big”
./greet.sh big world
# Hello big, glad to meet you.
# 正确:加引号,“big world”被视为一个参数
./greet.sh "big world" # 或 './greet.sh 'big world''
# Hello big world, glad to meet you.
3. 提取纯脚本名:解决 $0 含路径的问题
$0 会返回脚本的 “完整路径”(如 ./factorial.sh 或 /home/user/factorial.sh),若只需 “纯脚本名”,需用 basename 命令 剥离路径。
示例:提取脚本名并生成日志
#!/bin/bash
# 脚本功能:记录脚本运行时间到日志
script_name=$(basename $0) # 提取纯脚本名(如 factorial.sh)
log_path="$HOME/script_log.txt"
# 将运行时间写入日志
echo "${script_name} 运行于 $(date '+%Y-%m-%d %H:%M:%S')" >> $log_path
echo "日志已记录至 $log_path"
执行效果:
./record_log.sh
# 日志已记录至 /home/user/script_log.txt
cat /home/user/script_log.txt
# record_log.sh 运行于 2024-06-03 15:30:00
二、参数测试与特殊变量:避免脚本崩溃
直接使用位置变量时,若用户未传入参数,脚本可能因 “空变量” 报错。Shell 提供了 参数统计 和 特殊变量,可提前检测参数合法性。
1. 特殊变量:快速获取参数信息
Shell 定义了 3 个核心特殊变量,用于简化参数处理:
特殊变量 | 含义 | 示例(运行 ./script.sh a b c) |
---|---|---|
$# | 命令行参数的总个数(不含脚本名 $0) | $# 等于 3 |
$* | 所有参数视为 “单个字符串” | $* 等于 "a b c" |
$@ | 所有参数视为 “多个独立字符串” | $@ 等于 "a" "b" "c" |
${!#} | 最后一个参数(无需知道参数总数) | ${!#} 等于 "c" |
示例 1:用 $# 检测参数个数
#!/bin/bash
# 脚本功能:计算两个数的和,需传入 2 个参数
if [ $# -ne 2 ]; then
# 参数个数不对时,提示正确用法
echo "用法:$(basename $0) 数字1 数字2"
else
total=$[ $1 + $2 ]
echo "$1 + $2 = $total"
fi
执行效果:
# 未传参数:提示用法
./add.sh
# 用法:add.sh 数字1 数字2
# 传入 2 个参数:正常计算
./add.sh 17 25
# 17 + 25 = 42
示例 2:$* 与 $@ 的核心区别
$* 会将所有参数合并为 “一个字符串”,$@ 会保留参数的 “独立性”,差异仅在 加双引号时 体现:
#!/bin/bash
# 脚本功能:遍历所有参数
echo "=== 使用 \$*(合并为单个字符串) ==="
count=1
for param in "$*"; do
echo "参数 $count:$param"
count=$[ $count + 1 ]
done
echo -e "\n=== 使用 \$@(保留独立参数) ==="
count=1
for param in "$@"; do
echo "参数 $count:$param"
count=$[ $count + 1 ]
done
执行效果(运行 ./loop_params.sh a b c):
=== 使用 $*(合并为单个字符串) ===
参数 1:a b c
=== 使用 $@(保留独立参数) ===
参数 1:a
参数 2:b
参数 3:c
结论:遍历参数时优先用 $@,避免含空格的参数被拆分。
2. shift 命令:“左移” 参数,简化遍历
shift 命令会将所有位置变量 “向左移动 N 位”(默认 N=1),$1 被删除,$2 变成新的 $1,适合 “不知道参数总数” 时遍历所有参数。
示例:用 shift 遍历所有参数
#!/bin/bash
# 脚本功能:遍历所有参数并打印
count=1
echo "所有参数:"
# 当 $1 不为空时,持续循环
while [ -n "$1" ]; do
echo "参数 $count:$1"
count=$[ $count + 1 ]
shift # 左移 1 位,$2 变成新的 $1
done
执行效果(运行 ./shift_loop.sh x y z):
所有参数:
参数 1:x
参数 2:y
参数 3:z
进阶:指定 shift 移动的位数
#!/bin/bash
# 脚本功能:跳过前 2 个参数,处理后续参数
echo "原始参数:$*"
shift 2 # 左移 2 位,跳过前 2 个参数
echo "跳过前 2 个参数后,第一个参数:$1"
执行效果(运行 ./shift_2.sh a b c d):
原始参数:a b c d
跳过前 2 个参数后,第一个参数:c
三、命令行选项:改变脚本行为的 “开关”
命令行选项是带 - 的单个字母(如 -a、-f),用于 “开关功能” 或 “传递参数值”(如 -f log.txt 指定日志文件)。Shell 提供 3 种处理选项的方法:case 语句、getopt、getopts。
1. 基础方法:用 case 语句识别选项
适合处理简单选项(如 -a、-b),核心是通过 case 匹配选项,用 shift 遍历所有参数。
示例 1:处理无参数的选项
#!/bin/bash
# 脚本功能:识别 -a、-b、-c 选项
while [ -n "$1" ]; do
case "$1" in
-a) echo "找到选项:-a" ;;
-b) echo "找到选项:-b" ;;
-c) echo "找到选项:-c" ;;
*) echo "未知选项:$1" ;; # 匹配未定义的选项
esac
shift # 左移参数,处理下一个
done
执行效果(运行 ./case_options.sh -a -c -d):
./case_options.sh -a -c -d
# 找到选项:-a
# 找到选项:-c
# 未知选项:-d
示例 2:分离选项与普通参数(-- 分隔符)
Linux 标准约定:-- 是 “选项结束符”,-- 后的内容均视为 “普通参数”,而非选项。
#!/bin/bash
# 脚本功能:用 -- 分离选项和参数
while [ -n "$1" ]; do
case "$1" in
-a|-b|-c) echo "找到选项:$1" ;;
--) shift; break ;; # 遇到 -- 停止处理选项
*) echo "未知选项:$1" ;;
esac
shift
done
# 处理 -- 后的普通参数
count=1
echo -e "\n普通参数:"
for param in "$@"; do
echo "参数 $count:$param"
count=$[ $count + 1 ]
done
执行效果:
./sep_options_params.sh -a -b -- file1 file2
# 找到选项:-a
# 找到选项:-b
#
# 普通参数:
# 参数 1:file1
# 参数 2:file2
示例 3:处理带参数值的选项(如 -f log.txt)
部分选项需要 “额外参数值”(如 -f 指定文件),需在 case 中提取 $2 作为值,并多执行一次 shift 跳过该值。
#!/bin/bash
# 脚本功能:处理带参数值的选项(-f 指定文件,-n 指定次数)
while [ -n "$1" ]; do
case "$1" in
-f) file="$2"; echo "指定文件:$file"; shift ;; # 提取 $2 为文件路径
-n) times="$2"; echo "指定次数:$times"; shift ;; # 提取 $2 为次数
-v) echo "启用详细模式" ;;
*) echo "未知选项:$1" ;;
esac
shift
done
执行效果:
./value_options.sh -v -f /var/log/syslog -n 3
# 启用详细模式
# 指定文件:/var/log/syslog
# 指定次数:3
2. 进阶方法:用 getopt 处理复杂选项
getopt 是外部命令,可自动解析选项(包括合并选项如 -ac)、处理选项参数值,但不支持含空格的参数值。
核心语法:getopt optstring parameters
- optstring:定义有效选项,需参数值的选项后加 :(如 ab:c 表示 -a 无值,-b 有值,-c 无值);
- parameters:待解析的命令行参数(通常为 $@)。
示例:在脚本中使用 getopt
#!/bin/bash
# 脚本功能:用 getopt 解析选项
# 1. 用 getopt 格式化参数(-q 忽略错误)
set -- $(getopt -q ab:c "$@")
# 2. 遍历格式化后的参数
while [ -n "$1" ]; do
case "$1" in
-a) echo "找到选项:-a" ;;
-b) file="$2"; echo "指定文件:$file"; shift ;;
-c) echo "找到选项:-c" ;;
--) shift; break ;; # 结束选项处理
*) echo "未知选项:$1" ;;
esac
shift
done
# 3. 处理普通参数
echo -e "\n普通参数:$@"
执行效果(支持合并选项 -ac):
./getopt_demo.sh -ac -b test.txt file1
# 找到选项:-a
# 找到选项:-c
# 指定文件:test.txt
#
# 普通参数:file1
3. 推荐方法:用 getopts 处理所有场景
getopts 是 Bash 内建命令,解决了 getopt 的缺陷(支持含空格的参数值),且语法更简洁,是处理选项的首选工具。
核心语法:getopts optstring variable
- optstring:定义有效选项,规则与 getopt 一致(需值的选项后加 :,开头加 : 忽略错误);
- variable:存储当前解析到的选项(自动去除 -,如 -a 存为 a);
- 环境变量:OPTARG 存储选项的参数值,OPTIND 存储当前处理的参数位置。
示例:用 getopts 解析选项
#!/bin/bash
# 脚本功能:用 getopts 处理选项(支持含空格的参数值)
while getopts :ab:c opt; do # : 开头忽略错误,b: 表示 -b 需参数值
case "$opt" in
a) echo "找到选项:-a" ;;
b) echo "指定文件:$OPTARG" ;; # OPTARG 存储 -b 的参数值
c) echo "找到选项:-c" ;;
?) echo "未知选项:$OPTARG" ;; # ? 匹配未定义选项(OPTARG 为选项本身)
esac
done
# 处理普通参数(OPTIND 是选项结束的位置)
shift $[ $OPTIND - 1 ]
echo -e "\n普通参数:"
count=1
for param in "$@"; do
echo "参数 $count:$param"
count=$[ $count + 1 ]
done
执行效果(支持含空格的参数值):
./getopts_demo.sh -a -b "my file.txt" -c data1 data2
# 找到选项:-a
# 指定文件:my file.txt # 正确识别含空格的参数值
# 找到选项:-c
#
# 普通参数:
# 参数 1:data1
# 参数 2:data2
getopts 的核心优势
- 自动处理合并选项:如 -ac 会被拆分为 -a 和 -c,无需额外处理;
- 支持含空格的参数值:-b "my file.txt" 会被正确解析,OPTARG 为 my file.txt;
- 简化参数位置管理:OPTIND 自动记录下一个待处理的参数位置,方便分离选项和普通参数。
4. 选项标准化:遵循 Linux 通用约定
为了让脚本更易用,建议遵循 Linux 命令的“选项含义标准”,避免用户记忆额外逻辑。常见标准化选项如下:
选项 | 通用含义 | 脚本中的应用场景举例 |
---|---|---|
-a | 显示所有对象 | ./list.sh -a 列出所有文件(含隐藏文件) |
-c | 生成计数 | ./count.sh -c log.txt 统计文件行数 |
-d | 指定目录 | ./backup.sh -d /home/data 指定备份目录 |
-f | 指定输入文件 | ./parse.sh -f data.csv 指定待解析文件 |
-h | 显示帮助信息 | ./script.sh -h 打印用法说明 |
-l | 长格式输出 | ./ls.sh -l 显示文件详细信息(权限、大小等) |
-n | 非交互模式(批处理) | ./install.sh -n 静默安装(不提示) |
-v | 详细模式(打印过程) | ./deploy.sh -v 显示部署每一步操作 |
-x | 排除指定对象 | ./clean.sh -x temp/ 清理时排除 temp 目录 |
示例:为脚本添加 -h 帮助选项
#!/bin/bash
# 脚本功能:带帮助选项的加法脚本
show_help() {
echo "用法:$(basename $0) [选项] 数字1 数字2"
echo "选项:"
echo " -h 显示帮助信息"
echo " -v 启用详细模式"
echo "示例:"
echo " $(basename $0) 10 20 # 计算 10+20"
echo " $(basename $0) -v 10 20 # 显示计算过程"
}
# 解析选项
verbose=0
while getopts :hv opt; do
case "$opt" in
h) show_help; exit 0 ;;
v) verbose=1 ;;
?) echo "未知选项:$OPTARG"; show_help; exit 1 ;;
esac
done
# 处理普通参数(数字1和数字2)
shift $[ $OPTIND - 1 ]
if [ $# -ne 2 ]; then
echo "错误:需传入 2 个数字参数"
show_help
exit 1
fi
# 计算并输出结果
total=$[ $1 + $2 ]
if [ $verbose -eq 1 ]; then
echo "计算过程:$1 + $2 = $total"
else
echo $total
fi
执行效果(查看帮助):
./add_help.sh -h
# 用法:add_help.sh [选项] 数字1 数字2
# 选项:
# -h 显示帮助信息
# -v 启用详细模式
# 示例:
# add_help.sh 10 20 # 计算 10+20
# add_help.sh -v 10 20 # 显示计算过程
四、交互读取:用 read 命令实现用户问答
read 命令用于 “脚本运行中主动获取键盘输入”,支持自定义提示符、超时控制、隐藏输入(如密码)等功能,是实现脚本交互性的核心工具。
1. 基础用法:读取输入并存储到变量
read 命令默认从 “标准输入(键盘)” 读取数据,按 Enter 键结束,输入内容会存储到指定变量中(未指定变量时存入 REPLY 环境变量)。
示例 1:简单的姓名输入
#!/bin/bash
# 脚本功能:读取姓名并问候
# 方法1:用 echo -n 生成提示符(不换行)
echo -n "请输入您的姓名:"
read name # 读取输入到 name 变量
echo "您好,$name!欢迎使用本脚本。"
# 方法2:用 read -p 直接指定提示符(更简洁)
read -p "请输入您的年龄:" age
echo "您的年龄是 $age 岁,对应的天数约为 $[ $age * 365 ] 天。"
执行效果:
./read_basic.sh
# 请输入您的姓名:张三
# 您好,张三!欢迎使用本脚本。
# 请输入您的年龄:25
# 您的年龄是 25 岁,对应的天数约为 9125 天。
示例 2:未指定变量时用 REPLY
#!/bin/bash
# 脚本功能:用 REPLY 存储输入
read -p "请输入一句话:"
echo "您输入的内容是:$REPLY"
执行效果:
./read_reply.sh
# 请输入一句话:Shell 很强大!
# 您输入的内容是:Shell 很强大!
2. 进阶功能:超时、隐藏输入与字符限制
read 命令提供多个选项,满足不同交互场景需求:
选项 | 功能描述 | 示例 |
---|---|---|
-t N | 超时时间(N 秒),超时后返回非 0 状态码 | read -t 5 -p "5 秒内输入:" var |
-s | 隐藏输入(不显示输入内容,适合密码) | read -s -p "输入密码:" pass |
-n N | 读取 N 个字符后自动结束(无需按 Enter) | read -n 1 -p "是否继续(Y/N)?" ans |
-p "提示语" | 直接指定提示符(替代 echo -n) | read -p "请输入:" var |
示例 1:超时控制(避免脚本无限等待)
#!/bin/bash
# 脚本功能:5 秒内未输入则超时
if read -t 5 -p "请在 5 秒内输入您的姓名:" name; then
echo -e "\n您好,$name!"
else
echo -e "\n超时未输入,脚本退出。"
exit 1
fi
执行效果(超时场景):
./read_timeout.sh
# 请在 5 秒内输入您的姓名:
# 超时未输入,脚本退出。
示例 2:隐藏输入(密码输入场景)
#!/bin/bash
# 脚本功能:隐藏密码输入
read -s -p "请输入密码:" pass
echo -e "\n密码长度为:${#pass} 个字符" # ${#pass} 计算字符串长度
执行效果(输入时不显示密码):
./read_password.sh
# 请输入密码: # 输入时无显示
# 密码长度为:8 个字符
示例 3:读取单个字符(快速确认)
#!/bin/bash
# 脚本功能:读取单个字符确认操作
read -n 1 -p "是否删除该文件(Y/N)?" ans
echo # 换行,避免后续输出与提示符重叠
case "$ans" in
Y|y) echo "正在删除文件..." ;;
N|n) echo "取消删除操作。" ;;
*) echo "输入无效,请输入 Y 或 N。" ;;
esac
执行效果(输入 Y 后自动确认):
./read_single_char.sh
# 是否删除该文件(Y/N)?Y
# 正在删除文件...
3. 高级用法:从文件中读取内容
read 命令不仅能读取键盘输入,还能读取文件内容 —— 每次调用 read 读取文件的 “一行”,直到文件末尾(返回非 0 状态码)。
示例:逐行读取文件并处理
#!/bin/bash
# 脚本功能:逐行读取文件并统计行数
file_path="$HOME/test.txt"
if [ ! -f "$file_path" ]; then
echo "文件 $file_path 不存在!"
exit 1
fi
count=1
echo "文件 $file_path 的内容:"
# 用 cat 输出文件内容,通过管道传给 while read(逐行读取)
cat "$file_path" | while read line; do
echo "第 $count 行:$line"
count=$[ $count + 1 ]
done
echo "文件总行数:$[ $count - 1 ]"
执行效果(假设 test.txt 有 3 行内容):
./read_file.sh
# 文件 /home/user/test.txt 的内容:
# 第 1 行:Hello World
# 第 2 行:Shell 读取文件示例
# 第 3 行:End of file
# 文件总行数:3
五、总结:Shell 用户输入的最佳实践
- 选择合适的输入方式:
- 若输入是 “固定参数”(如数字、文件名),用 命令行参数(简洁高效);
- 若输入是 “功能开关”(如是否启用详细模式),用 命令行选项(getopts 首选);
- 若输入需要 “用户确认”(如密码、操作选择),用 read 交互读取(提升安全性)。
- 参数与选项的防护措施:
- 用 $# 检测参数个数,避免 “空参数” 导致脚本报错;
- 用双引号包裹变量(如 "$1"、"$OPTARG"),避免含空格的输入被拆分;
- 为脚本添加 -h 帮助选项,降低用户使用成本。
- 交互读取的体验优化:
- 用 read -p 替代 echo -n,简化提示符配置;
- 敏感信息(如密码)用 read -s 隐藏输入,提升安全性;
- 长时间等待的场景(如用户输入)用 read -t 设置超时,避免脚本挂起。
掌握以上内容后,你可以编写从 “简单工具” 到 “复杂交互脚本” 的各类 Shell 程序,满足日常运维、自动化任务的大部分需求。建议结合实际场景练习(如编写 “带选项的文件备份脚本”“带交互确认的日志清理脚本”),深化对输入处理逻辑的理解。