DanLevy.net

测验:Bash 与 Shell 精通

你能和电脑对话吗?比如说,顺畅点?

测试你的 Bash 脚本技能,这里有 16 道题!

覆盖变量、循环、条件、字符串操作、函数以及从基础到棘手的语法陷阱。

磨练(或证明)你的 shell 脚本 技能!

在 Bash 中如何定义变量?

在 Bash 中,变量声明时 = 两侧不能有空格。例如:

Terminal window
name=Alice

这会把值 "Alice" 赋给变量 name

注意:$name 用于引用或读取变量的值。

= 两侧添加空格会导致 shell 将其解释为要运行的程序,这不是给变量赋值时想要的行为。

同时,Bash 区分大小写,所以 nameNAMEName 是不同的变量。

最后,变量名不能包含空格或连字符(-),请使用下划线(_)或驼峰式命名。

什么会打印出 It's 🔨 Time!?

我知道。转义这么快就让字符串难以解析,真是疯狂。想象在 Bash 字符串中转义其他语言——所有的引号、撇号和 $ 符号都会捣乱你。🫠

单引号在单引号字符串内部需要转义。关闭引号、转义引号、重新打开引号的序列('\'')可以输出:

It's 🔨 Time!

还有其他处理方式,但这是最常见的。

此命令会输出什么?

Terminal window
echo c{a,b}t

{} 大括号展开会为其字符串上下文生成多个版本,每个逗号分隔的值或模式对应一个(或多个)版本。

这里,c{a,b}t 会展开为:

cat cbt

现在,这会打印什么?

Terminal window
price="$100"
echo "Cost: $price"

编号变量具有特殊含义。在本例中,$1 是一个特殊变量,保存传递给脚本或函数的第一个参数。

由于我们在 REPL 中运行脚本,没有参数,所以 $1 为空。剩余的文本 00 原样打印。

要打印字面 $ 字符,使用单引号,或用反斜杠转义它(\\):

Terminal window
price="\$100"
echo "Cost: $price"

这里发生了什么?

Terminal window
str="Bark bark"
echo ${str/bark/meow}

${var/pattern/replacement} 语法将 pattern 的第一次出现替换为 replacement。这里的输出是:

Bark meow

它是区分大小写的。要同时处理 barkBark,可以使用类似 ${var/[Bb]ark/Bark} 的模式,或在替换前对字符串进行规范化。

要替换所有出现的情况,使用 ${var//pattern/replacement}

要从字符串开头进行替换,使用 ${var/#pattern/replacement}

要从字符串结尾进行替换,使用 ${var/%pattern/replacement}

如何在 Bash 中获取变量的长度?

${#username} 语法返回 username 的长度。

例如:

Terminal window
username="@justsml"
echo ${#username} # => 8

虽然 wc 能工作,但它技术上不是 Bash 的一部分。

实用程序 wc 曾是一个老玩笑,指的是 “water closet”(厕所)。 开玩笑的!有人会读这些吗?

实际上 wc 是一个来自 POSIX(以及 AT&T Unix 时代)的古老命令。它是 “word count” 的缩写,可以统计文件或输入流中的行数、单词数和字符数。

如果文件 cats.txt 存在,这段脚本会输出什么?

Terminal window
if [ -e cats.txt]; then
echo "File exists"
else
echo "File does not exist"
fi

你注意到闭合括号前缺少的空格了吗?

Bash 在这方面非常挑剔:括号表达式内部必须有空格。

由于缺少空格导致 [ 命令看不到闭合的 ],Bash 会打印诊断信息,将测试视为失败,并继续执行 else 分支。

正确的语法是:

Terminal window
if [ -e example.txt ]; then
echo "File exists"
else
echo "File does not exist"
fi

注意:条件表达式建议使用双中括号 [[ ]] 推荐参见 BashFAQ.

在 Bash 中如何比较字符串?

Terminal window
cat1="Rosie"
cat2="Sunflower"
if [ "$cat1" === "$cat2" ]; then
echo "Same cat"
else
echo "Different cats"
fi

又一个测试语法错误!

你注意到无效的 === 运算符了吗?

你可能在想 JavaScript…

使用 [ ... ] 时,Bash 会报告诊断并且条件为 false,所以 else 分支会打印 Different cats。在 Bash 中,使用 === 进行相等比较。

这个脚本会输出什么?

Terminal window
function greet () {
echo "$1"
}
greet Hi Dan

Bash 中的函数可以接受参数。$1 变量保存传递给函数的第一个参数。

记住,$0 是脚本名,$1 是第一个参数,$2 是第二个参数,依此类推。空格用于分隔参数。 所以,greet Hi Dan 会把 "Hi" 作为第一个参数。若想把 "Hi Dan" 作为单个参数传入,需要加引号:greet "Hi Dan"

哪个运算符将一个命令的输出连接到下一个命令的输入

| 管道运算符将一个命令的输出连接到另一个命令的输入。例如:

Terminal window
echo "Mr. Levy 👨🏻‍🔬" | wc -m
# => 14

Bash 中的数学是如何工作的?

在 Bash 中,(( )) 语法执行整数运算。

它可用于简单计算:

Terminal window
((result = 2 + 2))
echo $result # => 4

或用于条件表达式:

Terminal window
if (( 2 > 1 )); then
echo "2 is greater than 1"
fi

对于浮点运算,考虑使用 bcawk

以下哪项能够正确地将 10 与 0.5 相乘并输出 5?

(( )) 语法仅执行整数算术。也就是说,只能处理没有小数点的整数!

Bash(也许让人惊讶)没有内置的浮点运算支持。

最常见的解决方案是使用 GNU 工具 bcawk

这个脚本中的 : 是做什么的?

Terminal window
rosie="Bad cat, good cat"
echo ${rosie:9}

${var:offset} 语法会从 offset 开始提取子串。这里的输出是:

good cat

要提取特定长度的子串,使用 ${var:offset:length}

要从字符串末尾提取,使用 ${var: -offset}。(注意 - 前面的空格!)

什么不是 Bash 中用于循环的关键字?

each 不是 Bash 中的循环关键字。主要的循环关键字是 forwhileuntil

虽然 do 在技术上不是循环关键字,但它是循环语法的关键部分。

哪个选项会执行 ls -l 并返回其输出?

$(ls -l) 语法会执行 括号 中的命令并替换为输出。例如:

Terminal window
echo "Today is $(date +%F)"
# => Today is 2029-12-31

第一个选项使用单引号 '而不是反引号。这会阻止展开,所以 '$(date +%F)' 只会打印字面字符串 $(date +%F)

虽然仍然支持使用反引号(`ls -l`)来执行命令,但最近在某些场景下被视为反模式。大多数人推荐使用 $(command),因为可读性更好且在不同 shell 与版本间更一致。

花括号 ${} 用于变量展开,而不是命令替换。

% 符号并不用于命令替换。

哪个运算符用于将错误输出合并到标准输出?

2>&1 运算符将标准错误(文件描述符 2)重定向到标准输出(文件描述符 1)。这在需要把错误信息和普通输出放在同一流中捕获时非常有用。

1>&2 运算符则是把标准输出重定向到标准错误,但题目问的是如何把标准错误重定向到标准输出。

想了解底层细节,查看 Greg 的优秀重定向 FAQ

同时感谢 Reddit 用户 u/OneTurnMore 提供的文案改进建议。

我的 Bash 测验让你崩溃了吗?

在下面的评论中告诉我吧!

进一步阅读

提升 Bash 技能,请参考以下资源: