Shell 常见面试题详解(新手详细版)
本文档面向零基础新手,以常见面试题为纲,每题给出考察点、详细解释、示例和参考答案,帮助理解概念并能在面试中说得清楚。
一、变量与特殊变量
1.1 $?、$#、$0、$1、$@、$ 分别表示什么?$@ 和 $ 有什么区别?
考察点:特殊变量含义;参数传递时的区别。
详细解释:
| 变量 | 含义 |
|---|---|
| $? | 上一条命令的退出码(0 成功,非 0 失败) |
| $# | 脚本或函数参数的个数(不含 $0) |
| $0 | 当前脚本名(或 Shell 名),不是第 1 个参数 |
| $1, $2, … | 第 1、2、… 个参数 |
| **$*** | 所有参数拼成一个字符串(受 IFS 影响) |
| $@ | 所有参数,每个参数保持独立(用于透传) |
*$@ 和 $ 的区别**(重点):
- 不加大引号时,两者行为类似。
- “$@”:每个参数一个词,适合原样传给子命令,如
cmd "$@"。 - *“$“:所有参数合成一个字符串**,透传时会变成“一个参数”。
示例:
# 假设执行: ./script.sh "a b" c
echo "参数个数: $#" # 2
echo "第1个: $1" # a b
echo "第2个: $2" # c
printf "[%s]n" "$@" # [a b] 和 [c] 两行
printf "[%s]n" "$*" # [a b c] 一行
参考答案:
$? 是退出码;$# 是参数个数;$0 是脚本名;$1、$2 是位置参数。$@ 和 $* 都是“所有参数”,但加双引号时 “$@” 保持多个参数,*“$“ 是一个字符串;给子命令传参时应使用 “$@”**。
1.2 单引号、双引号、反引号在 Shell 里有什么区别?
考察点:引号对变量展开、命令替换、转义的影响。
详细解释:
- 双引号 “…”:
$变量、$(命令) 会展开;`、$、“、 等可被转义。空格和多数字符保留。 - 单引号 ‘…’:
什么都不展开,全部当字面字符;$HOME 就是五个字符。 - 反引号 `…`:
命令替换,和 $( ) 等价;里面的 $变量 会展开。推荐写 $( ),可嵌套、可读性好。
示例:
var=world
echo "hello $var" # hello world
echo 'hello $var' # hello $var
echo "当前目录: $(pwd)"
echo '当前目录: $(pwd)' # 当前目录: $(pwd)
参考答案:
双引号会展开变量和命令替换;单引号全部字面,不展开;反引号是命令替换,建议用 $( ) 代替。
二、条件与测试
2.1 [ ] 和 [[ ]] 有什么区别?数字比较用什么?
考察点:test/[ ] 与 Bash 的 [[ ]];数值比较的写法。
详细解释:
- [ ]:是命令(即 test),左右和内部都要有空格;逻辑用 -a、-o、!;数字比较必须用 -eq、-ne、-gt、-lt 等,不能用 =、(会当字符串)。
- [[ ]]:Bash 关键字,不是命令;可直接写 &&、||、!;支持 =~ 正则; 按字典序或数值视情况而定;可读性更好。
示例:
n=10
[ "$n" -eq 10 ] && echo "相等" # 正确
[ "$n" = 10 ] && echo "相等" # 字符串比较,可能不符合预期
[[ $n -gt 5 && $n -lt 20 ]] && echo "在范围内"
参考答案:
[ ] 是 test 命令,要空格,数字用 -eq、-gt 等;[[ ]] 是 Bash 关键字,支持 &&、||、=~,更推荐在 Bash 里用。数字比较在 [ ] 里必须用 -eq、-ne、-gt、-ge、-lt、-le。
2.2 如何判断上一条命令是否执行成功?
考察点:退出码;$?;在条件里直接用命令。
详细解释:
命令成功时退出码为 0,失败为非 0;$? 是上一条命令的退出码。判断方式:
- if 命令; then …:根据命令的退出码判断,0 为真。
- 命令 && 成功时执行、命令 || 失败时执行。
- if [ $? -eq 0 ]; then …:显式看 $?(注意 $? 会随下一条命令改变,要先保存或立刻用)。
示例:
grep -q "key" file.txt && echo "找到" || echo "未找到"
if cp a b; then echo "复制成功"; fi
参考答案:
用 $? 看退出码,0 表示成功;或直接用 if 命令; then …、命令 && …、命令 || … 根据成功与否分支。
三、字符串与变量展开
3.1 如何获取字符串长度?如何截取子串?
考察点:${#var};${var:起:长}。
详细解释:
- ${#变量名}:字符串长度。
- ${变量:起始:长度}:从起始位置截取“长度”个字符(起始从 0 开始);省略长度则到末尾。
示例:
s="hello"
echo ${#s} # 5
echo ${s:0:2} # he
echo ${s:2:2} # ll
echo ${s:2} # llo
参考答案:
长度用 ${#var};子串用 ${var:起始:长度} 或 ${var:起始}。
3.2 ${var:-default}、${var:=default}、${var:?msg} 分别表示什么?
考察点:变量默认值、赋默认值、缺省时报错。
详细解释:
- ${var:-default}:若 var 未设置或为空,展开为 default;不改变 var。
- ${var:=default}:若 var 未设置或为空,先给 var 赋值为 default,再展开。
- ${var:?错误信息}:若 var 未设置或为空,打印错误信息并退出。
示例:
unset x
echo "${x:-没有}" # 没有
echo "${x:=0}" # 0,且 x 被设为 0
: ${1:?请提供参数} # 无 $1 时报错并退出
参考答案:
:- 是空时用默认值不赋值;:= 是空时赋值并展开;:? 是空时报错并退出,常用于必填参数校验。
四、文件与路径
4.1 如何取文件路径中的“文件名”和“目录部分”?
考察点:basename、dirname;或 ${var##/}、${var%/}。
详细解释:
- basename 路径:最后一段(文件名或目录名)。
- dirname 路径:去掉最后一段的目录部分。
- 用变量展开:${path##/} 删掉最后一个 / 及左边(即文件名);${path%/} 删掉最后一个 / 及右边(即目录)。
示例:
path="/home/user/file.txt"
basename "$path" # file.txt
dirname "$path" # /home/user
echo ${path##*/} # file.txt
echo ${path%/*} # /home/user
参考答案:
用 basename、dirname,或 ${path##/} 取文件名、${path%/} 取目录。
4.2 如何取文件名的“扩展名”?
考察点:${var##*.} 或 basename + 截取。
详细解释:
*${path##.} 表示删掉最后一个 .** 及其左边,得到扩展名;若没有点则得到整串。
示例:
f="readme.txt"
echo ${f##*.} # txt
f="noext"
echo ${f##*.} # noext(无点则整串)
参考答案:
用 *${path##.}** 得到最后一个点后面的部分作为扩展名。
五、重定向与管道
5.1 标准输出和标准错误都重定向到同一文件,为什么常写 > file 2>&1 而不是 2>&1 > file?
考察点:重定向顺序;文件描述符 1、2 的指向。
详细解释:
重定向从左到右执行。2>&1 表示“把 2 指向当前 1 的去向”。
- > file 2>&1:先把 1 改到 file,再把 2 指向 1(即 file)→ 输出和错误都进 file。
- 2>&1 > file:先把 2 指向当时的 1(还是屏幕),再把 1 改到 file → 错误仍在屏幕,只有标准输出进 file。
参考答案:
顺序不同结果不同。要先让 1 指向文件,再让 2 指向 1,所以应写 > file 2>&1;若先写 2>&1,2 会指向当时的 1(屏幕),错误就不会进文件。
5.2 如何“丢弃”命令的所有输出(正常输出和错误)?
考察点:/dev/null;2>&1 或 &>。
详细解释:
/dev/null 是“黑洞”,写进去的内容被丢弃。把标准输出和标准错误都重定向到它即可。
示例:
cmd > /dev/null 2>&1
# 或 Bash:cmd &> /dev/null
参考答案:
使用 > /dev/null 2>&1 或 &> /dev/null,把标准输出和标准错误都重定向到 /dev/null。
5.3 管道 | 传递的是标准输出还是标准错误?
考察点:管道只连接标准输出;错误要进管道需先 2>&1。
详细解释:
管道只把前一个命令的标准输出接到后一个命令的标准输入;标准错误默认仍到终端。若要让错误也参与管道,需先合并:cmd 2>&1 | 下一命令。
参考答案:
管道默认只传标准输出;若也要传标准错误,需先写 2>&1 再写 |,例如 cmd 2>&1 | grep xxx。
六、文本处理:grep、sed、awk
6.1 grep、sed、awk 分别适合什么场景?简单对比。
考察点:三者的定位和典型用法。
详细解释:
- grep:按行筛选,找“包含某模式”的行;适合查日志、过滤输出。
- sed:按行做替换、删除、插入;适合批量改内容、删行。
- awk:按列处理,可做取列、计算、格式化;适合表格型文本、统计。
示例:
grep "error" log.txt # 筛出含 error 的行
sed 's/old/new/g' file.txt # 替换
awk '{print $1, $3}' file.txt # 取第 1、3 列
awk '{sum+=$2} END{print sum}' f # 第 2 列求和
参考答案:
grep 按行筛选;sed 按行编辑(替换、删行等);awk 按列处理、计算、格式化。可组合使用,如 grep 筛行后再用 awk 取列。
6.2 如何统计文件中某字符串出现的行数?如何统计出现次数总和?
考察点:grep -c;grep -o + wc。
详细解释:
- 行数:grep -c “字符串” 文件 表示“有多少行包含该字符串”。
- 总出现次数(同一行多次算多次):grep -o “字符串” 文件 | wc -l;-o 只输出匹配到的片段,每出现一次一行,wc -l 即总次数。
示例:
grep -c "error" log.txt
grep -o "error" log.txt | wc -l
参考答案:
统计行数用 grep -c “串” 文件;统计总出现次数用 grep -o “串” 文件 | wc -l。
七、循环与数组
7.1 如何遍历数组的每个元素?为什么要用 “${arr[@]}” 而不是 ${arr[*]}?
考察点:for in “${arr[@]}”;”$@” 与 “$*” 的类似区别。
详细解释:
遍历数组应写 for i in “${arr[@]}”。“${arr[@]}” 保证每个元素是一个词,元素里含空格也不会被拆开;*${arr[]} 或未加引号的 ${arr[@]}** 可能把含空格的元素拆成多个词。
示例:
arr=("a b" "c")
for x in "${arr[@]}"; do echo "[$x]"; done
# [a b]
# [c]
for x in ${arr[@]}; do echo "[$x]"; done
# [a]
# [b]
# [c]
参考答案:
用 for i in “${arr[@]}” 遍历;”${arr[@]}” 保持每个元素完整,适合含空格的元素;用 * 或不加引号可能拆词。
7.2 如何逐行读文件并处理?
考察点:while read;IFS=;重定向。
详细解释:
用 while IFS= read -r line; do … done < 文件。IFS= 避免去掉行首尾空格;-r 不把 当转义;< 文件 把文件内容作为 while 的输入。
示例:
while IFS= read -r line; do
echo "行: $line"
done < file.txt
参考答案:
while IFS= read -r line; do … done < 文件;IFS= 和 -r 保证行内容原样,< 文件 提供输入。
八、函数与脚本结构
8.1 Shell 函数如何“返回”一个值?和 return、exit 有什么区别?
考察点:return 只返回退出码;用 echo + $() 得到“返回值”;exit 结束脚本。
详细解释:
- return n:只设置函数退出码(0~255),不返回任意字符串;调用方用 $? 判断成功与否。
- “返回”一个字符串或数字:在函数里 echo 结果,调用方用 result=$(函数名) 接收。
- exit n:结束整个脚本(或当前 Shell),在函数里调 exit 也会直接退出脚本。
示例:
get_name() { echo "张三"; }
name=$(get_name)
# 用 return 只能返回 0/1,适合成功失败
参考答案:
函数没有“返回值”语法;return 只设退出码,echo + $(函数) 可得到“返回”的字符串或数字;exit 结束整个脚本,一般只在脚本顶层或严重错误时用。
8.2 如何让函数内的变量不影响外部(局部变量)?
考察点:local。
详细解释:
在函数里用 local 变量名=值 声明,该变量只在函数内(及它调用的函数内)有效,不会改变外部的同名变量。
示例:
x=1
f() { local x=2; echo "内:$x"; }
f; echo "外:$x"
# 内:2 外:1
参考答案:
在函数内用 local 声明变量,例如 local x=1,这样变量只在函数内有效。
九、进程与后台
9.1 如何把命令放到后台运行?如何让它在退出终端后仍然运行?
考察点:&;nohup;disown。
详细解释:
- 命令 &:在后台运行,终端关掉后进程通常会收到 SIGHUP 被结束。
- nohup 命令 &:忽略 SIGHUP,终端关闭后进程仍可继续;标准输出默认写到 nohup.out。
- 命令 & 然后 disown:也可让作业脱离当前 Shell,避免关终端时被结束。
示例:
sleep 100 &
nohup ./long_script.sh &
参考答案:
后台运行用 命令 &;要退出终端后仍运行用 nohup 命令 &,或 命令 & 再 disown。
9.2 $$ 和 $! 分别是什么?
考察点:当前进程 PID;最后一个后台进程 PID。
详细解释:
$$ 是当前 Shell(脚本)的进程 ID。$! 是最后一个放到后台的进程的 PID,常用于 wait 或 kill。
示例:
echo "当前脚本 PID: $$"
sleep 10 &
echo "后台进程 PID: $!"
参考答案:
$$ 是当前 Shell 的 PID;$! 是最后一个后台进程的 PID。
十、综合与习惯
10.1 脚本里如何获取“脚本所在目录”的绝对路径?
考察点:dirname + 结合 $0;可移植写法。
详细解释:
$0 是脚本名,可能含路径;dirname “$0” 得到脚本所在目录,但可能是相对路径。若需要绝对路径,可再配合 cd 和 pwd:$(cd “$(dirname “$0″)” && pwd)。
示例:
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
echo "脚本所在目录: $SCRIPT_DIR"
参考答案:
用 $(cd “$(dirname “$0″)” && pwd) 得到脚本所在目录的绝对路径,便于基于该目录找配置或数据文件。
10.2 写 Shell 脚本时,你一般会注意哪些“好习惯”?
考察点:规范与安全习惯(可展开成简答)。
参考答案要点(可按需展开):
- Shebang:第一行写 #!/bin/bash 或 #!/usr/bin/env bash。
- set -e / set -u:需要时用 set -e(命令失败即退出)、set -u(未定义变量报错)。
- 变量加引号:“$var”,避免空或含空格时出错。
- 参数校验:检查 $#、-f/-d 等,缺参或文件不存在时提示用法并 exit 1。
- 错误信息写 stderr:echo “错误” >&2;正常输出用 stdout,便于重定向。
- 退出码:正常 exit 0,异常 exit 1 或 2,便于被调用方判断。
- 危险操作前确认:如 rm、覆盖前用 read 或 -i 确认,或先 echo 列出再执行。
- 函数用 local:避免污染全局变量。
- 注释:对复杂逻辑写注释,便于维护。
十一、题目速查表
| 类别 | 常见考点 |
|---|---|
| 特殊变量 | $? $# $0 $1 $@ $ 区别;”$@” 与 “$“ |
| 引号 | 单引号不展开;双引号展开;反引号命令替换 |
| 条件 | [ ] 与 [[ ]];数字用 -eq 等;上一条命令成功用 $? 或 if cmd |
| 变量展开 | ${#var} ${var:起:长} ${var:-default} ${var:?msg} |
| 路径 | basename/dirname;${path##/} ${path%/} ${path##*.} |
| 重定向 | > file 2>&1 顺序;/dev/null;管道只传 stdout,要 2>&1 |
| 文本 | grep 筛行;sed 替换/删行;awk 取列/计算;grep -c/-o |
| 循环/数组 | for in “${arr[@]}”;while read < file |
| 函数 | return 退出码;echo+$() 当返回值;local;exit 结束脚本 |
| 进程 | & 后台;nohup;$$ $! |
| 规范 | Shebang、引号、校验、>&2、exit、注释 |
十二、小结
面试时除了背答案,最好能结合例子说明(如为什么用 “$@”、为什么是 > file 2>&1)。建议把本稿中的示例在终端里敲一遍,再结合你已学的 Shell变量.md、Shell流程控制.md、Shell函数.md、Shell传递参数.md 等文档理解原理,这样回答会更稳、更清晰。
文档以 Bash 和常见 Linux 环境为准;部分细节在不同 Shell 或系统中可能略有差异。