2409 字
12 分钟
Shell 八股

基础语法#

Shell脚本第一行是什么?为什么重要? #!/bin/bash(shebang),指定脚本解释器。

变量赋值为什么报错command not found 等号两边不能有空格——name = "Alice"会被解析为命令,正确写法name="Alice"

如何定义只读变量? readonly vardeclare -r var,后续无法修改。

如何撤销变量? unset var(不能撤销只读变量)。

[ ]vs[[ ]]vs(())

语法类型支持逻辑运算符支持正则/通配符空变量安全性
[ ]POSIX 通用-a(与)、-o(或)不支持变量必须加双引号,否则报错
[[ ]]Bash 特有&&、||、!支持 =~(正则)和 ==(通配符)变量无需加引号,自动处理空值
(( ))Bash 特有数学比较(><>=专用于数值计算专用于 C 风格循环和数值判断

判断文件是否存在且为普通文件 [ -f "$file" ][[ -f $file ]]

数值计算

  • $((...))(最推荐,纯 Bash 内建,速度最快):sum=$((a + b))
  • let 命令let sum=a+b(无需 $ 前缀)
  • expr 外部命令(慢,过时):sum=$(expr $a + $b)(注意加号两侧必须有空格)
  • 浮点数运算:必须借助 bcawk,如 echo "scale=2; 5/3" | bc

exit和return的区别? exit N终止整个脚本;return N从函数返回。0表示成功,非0表示失败。

位置参数$0$1$#$?$$$@的含义?

  • $0:脚本名,
  • $1:第一个参数,
  • $#:传递给脚本的参数个数,
  • $?:上条命令退出码。
  • $$:当前Shell进程的 PID(常用于生成临时唯一文件名,如 /tmp/a_$$)。
  • $!:后台最后一个进程的 PID(配合 wait $! 使用)。
  • $@:所有参数(带引号保留空格),

什么是IFS?"$*""$@" 区别?

Internal Field Separator(内部字段分隔符),是 Shell 中的一个特殊环境变量,用来决定 Shell 在哪些字符处把一个字符串拆分成多个单词(字段)。默认值是:空格 + 制表符 + 换行符

假设这样调用:./test.sh "a b" c d

写法是否保留每个参数的独立性是否受 IFS 影响结果
$@ / $*❌ 会重新分词✅ 受影响“a” “b” “c” “d”
"$@"✅ 每个参数独立❌ 不受影响“a b” “c” “d”
"$*"❌ 合并成一个字符串✅ 受影响(用 IFS 连接)“a b c d”

重定向#

用于改变命令的输入来源或输出目的地,替代默认的 键盘输入(stdin)屏幕输出(stdout/stderr)

常用语法

  • 输出覆盖command > file(覆盖写入)
  • 输出追加command >> file(追加写入)
  • 输入重定向command < file(从文件读取输入)
  • 错误输出command 2> file(仅错误信息)
  • 合并输出command > file 2>&1(stdout 和 stderr 合并)
  • Here Documentcommand << EOF ... EOF(内联多行输入),常用生成配置文件。
    • <<- 可以忽略前导 Tab(缩进友好)
  • Here Stringcommand <<< "string"(单行字符串作为标准输入)

2>&1 必须放在哪?

  • 功能:将标准错误(2)重定向到标准输出(1)所在的地方。
  • 正确写法command > file 2>&1(先让标准输出去file,再把错误也指向file)。
  • 错误写法command 2>&1 > file(错误先指向了当前终端的标准输出,随后标准输出才去file,导致错误依然打印在屏幕上)
  • 简写command &> file 等效于上面正确的写法。

作用域#

子 Shell 与大括号 {} 的区别?

  • 小括号 ( ... ):在子 Shell 中执行,内部变量修改不影响父 Shell。
  • 大括号 { ... }:在当前 Shell 中执行,内部变量修改会影响父 Shell。注意大括号内部左侧必须有空格,且命令结尾必须加分号 ;

进程替换(<())与管道(|)的本质区别

  • 管道 |:会创建子 Shell,导致父 Shell 变量无法被子进程修改。a=1; echo "2" | read a; echo $a 依然输出 1。
  • 进程替换 <():不会引入子 Shell 的副作用,相当于把命令输出伪装成文件。while read line; do ...; done < <(cat file) 可以在当前 Shell 中执行循环(规避管道变零)。

export 变量和无export区别?

  • export:变量会传递给所有子进程(子Shell、外部命令、脚本等),称为环境变量
  • export:变量仅在当前Shell进程中有效,子进程不可见,称为局部变量(或Shell变量)。

source/bash script.sh 和 ./script.sh 和 exec ./mod.sh的区别? source在当前Shell执行,变量和环境变量保留;./script.sh在子Shell执行,变量不保留。

执行方式执行环境变量继承是否影响当前环境权限
source 或 . script.sh当前Shellr
bash script.sh子Shellr
./script.sh子Shellrx
exec script.sh脚本覆盖当前Shellrx

综合上面两个:

#!/bin/bash
export PATH="aaa"
cd bbb

记上述脚本为mod.sh,问:source/bash mod.shexec ./mod.sh 父 Shell 的 PATH 和当前目录怎么变化?为什么?

执行方式父Shell的 PATH父Shell的PWD脚本运行结束后
source mod.shaaabbb变量和目录变更永久保留在父Shell中
bash mod.sh不变不变脚本在子Shell中运行,父Shell毫无影响
exec ./mod.sh消失消失父Shell被脚本替换,进程随脚本结束而退出

进阶与陷阱#

如何安全地逐行读取文件?

Terminal window
while IFS= read -r line; do
echo "$line"
done < file

使用IFS=将IFS设置为一个空字符串,以确保读取的行不会因为空格或制表符被截断,-r防止反斜杠转义。

set -e的作用? set命令用于显示或设置 Shell 的特性和变量,-e 遇到非零退出码立即退出脚本。 注意:管道中命令失败不一定触发(需配合set -o pipefail)。

set -euxo pipefail 各参数含义?

  • -e:脚本中任何命令失败(非0退出码)立即终止退出(防止错误蔓延)。
  • -u:引用未定义变量时报错退出(防止空变量酿成事故)。
  • -x调试模式,执行前把每条命令打印到终端(用于排错)。
  • -o pipefail:管道中任何一个子命令失败,整个管道就算失败(弥补 -e 只管管道最后一条命令的缺陷)。
  • -n:仅语法检查,不执行(可用于排错)。
  • -v:打印读取到的原始输入行(与 -x 不同,-x 打印展开后的命令)。

如何避免rm -rf误删?ls确认再执行;用rm -i交互确认;关键操作前备份;脚本中用set -u防止空变量。

Shell脚本中如何捕获信号并做清理? trap 'cleanup; exit' INT TERM 捕获中断和终止信号。

信号捕获与后台控制

  • trap:捕获信号并执行自定义操作。常用于脚本退出时清理临时文件。
    • 语法:trap "rm -f /tmp/temp_$$" EXIT(脚本无论正常/异常退出都会触发)。
    • 常见信号:INT(Ctrl+C)、TERMEXIT
  • wait:阻塞等待后台进程结束。wait $pid 等待指定进程,无参则等待所有后台子进程。
    • 面试题:sleep 3 & wait 会等待 sleep 结束再继续。

如何用Shell实现并发控制? 使用&后台执行 + wait等待,或用xargs -P NGNU parallel控制并发数。

后台运行 & vs nohup 的区别

  • command &:放入后台运行,但进程依然属于当前终端(Shell)的子进程。终端关闭(收到 SIGHUP 信号),进程会随之消亡
  • nohup command &:让进程忽略 SIGHUP 信号(1信号),同时默认将输出重定向到 nohup.out终端关闭后,进程依然存活
  • 最佳实践:想退出终端后服务不挂,必须用 nohup + &(或用 screen/tmux)。

如何定义函数并获取返回值?

  • 定义function func_name() { ... }func_name() { ... }
  • 局部变量:必须用 local 声明,否则默认全局污染。
  • 返回值(数字)return 0(成功)或 return 1(失败),通过 $? 接收。return 只能返回 0-255 的状态码
  • 返回字符串:不能用 return,必须用 echo 输出,调用方用 $(func) 接收。

单引号 '、双引号 "、反引号 “和 $() 的区别

  • 单引号 ''所见即所得,完全保留字面意思,无视所有变量和转义符。
  • 双引号 ""部分保留,允许变量展开 $var 和命令替换 $(),但会阻止通配符 * 展开。
  • 反引号 “与 $():均为命令替换(先执行内部命令)。强烈推荐 $(),\支持嵌套(如 $(grep $(whoami) file))且引号处理更清晰。

登录Shell vs 非登录Shell 与 .bashrc/.bash_profile 加载逻辑

  • 登录Shell(如 su -、SSH登录):先读 /etc/profile,再找 ~/.bash_profile(或 ~/.bash_login~/.profile),通常不读 ~/.bashrc
  • 非登录Shell(如手动敲 bash):只读 ~/.bashrc
  • 解决痛点:为了登录Shell也能加载别名,通常在 ~/.bash_profile 里强制写入 source ~/.bashrc

其他#

命令连接符/逻辑判断:;&&||#

  • ;无视成败,前面命令执行完(无论对错),无条件执行后面的命令。
  • &&短路与,前面命令执行成功$?=0)才执行后面的。
  • ||短路或,前面命令执行失败$?≠0)才执行后面的。
  • 经典组合make && make install || echo "Failed"(编译成功才安装,任一失败则报错)。

