测验:Bash 与 Shell 精通
你能和电脑对话吗?比如说,顺畅点?
测试你的 Bash 脚本技能,这里有 16 道题!
覆盖变量、循环、条件、字符串操作、函数以及从基础到棘手的语法陷阱。
磨练(或证明)你的 shell 脚本 技能!
在 Bash 中,变量声明时 = 两侧不能有空格。例如:
这会把值 "Alice" 赋给变量 name。
注意:$name 用于引用或读取变量的值。
在 = 两侧添加空格会导致 shell 将其解释为要运行的程序,这不是给变量赋值时想要的行为。
同时,Bash 区分大小写,所以 name、NAME 和 Name 是不同的变量。
最后,变量名不能包含空格或连字符(-),请使用下划线(_)或驼峰式命名。
我知道。转义这么快就让字符串难以解析,真是疯狂。想象在 Bash 字符串中转义其他语言——所有的引号、撇号和 $ 符号都会捣乱你。🫠
单引号在单引号字符串内部需要转义。关闭引号、转义引号、重新打开引号的序列('\'')可以输出:
还有其他处理方式,但这是最常见的。
{} 大括号展开会为其字符串上下文生成多个版本,每个逗号分隔的值或模式对应一个(或多个)版本。
这里,c{a,b}t 会展开为:
编号变量具有特殊含义。在本例中,$1 是一个特殊变量,保存传递给脚本或函数的第一个参数。
由于我们在 REPL 中运行脚本,没有参数,所以 $1 为空。剩余的文本 00 原样打印。
要打印字面 $ 字符,使用单引号,或用反斜杠转义它(\\):
${var/pattern/replacement} 语法将 pattern 的第一次出现替换为 replacement。这里的输出是:
它是区分大小写的。要同时处理 bark 和 Bark,可以使用类似 ${var/[Bb]ark/Bark} 的模式,或在替换前对字符串进行规范化。
要替换所有出现的情况,使用 ${var//pattern/replacement}。
要从字符串开头进行替换,使用 ${var/#pattern/replacement}。
要从字符串结尾进行替换,使用 ${var/%pattern/replacement}。
${#username} 语法返回 username 的长度。
例如:
虽然 wc 能工作,但它技术上不是 Bash 的一部分。
实用程序 wc 曾是一个老玩笑,指的是 “water closet”(厕所)。
开玩笑的!有人会读这些吗?
实际上 wc 是一个来自 POSIX(以及 AT&T Unix 时代)的古老命令。它是 “word count” 的缩写,可以统计文件或输入流中的行数、单词数和字符数。
如果文件 cats.txt 存在,这段脚本会输出什么?
echo "File does not exist"
你注意到闭合括号前缺少的空格了吗?
Bash 在这方面非常挑剔:括号表达式内部必须有空格。
由于缺少空格导致 [ 命令看不到闭合的 ],Bash 会打印诊断信息,将测试视为失败,并继续执行 else 分支。
正确的语法是:
if [ -e example.txt ]; then
echo "File does not exist"
注意:条件表达式建议使用双中括号 [[ ]] 推荐。参见 BashFAQ.
在 Bash 中如何比较字符串?
if [ "$cat1" === "$cat2" ]; then
又一个测试语法错误!
你注意到无效的 === 运算符了吗?
你可能在想 JavaScript…
使用 [ ... ] 时,Bash 会报告诊断并且条件为 false,所以 else 分支会打印 Different cats。在 Bash 中,使用 = 或 == 进行相等比较。
Bash 中的函数可以接受参数。$1 变量保存传递给函数的第一个参数。
记住,$0 是脚本名,$1 是第一个参数,$2 是第二个参数,依此类推。空格用于分隔参数。 所以,greet Hi Dan 会把 "Hi" 作为第一个参数。若想把 "Hi Dan" 作为单个参数传入,需要加引号:greet "Hi Dan"。
哪个运算符将一个命令的输出连接到下一个命令的输入?
| 管道运算符将一个命令的输出连接到另一个命令的输入。例如:
echo "Mr. Levy 👨🏻🔬" | wc -m
在 Bash 中,(( )) 语法执行整数运算。
它可用于简单计算:
或用于条件表达式:
echo "2 is greater than 1"
对于浮点运算,考虑使用 bc 或 awk。
以下哪项能够正确地将 10 与 0.5 相乘并输出 5?
(( )) 语法仅执行整数算术。也就是说,只能处理没有小数点的整数!
Bash(也许让人惊讶)没有内置的浮点运算支持。
最常见的解决方案是使用 GNU 工具 bc 或 awk。
这个脚本中的 : 是做什么的?
rosie="Bad cat, good cat"
${var:offset} 语法会从 offset 开始提取子串。这里的输出是:
要提取特定长度的子串,使用 ${var:offset:length}。
要从字符串末尾提取,使用 ${var: -offset}。(注意 - 前面的空格!)
each 不是 Bash 中的循环关键字。主要的循环关键字是 for、while 和 until。
虽然 do 在技术上不是循环关键字,但它是循环语法的关键部分。
$(ls -l) 语法会执行 括号 中的命令并替换为输出。例如:
echo "Today is $(date +%F)"
第一个选项使用单引号 ',而不是反引号。这会阻止展开,所以 '$(date +%F)' 只会打印字面字符串 $(date +%F)。
虽然仍然支持使用反引号(`ls -l`)来执行命令,但最近在某些场景下被视为反模式。大多数人推荐使用 $(command),因为可读性更好且在不同 shell 与版本间更一致。
花括号 ${} 用于变量展开,而不是命令替换。
% 符号并不用于命令替换。
2>&1 运算符将标准错误(文件描述符 2)重定向到标准输出(文件描述符 1)。这在需要把错误信息和普通输出放在同一流中捕获时非常有用。
1>&2 运算符则是把标准输出重定向到标准错误,但题目问的是如何把标准错误重定向到标准输出。
想了解底层细节,查看 Greg 的优秀重定向 FAQ。
同时感谢 Reddit 用户 u/OneTurnMore 提供的文案改进建议。
我的 Bash 测验让你崩溃了吗?
在下面的评论中告诉我吧!
进一步阅读
提升 Bash 技能,请参考以下资源: