20 道Shell 与 Python 脚本练习题及答案
CompTIA Linux+ 认证考试练习的全部Shell 与 Python 脚本练习题,附正确答案和简短解析。
开始练习测验 →1. 某个备份脚本的第一行是 #!/bin/bash,并以 ./backup.sh 的方式运行。这一行到底起什么作用?
- A.它指明内核要加载哪个解释器来运行该文件,其余内容都交给 /bin/bash 处理。✓ 答案
- B.它把 /bin/bash 放到脚本 PATH 的最前面,命令会优先在该目录里查找。
- C.它只是写给 shell 看的注释,脚本会由调用者当时所使用的那个 shell 解释执行。
- D.它把文件标记为可以自行运行,因此不再需要用 chmod 设置可执行位。
#! 必须位于第一行;内核读取它,并以该脚本作为参数执行所指定的解释器。它对内核而言不是注释,不会改动 PATH,也不会赋予直接执行所必需的可执行权限。
编号 QB-G5MARA依据: bash(1) manual page, Invocation and the #! interpreter directive报告此题的问题
2. 执行 ./deploy.sh 输出 "bash: ./deploy.sh: Permission denied",echo $? 显示 126;而 bash ./deploy.sh 却能正常运行。造成差异的原因是什么?
- A.当前工作目录不在 PATH 中,./ 形式无法定位文件,而解释器形式会在 PATH 中查找。
- B.文件里没有解释器指令行,直接执行时内核无从选择解释器,而 bash 形式自带解释器。
- C.该脚本属主是 root,只有通过 sudo 才能执行,而普通用户读取文件内容仍然允许。
- D.该文件缺少可执行位,用 chmod +x deploy.sh 即可直接执行;指定解释器只需读权限。✓ 答案
退出状态 126 表示命令已找到但无法执行,对脚本而言就是缺少可执行权限;文件不存在或路径错误才会返回 127。把文件交给 bash 只需要读权限,所以那种写法能成功。
编号 QB-XM3TY8依据: POSIX.1 Shell Command Language, Exit Status; bash(1) exit status conventions (126 and 127)报告此题的问题
3. 某脚本以 ./sync.sh /srv/data "quarter report" --verbose 的方式调用。关于它的位置参数,哪一项说法正确?
- A.$# 为 4,因为 $0 也计入,"$@" 展开成四个词并以脚本名开头。
- B.$# 为 3,"$@" 展开成三个词,带空格的那个参数保持完整。✓ 答案
- C.$# 为 3,但 "$@" 会把各参数合并成一个词,用 IFS 的首字符分隔。
- D.$# 为 4,且 "$*" 保持各参数独立,而 "$@" 把它们合并成一个词。
$# 只统计参数个数,不含 $0,所以这里是 3。加引号的 "$@" 每个参数展开为一个词,因此 "quarter report" 中的空格得以保留;把参数连成一个词的是 "$*"。
编号 QB-F59YJ9依据: bash(1) manual page, Special Parameters ($#, $@ and $*)报告此题的问题
4. 脚本中有 f="/var/log/app log.txt",随后是 if [ -f $f ]; then,shell 报出 "[: too many arguments"。原因和修正方法是什么?
- A.-f 运算符不接受含空格的名字,这类路径必须改用 [ -e $f ] 来判断。
- B.空格必须在赋值时用反斜线转义,因为引号在存入变量之前就被去掉了。
- C.未加引号的展开被拆成两个词,写成 [ -f "$f" ] 即可只传一个参数。✓ 答案
- D.[ 内建命令只接受一个操作数,该判断得写成 [ -f $f -a -r $f ] 的形式。
参数展开之后会进行单词拆分,于是 [ -f $f ] 变成了三个操作数,test 便报参数过多。双引号可以阻止拆分,把路径当作一个参数;赋值本身已经正确保存了空格。
编号 QB-WMQFT7依据: bash(1) manual page, Word Splitting and the test/[ builtin报告此题的问题
5. 脚本中 count="01"。测试 [ "$count" = "1" ] 为假,而 [ "$count" -eq 1 ] 为真。两者为何结果不同?
- A.= 只能用在 [[ ]] 里,在 [ ] 中一律返回假,而 -eq 在两种形式中都有效因此成立。
- B.= 会先去掉前导零再比较,所以两个测试都该为真,为假说明变量引用写错了。
- C.-eq 按字典顺序排列操作数,01 正好排在 1 之前,于是 shell 认为两个值相等。
- D.= 按文本比较两个操作数,-eq 按整数求值,所以 01 作为字符串不同、作为数字相等。✓ 答案
test 的 = 是字符串比较,-eq 是算术比较,因此像 01 这样带前导零的数字在文本上不相等、在数值上相等。需要数值比较时误用 =,或对非数字文本使用 -eq,都是脚本里的典型错误。
编号 QB-E5WMD8依据: bash(1) manual page, test/[ conditional expressions: string versus arithmetic operators报告此题的问题
6. 脚本中有:case "$1" in start) echo starting ;; stop|restart) echo stopping ;; *) echo usage; exit 2 ;; esac。以参数 restart 运行该脚本时会发生什么?
- A.stop|restart 分支和 *) 分支都会输出,因为 * 同样匹配,case 在 ;; 之后继续比较。
- B.stop|restart 分支输出 stopping,随后 ;; 结束 case,不再尝试 *) 分支。✓ 答案
- C.没有任何分支运行,因为并列模式之间要用逗号分隔,整个 case 被跳过。
- D.*) 分支输出 usage 并以 2 退出,因为 case 先比较默认模式再比较字面量。
case 自上而下比较各个模式,执行第一个匹配的命令列表,;; 结束该分支并跳到 esac 之后继续。| 用于分隔并列模式,*) 只有在上面都不匹配时才会执行。
编号 QB-VMC5N6依据: POSIX.1 Shell Command Language, case conditional construct报告此题的问题
7. 某启动脚本中有:until pg_isready -h db.internal >/dev/null 2>&1; do sleep 5; done。这个循环如何运行?
- A.只要 pg_isready 失败就继续 sleep,首次成功即退出循环,因为 until 在状态非零时执行循环体。✓ 答案
- B.只要 pg_isready 成功就继续 sleep,首次失败即退出循环,因为 until 只是 while 的另一种同义写法。
- C.循环体会先执行一次再判断条件,因为 until 属于后测型循环,类似其他语言里的 do-while。
- D.若 pg_isready 一开始就失败则完全跳过循环体,因为 until 需要零状态才会进入循环。
until 在条件命令返回非零状态时不断执行循环体,一旦返回零就停止,这与 while 正好相反。所以它非常适合用来等待某个服务变为可用。
编号 QB-D5JA87依据: POSIX.1 Shell Command Language, until loop报告此题的问题
8. 脚本执行:count=0;grep -c ERROR /var/log/app.log | while read -r n; do count=$n; done;echo "$count"。输出始终是 0,为什么?
- A.默认 IFS 会让 read 丢掉数字,因此每一轮循环 count 都被赋成空值。
- B.grep -c 把计数写到 stderr,循环什么也读不到,count 保持原值不变。
- C.管道的每一段都在子 shell 中运行,循环里的赋值随子 shell 结束而丢失。✓ 答案
- D.必须在循环前 export count,父 shell 才能看到子进程更新后的值。
管道中的命令在子 shell 中执行,而子进程无法修改父进程的变量,所以循环结束后 count 又回到 0。改用重定向或进程替换(done < <(grep -c ...))可让循环留在当前 shell;export 只能把值向下传给子进程。
编号 QB-4NUFUE依据: bash(1) manual page, Pipelines and Command Execution Environment (subshell semantics)报告此题的问题
9. 脚本先有 tmp=/var/tmp/keep,再定义 prep() { tmp=$(mktemp); },调用 prep 之后执行 rm -rf "$tmp"。怎样修改才能保住调用方原来的 tmp 值?
- A.在 prep 内部加上 export tmp,新值就只会传给子进程的环境。
- B.在 prep 内部把 tmp 设为 readonly,改动只在 prep 运行期间有效。
- C.在 prep 内部用 local tmp 声明,赋值的作用范围就限制在函数体内。✓ 答案
- D.用 function 关键字定义 prep,函数内的每个变量都会获得独立作用域。
除非用 local 声明,shell 函数与调用方共用同一套变量,所以 prep 的赋值覆盖了脚本里的 tmp。local 只在本次调用期间创建独立实例;export 只影响子进程继承的内容,而 readonly 会让赋值直接失败而不是限定范围。
编号 QB-M52NEF依据: bash(1) manual page, Shell Functions and the local builtin报告此题的问题
10. 某包装脚本写着 if /usr/local/bin/healthcheck.sh; then echo ok; else echo bad; fi,但即使检查脚本最后执行的是 echo "FAIL",它也总是走 ok 分支。检查脚本应当怎么做?
- A.失败路径要以非零状态退出,因为调用方判断的是退出码。✓ 答案
- B.失败信息要写到 stderr,因为 shell 依据所用的流判定成败。
- C.把 FAIL 大写输出供调用方 grep,因为 if 检查命令的输出内容。
- D.在输出信息前赋值 status=1,因为脚本返回最后一次赋的值。
脚本的退出状态就是它对调用方的接口:if、&&、|| 和 $? 读的都是这个数字,零表示成功。由于 echo 本身会成功,只打印 FAIL 的脚本仍然返回 0,所以失败路径必须用 exit 返回非零值。
编号 QB-XT9FRA依据: POSIX.1 Shell Command Language, Exit Status and the exit special built-in报告此题的问题
11. 部署脚本中有:mkdir -p /srv/app/rel && cp -a build/. /srv/app/rel/ || echo "deploy failed" >&2。对这条命令链的哪项描述正确?
- A.三条命令都会按书写顺序执行,因为 && 和 || 只做连接、并不检查退出状态。
- B.只有 mkdir 成功时才执行 cp,而只有 mkdir 返回非零时才会输出该信息。
- C.只有 mkdir 成功时才执行 cp,而两条命令中任意一条返回非零都会输出该信息。✓ 答案
- D.无论 mkdir 结果如何都会执行 cp,只有 cp 失败才输出信息,因为 && 仅起分组作用。
&& 只在前一条命令状态为零时执行右侧命令,|| 则在它前面整个 AND-OR 列表的状态非零时执行右侧命令。所以 mkdir 失败会跳过 cp 并触发提示,cp 失败也同样触发提示。
编号 QB-EB4A6A依据: POSIX.1 Shell Command Language, AND-OR lists (&& and ||)报告此题的问题
12. 一个很长的配置脚本在某条命令失败后仍继续执行,并且因变量名拼错而默默使用了空值。在开头加哪一行能让它尽早失败退出?
- A.set -x,每条命令在执行前都会打印出来,并在第一条失败的命令处停止脚本。
- B.set -euo pipefail,命令失败、变量未定义或管道中某一段失败都会中止脚本。✓ 答案
- C.shopt -s failglob,只要脚本运行的任何命令返回非零状态就会立即终止。
- D.set +e,恢复 shell 的默认行为,即某条命令一旦失败就立刻退出脚本。
-e 在未处理的非零状态时退出,-u 把引用未定义变量当作错误,pipefail 让管道返回第一个失败阶段的状态而不是只看最后一条命令。set +e 的作用与 -e 相反,set -x 只做跟踪,failglob 针对的是无匹配的通配符。
编号 QB-YTMSWB依据: bash(1) manual page, set builtin options -e, -u, -x and -o pipefail报告此题的问题
13. 某脚本生成了错误的目标路径却没有报任何错误。哪个命令能在 shell 执行每条命令时显示其展开后的形式?
- A.bash -n ./script.sh,它一边运行脚本一边列出其执行的每次变量赋值。
- B.bash -x ./script.sh,它在每条命令运行前把展开后的命令回显出来。✓ 答案
- C.bash -v ./script.sh,它在命令运行之后才显示该命令,且展开已经完成。
- D.bash -e ./script.sh,它在首次失败处停止并转储所有 shell 变量的值。
-x 选项(等同于在脚本内 set -x)会把每条简单命令在展开后写成一行跟踪输出,前面带 PS4 提示符,从而暴露变量的真实内容。-n 只做语法检查而不执行,-v 在展开前回显输入行,-e 改变的是错误处理而非跟踪。
编号 QB-FBFMBB依据: bash(1) manual page, set -x execution tracing and shell invocation options报告此题的问题
14. 脚本用 workdir=$(mktemp -d) 创建临时目录,并从多处退出,失败时会留下无用的目录。标准的清理做法是什么?
- A.创建目录后立即注册 trap 'rm -rf "$workdir"' EXIT,任何退出路径都会删除它。✓ 答案
- B.创建目录后立即注册 trap 'rm -rf "$workdir"' ERR,正常结束的情况也能一并覆盖。
- C.把 rm -rf "$workdir" 放在脚本最后一行,任何退出路径包括提前失败都会执行到。
- D.改用 mktemp -d -u 创建,这样脚本进程结束时内核会自动删除该目录。
EXIT 上的 trap 会在 shell 退出时执行处理程序,无论是执行到末尾、调用了 exit,还是在 set -e 下中止,因此能覆盖所有退出路径。ERR 陷阱只在失败时触发,写在最后一行的清理会被提前退出跳过,而 -u 只让 mktemp 打印一个未使用的名字并不创建目录。
编号 QB-VTJSD8依据: bash(1) manual page, trap builtin and the EXIT pseudo-signal; mktemp(1) manual page报告此题的问题
15. 一个配置脚本每晚在所有主机上运行。它用 >> 向 /etc/hosts 追加一行,并用 useradd svc 创建账号。哪一项对缺陷的描述最准确?
- A.它不具备可读性:两条命令每次都能正确执行,但读者看不出哪一项改动更重要。
- B.它不具备原子性:两个步骤彼此分开,中途被打断就会留下没有对应账号的主机条目。
- C.它不具备可移植性:>> 重定向会把该行写进不同发行版家族中路径各异的文件。
- D.它不具备幂等性:每次运行都追加一行重复条目,useradd 随后失败,结果取决于运行次数。✓ 答案
幂等的脚本无论运行多少次都让系统处于同样的状态,这正是计划任务可以安全重复执行的前提。无条件追加和不加判断就调用 useradd 都破坏了这一点,因此脚本在修改前必须先检查该行和该账号是否已经存在。
编号 QB-CBDMT7依据: CompTIA Linux+ exam objectives, automation and scripting domain (idempotent automated tasks); useradd(8) manual page报告此题的问题
16. 某报表脚本需要在一台 python3 由发行版包管理器提供的服务器上安装第三方 Python 库。恰当的安装方式是什么?
- A.用 sudo pip install 安装该库,让所有用户共用一份并由包管理器跟踪。
- B.把 PYTHONPATH 指向存放解包源码的目录,让系统解释器直接找到该库。
- C.用 python3 -m venv 建立虚拟环境,激活后在其中用 pip install 安装该库。✓ 答案
- D.以服务账号身份执行 pip install --user,由发行版包管理器记录这个依赖。
虚拟环境为项目提供独立的解释器前缀和 site-packages,因此 pip 安装的内容不会与发行版包管理器所拥有的文件冲突或覆盖它们,再配合依赖清单即可在别处复现同一套包。用 sudo pip 装进受管解释器有可能破坏系统工具,而 --user 或 PYTHONPATH 之类的做法仍然让依赖无人记录。
编号 QB-WTW4K9依据: Python Standard Library documentation, venv module (creation of virtual environments)报告此题的问题
17. 某 Python 脚本载入数千条账号记录,之后需要反复按用户名查找单条记录。哪种数据结构最适合这种查找?
- A.按文件顺序排列的元组列表,因为按下标取元素是访问任意记录最快的方式。
- B.以用户名为键的字典,因为按键可以直接取到对应记录而无需逐条遍历。✓ 答案
- C.由用户名字符串组成的集合,因为集合会把用户及其各个字段一并保存起来。
- D.保存整个文件内容的字符串,因为切片能直接取出某条记录而不必先做解析。
字典是基于哈希的映射,按键取值的开销与记录总数无关;而在列表中查找某条记录只能逐项比较。集合只保存键本身,无法携带每条记录的其余字段。
编号 QB-DBRXY8依据: Python Standard Library documentation, built-in types: mapping type dict报告此题的问题
18. 某 Python 脚本读取 JSON 配置文件,目前只要文件缺失或格式损坏就抛出回溯信息终止。现在要求改为记录日志并用默认值继续运行,应当加入什么?
- A.在读取之前先用 os.path.exists 检查配置文件路径,这同样能覆盖文件存在但 JSON 内容非法的情形。
- B.在 else 分支里调用 sys.exit(0),把文件缺失算作正常结束,随后再载入默认值。
- C.在读取函数末尾加一个裸 raise,把回溯降级为警告,让脚本得以继续往下执行。
- D.在读取处加 try/except 捕获 OSError 和 json.JSONDecodeError,记录后改用默认值。✓ 答案
捕获具体异常可以让脚本自行决定如何处理而不是终止,而且两种失败都要处理:文件缺失或不可读会抛 OSError,内容非法会抛 json.JSONDecodeError。事先检查文件是否存在既有竞态问题,也说明不了内容是否合法,而裸 raise 是再次抛出异常而非降级处理。
编号 QB-3UPSEF依据: Python Language Reference, the try statement; Python Standard Library documentation, json module exceptions报告此题的问题
19. 某 Python 运维脚本需要运行 systemctl is-active nginx 并根据结果分支处理。恰当的调用方式是哪一种?
- A.用 eval 处理该命令字符串,因为 eval 会把文本交给 shell 并返回其退出状态。
- B.用 os.system,命令写成一个字符串,把它的返回值当作捕获到的标准输出读取。
- C.用 subprocess.run 并设 shell=True,把单元名拼进字符串,由 shell 安全处理引用。
- D.用 subprocess.run,命令以列表传入并设 capture_output=True,再按 returncode 分支。✓ 答案
subprocess.run 是文档推荐的运行外部命令的接口:以参数列表传入可完全绕开 shell,返回的 CompletedProcess 带有 returncode 以及捕获的 stdout 和 stderr 供判断。os.system 返回的是等待状态而非输出,shell=True 加字符串拼接会引入命令注入风险,而 eval 求值的是 Python 表达式而非 shell 命令。
编号 QB-JBHMUE依据: Python Standard Library documentation, subprocess module (subprocess.run and security considerations)报告此题的问题
20. 一个运维脚本需要拼接并检查文件系统路径、读取以 JSON 编写的配置文件、给每条记录打上当天日期,并把分级消息写入日志文件。按顺序对应这四项任务的标准库模块是哪一组?
- A.pathlib、json、datetime、logging✓ 答案
- B.os.path、pickle、calendar、print
- C.glob、configparser、datetime、warnings
- D.shutil、csv、time、syslog
pathlib 把文件系统路径表示为对象,可以拼接并判断是否存在;json 负责解析和生成 JSON 文本;datetime 提供日期和时间戳;logging 按严重级别把消息发往可配置的处理器。其余几组混入了复制文件、表格文本、对象序列化或通配符匹配的模块,都无法完成这四项工作。
编号 QB-4K8XW5依据: Python Standard Library documentation, pathlib, json, datetime and logging modules报告此题的问题
本考试的其他主题
练习题依据 CompTIA 公布的 Linux+ 考试大纲及 Linux 系统管理的通行实践编写。CompTIA 和 Linux+ 是 CompTIA 的商标,Linux 是 Linus Torvalds 的注册商标;本网站与 CompTIA 无关联,也未获其认可。真实考试除选择题外还包含实操题(performance-based questions),要求在模拟环境中实际完成任务——四选一的题型无法复现这一部分,因此本题库覆盖的是知识部分,请同时在真实系统上动手练习。CompTIA 会定期修订大纲并为每个版本编号:报考前请向 CompTIA 确认现行大纲、考试代码与报考要求。 关于 CompTIA Linux+ 认证 →