分支与循环结构速记#

结构语法要点
if-then-elseif [ condition ]; then ...; elif [ cond2 ]; then ...; else ...; fi分号和 then 同行
case适合字符串匹配,支持 * 通配符。末尾 ;; 跳出,*) 为默认
for 循环for i in {1..10}; do ...; done(序列) for ((i=0; i<10; i++)); do ...; done(C风格)
while 循环while read line; do ...; done < file逐行读文件标准做法
until 循环条件为假时执行,与 while 相反

数组操作(Bash 特有)#

操作语法
定义索引数组arr=(a b c)arr[0]="a"
定义关联数组(类似Map)declare -A map; map["key"]="value"
获取所有元素${arr[@]}(独立参数) vs ${arr[*]}(单字符串,类似于 "$@" vs "$*"
获取数组长度${#arr[@]}${#arr[*]}
获取索引列表${!arr[@]}(关联数组常用)
切片${arr[@]:1:2}(从下标1取2个)
Shell 八股
https://blog-l7wd3.pages.dev/posts/interview/shell/
作者
L7WD3-Xiao
发布于
2026-08-10
许可协议
CC BY-NC-SA 4.0

分享文章

生成精美分享图或复制链接,与更多人分享本文。

继续阅读

沿着主题读

基于共同的标签与分类

换条路线

从其他文章中稳定抽取