Shell常见面试题

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;$? 是上一条命令的退出码。判断方式:

  1. if 命令; then …:根据命令的退出码判断,0 为真。
  2. 命令 && 成功时执行命令 || 失败时执行
  3. 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

参考答案
basenamedirname,或 ${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” 得到脚本所在目录,但可能是相对路径。若需要绝对路径,可再配合 cdpwd$(cd “$(dirname “$0″)” && pwd)

示例

SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
echo "脚本所在目录: $SCRIPT_DIR"

参考答案
$(cd “$(dirname “$0″)” && pwd) 得到脚本所在目录的绝对路径,便于基于该目录找配置或数据文件。


10.2 写 Shell 脚本时,你一般会注意哪些“好习惯”?

考察点:规范与安全习惯(可展开成简答)。

参考答案要点(可按需展开):

  1. Shebang:第一行写 #!/bin/bash#!/usr/bin/env bash
  2. set -e / set -u:需要时用 set -e(命令失败即退出)、set -u(未定义变量报错)。
  3. 变量加引号“$var”,避免空或含空格时出错。
  4. 参数校验:检查 $#-f/-d 等,缺参或文件不存在时提示用法并 exit 1
  5. 错误信息写 stderrecho “错误” >&2;正常输出用 stdout,便于重定向。
  6. 退出码:正常 exit 0,异常 exit 1 或 2,便于被调用方判断。
  7. 危险操作前确认:如 rm、覆盖前用 read-i 确认,或先 echo 列出再执行。
  8. 函数用 local:避免污染全局变量。
  9. 注释:对复杂逻辑写注释,便于维护。

十一、题目速查表

类别 常见考点
特殊变量 $? $# $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变量.mdShell流程控制.mdShell函数.mdShell传递参数.md 等文档理解原理,这样回答会更稳、更清晰。


文档以 Bash 和常见 Linux 环境为准;部分细节在不同 Shell 或系统中可能略有差异。

发表评论