The Missing Semester of Your CS Education 学习笔记以及一些拓展知识(二)

文章目录

  • The Missing Semester of Your CS Education 学习笔记以及一些拓展知识
    • Bash脚本
      • 笔记部分
        • 一些在Bash脚本中的常用命令补充
          • 常用标准输入输出命令
          • 常用环境变量(普通变量)控制命令
          • 常用系统时间信息获取命令
          • 常用函数执行状态控制命令
          • 常用脚本执行控制命令
        • Bash脚本的创建和运行
        • Bash脚本的SheBang
        • Bash脚本的变量
          • 变量的定义
          • 变量的使用
          • 变量的类型
            • 局部变量
            • 环境变量
            • 基本内置变量(位置参数)
            • 特殊内置变量
          • Bash变量的三个特殊机制
            • 数组
            • 命令替换 (Command Substitution)
            • 变量扩展(参数拓展)
          • 变量使用时的注意事项
        • Bash脚本的条件结构
          • test命令
            • test的基本功能
            • test指令的三种形式
            • test的常用测试表达式
            • 组合条件
            • test的使用基本注意事项
          • let命令
            • let的基本功能
            • let支持的运算符
            • let的改进写法:双括号算术运算 ((...))
          • if-then结构
            • if的三种结构
            • if的判断条件类型
            • if的一些使用注意
          • case结构
            • case的基本结构
            • case使用的一些注意事项
          • &&和||
        • Bash脚本的循环结构
          • for循环
            • for循环的两种基本结构
            • for循环的注意事项
          • while循环
            • while的基本语法:
            • while的常见用法
            • while的注意事项
          • until循环
          • 循环控制指令
            • break
            • continue
        • Bash脚本的函数
          • 函数的定义
          • 函数的调用
          • 函数的传参
          • 函数变量的作用域
          • 函数的执行结果
          • 总结函数的注意事项
        • Bash脚本的Debug
          • 语法检查
          • 脚本逻辑的debug
          • 其他
      • 习题部分

The Missing Semester of Your CS Education 学习笔记以及一些拓展知识

以下是使用The Missing Semester of Your CS Education的个人学习笔记,方便之后翻阅

Bash脚本

上一节我们学习了简单的命令。假设有这么一种情况:我们希望每次开机后都执行一串相同的命令,难道说我们只能一个命令一个命令地在终端里面敲吗?答案是我们可以把这些重复命令编写成shell脚本,以下是二者的主要区别:

  • 交互性 vs. 自动化
    命令行是为交互而生的。你输入一个命令,马上就能看到结果,然后根据结果决定下一步做什么。这个即时反馈的循环对于文件管理、系统监控和问题排查至关重要。
    脚本是为自动化而生的。它的设计初衷就是“一次编写,多次运行”。对于那些需要重复执行的、由多个步骤组成的任务(例如:每日备份、部署网站、批量转换文件),写成脚本可以极大地提高效率并减少人为错误。
  • 编程能力上,Bash脚本引入了非常多的语法结构,支持编写复杂的逻辑功能。
  • 运行环境的差别(非常重要)
    命令行中的命令在当前 Shell 环境中执行。你在命令行里做的任何环境改变(如用 cd 切换目录、用 export 设置环境变量),都会立即对你当前的 Shell 生效。
    脚本默认在一个新的子 Shell (subshell) 中执行。这意味着脚本内部对环境的修改(如 cd、变量赋值)不会影响到执行它的那个父 Shell。

举个例子:你在命令行里现在位于 /home/user。你有一个脚本 test.sh,内容是 cd /tmp。你执行 ./test.sh。脚本执行时,它的工作目录确实切换到了 /tmp。但当脚本执行完毕,你回到命令行时,你所在的目录仍然是 /home/user,而不是 /tmp。因为 cd 命令只在那个短暂存在的子 Shell 里生效了。

例外:如果你使用 source 命令(或其简写 .)来执行脚本,那么脚本会在当前 Shell 中执行,其内部的环境变量和目录切换会保留下来。例如:source test.sh。

笔记部分

一些在Bash脚本中的常用命令补充
常用标准输入输出命令
命令全称基本功能常用参数
readread从标准输入(stdin)中读取一行文本,并将其赋值给一个或多个变量-p(prompt):在读取输入前,先显示指定的提示信息。常用于交互式脚本。
-r (raw read):原始读取模式。禁止反斜杠 \ 的转义功能。
-s (silent):静默模式。不将用户输入的内容显示在屏幕上。非常适合用于输入密码或敏感信息。
-t <秒数> :设置一个超时时间。如果在指定秒数内用户没有输入,read 命令会失败并返回一个非零状态码。
-n <字符数> :读取指定数量的字符后立即返回,而无需等待用户按回车。
echoecho(回声)在标准输出(stdout)上打印文本或变量内容。-n :输出内容后不自动添加换行符。
-e :启用对反斜杠转义字符的解释。
crulSee URL默认将从URL获取的响应打印到标准输出-s (silent):静默模式。不显示进度条和错误信息。在脚本中强烈推荐使用。
-L (Location):自动跟随重定向。如果请求的 URL 返回一个3xx重定向,curl 会自动请求新的 URL。
-o <文件名> (output):将下载内容写入指定文件,而不是标准输出。
-O:将下载内容以 URL 中的原始文件名保存在当前目录。
-I (Information):只获取 HTTP 响应头(HEAD请求)。
-X <方法> (e.g., POST, PUT, DELETE):指定 HTTP 请求方法
-H “<头部信息>” (Header):添加自定义的 HTTP 请求头。
-d “<数据>” (data):发送 POST 请求的数据体。

crul有一个姊妹命令wget(web get),与 curl 不同,它的默认行为是将内容保存到文件而非stdio。

常用环境变量(普通变量)控制命令
命令全称基本功能常用参数
envenvironment显示所有环境变量或者进行一些环境变量临时设置env: 直接执行,列出当前所有的环境变量,每行一个 变量名=值。
-i表示忽略当前的所有环境变量,在一个完全干净的空环境中执行后续命令。
env <变量=值> <命令>: 临时为 <命令> 设置一个环境变量,但不影响当前的 Shell 环境。
printenvprint environment打印环境变量的值printenv: 不带参数时,行为与 env 几乎完全相同,列出所有环境变量。
printenv <变量名>: 只打印指定单个环境变量的值。
exportexport将一个变量“输出”到环境中,用于创建新的环境变量,或者将一个已经存在的局部变量提升(或称“导出”)为环境变量。export 变量名=值: 定义一个变量并立即将其导出为环境变量(最常用)。
变量名=值; export 变量名: 先定义一个局部变量,再将其导出。
-p: 打印出当前 Shell 中所有被导出的环境变量,其输出格式是可被 Shell 重新执行的 export 变量名=“值” 形式。单独执行 export -p 的效果与 env 类似。
-n: 将一个已存在的环境变量降级为一个局部变量,它将不再被子进程继承
unsetunset从当前 Shell 环境中彻底删除一个变量或函数-v (variable):明确表示要删除的是一个变量(这是默认行为,通常可省略)。
-f (function):明确表示要删除的是一个函数。
常用系统时间信息获取命令
命令全称基本功能常用参数
datedate获取系统时间或者设置系统时间+Format:自定义日期形式,如:date +‘%Y-%m-%d %H:%M:%S’
-s 设置系统时间
-d 计算相对时间

关于获取系统的硬件信息和系统信息之后再说吧

常用函数执行状态控制命令
命令全称基本功能常用参数
returnreturn立即终止当前正在执行的函数并为该函数设置一个退出状态码 ($?)return [n],n 是一个可选的整数参数,范围是0到255。如果省略n,函数的退出状态码将是函数中最后一条被执行的命令的退出状态码。
truetrue不做任何事,返回状态码0
falsefalse不做任何事,返回状态码1
常用脚本执行控制命令
命令全称基本功能常用参数
exitexit立即终止整个脚本的执行,并可以为脚本设置一个最终的退出状态码 ($?)。exit [n]如果省略 n,则脚本的退出状态码是 exit 命令之前最后一条被执行的命令的退出状态码。
sleepsleep让脚本的执行暂停指定的一段时间。sleep <数字>[后缀],后缀可以是s、m、h、d
shiftshift将参数列表向左“移动”一位。原来的$1会被丢弃(不可恢复),$2变成新的$1,$3变成新的 $2,以此类推。同时,参数总数 $# 的值也会减 1。shift [n],n 是一个可选的数字,表示要移动的位数。如果省略,默认为 1。
sourcesource当前的Shell环境中读取并执行一个脚本文件中的命令,而不是为该脚本启动一个新的子 Shell。
execexecute使用一个新的命令来替换当前的 Shell 进程。

关于更进一步的一些进程控制命令如kill、jobs、fg、bg之后再说吧

Bash脚本的创建和运行

还是HelloWorld程序起手:

touch helloworld.sh
nano helloworld.sh# 编写如下内容,保存后退出
#!/bin/bash
echo "Hello World"chmod u+x helloworld.sh #更改权限
./helloworld.sh

从这个小小的例子可以看出:创建文件、编写代码、赋予权限、执行是所有 Bash 脚本开发的基础流程。

Bash脚本的SheBang

Shebang,即#(sharp,但简写成she)+!(Bang)是位于脚本文件第一行开头的一个特殊字符序列。它的作用是向操作系统(OS)的程序加载指明当用户尝试直接执行这个文件时,请不要把它当作普通的二进制文件,而是应该用我后面指定的这个解释器来运行。

基本语法:

#!/path/to/interpreter [optional-argument]
  • #!: 这两个字符是固定不变的“魔术字符”,内核通过它们识别这是一个需要解释器执行的脚本。
  • /path/to/interpreter: 这是解释器程序的绝对路径。对于 Bash 脚本,它通常是 /bin/bash。
  • [optional-argument]: 这是一个可选的参数,会传递给解释器,一般情况下省略。

例如:

#!/bin/bash
#!/bin/sh
#!/usr/bin/python3
#!/usr/bin/node

以上分别是bash、通用shell、python和Node.js脚本的解释器声明。

如果你不写 Shebang,直接运行 ./myscript.sh,那么你当前的 Shell(比如你正在使用的 Bash 或 Zsh)会尝试去逐行解释执行这个脚本,就可能会因为语法不兼容而出错。

Bash脚本的变量
变量的定义

基本语法和示例

var=value# 字符串变量
greeting="Hello, World!"# 整数变量(本质上仍是字符串)
cnt=10

定义时注意事项

  • 等号两边不能有空格! 这是初学者最常犯的错误。VAR = “value” 是错误的,Shell 会把 VAR 当成一个命令来执行。
  • 变量名规范:通常由字母、数字和下划线组成,不能以数字开头。习惯上,环境变量和全局变量使用全大写,脚本内部的局部变量使用小写,以作区分。
  • 无需声明类型:Bash 中的变量默认都是字符串类型。即使你赋值一个数字 10,它本质上也是字符串 “10”,但在进行数学运算时,Shell 会自动进行转换。
变量的使用

基本语法和示例:

$变量名 或者 ${变量名}
# 更推荐后者写法name="Alex"
echo "My name is $name"
echo "My name is ${name}"

花括号 {} 是一种更严谨和安全的写法,它可以明确变量名的边界,避免歧义。看下面的例子:

fruit="apple"
echo "I have five ${fruit}s"  # 正确输出: I have five apples
echo "I have five $fruits"    # 错误输出: I have five  (因为 Shell 试图寻找一个叫 fruits 的变量,但它不存在)
变量的类型

在Bash中变量本质上都被视作字符串,所以下面分类依据是作用域和来源。

局部变量

在脚本中直接定义的变量,其作用域默认为整个脚本。如果在函数内部定义,为了防止污染外部作用域,应使用local关键字声明。

#!/bin/bash
name="global"my_func() {local name="local" # 使用 local 关键字,此变量只在函数内有效echo "Function sees name as: $name"
}echo "Before function call, name is: $name"
my_func
echo "After function call, name is: $name" # 外部变量不受函数内部影响# 输出
Before function call, name is: global
Function sees name as: local
After function call, name is: global
环境变量

这些变量对当前 Shell 会话以及由它启动的所有子进程(包括你运行的脚本)都可见。它们通常用于配置系统环境。环境变量可以自己利用report工具定义,也有如下常见的内置环境变量:

环境变量说明
$HOME当前用户的夹目录
$PATH可执行文件的搜索路径
$USER用户名
$PWD当前工作目录
$HOSTNAME系统主机名称
$LANG系统语言和编码
基本内置变量(位置参数)

这些变量由 Shell 自动赋值,用于获取传递给脚本的命令行参数。

  • $0: 脚本本身的文件名。
  • $1, $2, $3…: 分别代表第 1、第 2、第 3 个参数。
  • ${10}, ${11}… 当参数超过 9 个时,必须用花括号 {} 包裹。
  • $#: 传递给脚本的参数总个数。
  • $@: 代表所有参数的列表,每个参数都是独立的字符串。在循环中,“$@” 是最常用和最安全的方式。
  • $*: 代表所有参数组成的单个字符串。
#!/bin/bash
# 用法: ./backup.sh /path/to/source /path/to/destinationSOURCE_DIR=$1
DEST_DIR=$2echo "将从 '$SOURCE_DIR' 备份到 '$DEST_DIR'..."
cp -r "$SOURCE_DIR" "$DEST_DIR"

这里特别注意一下$@与$*的区别:

当不使用双引号时,$@ 和 $* 的表现是一样的。但一旦用双引号包裹,它们的行为就截然不同。

  • “$*”:会将所有参数视为一个整体的字符串。“param1 param2 param3”。
  • “$@”:会将每个参数视为独立的字符串。“param1” “param2” “param3”

例如脚本loop_test.sh:

#!/bin/bash
echo "--- 循环遍历 \"\$*\" (单个字符串) ---"
for arg in "$*"; doecho "参数: $arg"
doneecho ""
echo "--- 循环遍历 \"\$@\" (独立字符串列表) ---"
for arg in "$@"; doecho "参数: $arg"
done## 执行
chmod +x loop_test.sh
./loop_test.sh "first arg" "second" "third"## 结果
--- 循环遍历 "$*" (单个字符串) ---
参数: first arg second third--- 循环遍历 "$@" (独立字符串列表) ---
参数: first arg
参数: second
参数: third

在需要遍历或传递参数列表时,99% 的情况下你都应该使用 “$@”。它能保证参数(即使包含空格)被正确、独立地处理。

特殊内置变量

这些变量由 Shell 预设,用于提供脚本运行状态的信息。

  • $?: 上一个命令的退出状态码。这是脚本中进行错误检查的最重要变量。0代表成功。非0代表失败。
  • $$:当前脚本的进程ID。常用于创建唯一的临时文件名。
  • $!: 上一个在后台运行的命令的进程 ID。
cp source.txt dest.txt
if [ $? -eq 0 ]; thenecho "文件复制成功!"
elseecho "文件复制失败!"
fi
Bash变量的三个特殊机制
数组

Bash 支持一维数组来存储一组值。

  • 定义数组: array=(“apple” “banana” “cherry”)
  • 获取元素: ${array[0]} (获取第一个元素,索引从0开始)
  • 获取所有元素: ${array[@]}
  • 获取数组长度: ${#array[@]}
SERVERS=("server1.com" "server2.com" "server3.com")for server in "${SERVERS[@]}"; doecho "正在 ping 服务器: $server"ping -c 1 "$server"
done
命令替换 (Command Substitution)

这是一种非常强大的机制,允许你将一个命令的输出结果赋值给一个变量。
基本语法和示例:

var=$(command) 或者 VAR=`command`# 获取当前日期
TODAY=$(date +%Y-%m-%d)
echo "今天是: $TODAY"# 获取 txt 文件数量
FILE_COUNT=$(find . -type f -name "*.txt" | wc -l)
echo "这里有 $FILE_COUNT 个 txt 文件。"

推荐使用()而非``,因为使用 $(…) 的好处是它可以轻松地嵌套,而反引号`嵌套起来非常麻烦和混乱。

变量扩展(参数拓展)

Bash 提供了强大的参数扩展功能,可以在不使用外部命令的情况下对变量进行处理。

  • 提供默认值(提升代码健壮性)

    • ${variable:-default}: 如果 variable 未定义或为空,则返回 default,但不改变 variable 本身的值。
    • ${variable:=default}: 如果 variable 未定义或为空,则返回 default,并同时将 default 赋值给 variable。
  • 字符串操作

    • ${#variable}: 获取变量值的长度。
    • ${variable#pattern}: 从开头删除最短匹配 pattern 的部分。
    • ${variable##pattern}: 从开头删除最长匹配 pattern 的部分。
    • ${variable%pattern}: 从结尾删除最短匹配 pattern 的部分。
    • ${variable%%pattern}: 从结尾删除最长匹配 pattern 的部分。
    • ${variable/pattern/string}: 替换第一个匹配的 pattern。
    • ${variable//pattern/string}: 替换所有匹配的 pattern。
# 默认值
echo "未定义的用户: ${UNSET_USER:-guest}" # 输出:未定义的用户: guest# 字符串操作
FILEPATH="/home/user/document.txt"
echo "文件名: ${FILEPATH##*/}" # 输出:document.txt (删除最长的前缀 */)
echo "目录名: ${FILEPATH%/*}"  # 输出:/home/user (删除最短的后缀 /*)
echo "扩展名: ${FILEPATH##*.}" # 输出:txt
变量使用时的注意事项
  • 永远用双引号包裹变量:使用$variable 或 ${variable} 时,请始终用双引号包裹,即 “$variable”。这可以防止当变量值包含空格或特殊字符时,发生意想不到的“分词”和“文件名扩展”问题。这是编写健壮脚本的黄金法则。
  • 赋值时等号两边无空格:再次强调,var=“value” 是正确的,其他形式都是错误的。
  • 优先使用 ${}:使用 ${variable} 的形式可以使代码更清晰,并避免变量名边界的歧义。
  • 检查命令成功与否:在执行一个重要命令后,检查 $? 的值,以确保脚本在出错时能妥善处理,而不是继续盲目执行。
  • 区分环境变量和局部变量:使用大写命名环境变量,使用小写命名脚本内部的变量,可以提高代码的可读性。在函数内部尽量使用 local 关键字。
Bash脚本的条件结构
test命令
test的基本功能

test 是一个标准的、内建于 Shell 的命令。它的核心用途只有一个:计算一个表达式的真伪。

强调一下是表达式的真伪而非命令执行的情况

  • 如果表达式为真 (true),test 命令执行成功,并返回退出状态码 0。
  • 如果表达式为假 (false),test 命令执行失败,并返回一个非 0 的退出状态码(通常是 1)。

它的输出不是文本"true"或"false",而是通过退出状态码 ($?) 来报告结果。这正是if语句所需要的判断依据。

# 检查文件 /etc/hosts 是否存在 (这个文件通常存在)
test -f /etc/hosts
echo $?  # 输出: 0 (代表 true)# 检查一个不存在的文件
test -f /nonexistent/file
echo $?  # 输出: 1 (代表 false)
test指令的三种形式
  1. test expression (经典形式)
    这是 test 命令最原始的形态,现在已不常用,但了解它有助于理解本质。
if test -f "/etc/hosts"; thenecho "文件存在。"
fi
  1. [ expression ] (POSIX 标准形式)
    是test命令的等效写法,一个方括号 [。它在功能上与 test 完全等价。
if [ -f "/etc/hosts" ]; thenecho "文件存在。"
fi

注意:

  • [ 实际上是一个命令的名称,和 test 一样。

  • [ 后面和 ] 前面必须有空格! 这是强制性的语法要求。可以理解为 [ 文件存在吗 /etc/hosts ],每个部分都是独立的参数。[ -f …] 是错误的。

  1. [[ expression ]] (Bash 扩展形式,推荐使用)
    在编写 Bash 脚本时,强烈推荐使用它。它解决了 [ 的一些不足。
if [[ -f "/etc/hosts" ]]; thenecho "文件存在。"
fi

[[ … ]] 相比于 [ … ] 的优势:

  • 更安全的变量处理:在 [[ … ]] 中,即使变量包含空格或为空,不加双引号通常也不会导致错误。而在 [ … ] 中,不加双引号的变量是导致脚本错误的常见原因。尽管如此,为变量加上双引号永远是最佳实践。
  • 更直观的逻辑运算:可以直接使用 && (与), || (或),而不是 -a, -o。
  • 支持模式匹配和正则:可以用 == 和 != 进行通配符匹配(globbing),用 =~ 进行正则表达式匹配。
test的常用测试表达式
  1. 文件测试
    这是脚本中最常见的测试类型,用于检查文件或目录的状态。
表达式描述
-e <路径>exists - 路径是否存在(文件或目录均可)。
-f <路径>file - 路径是否存在且为一个普通文件。
-d <路径>directory - 路径是否存在且为一个目录。
-s <路径>size - 文件是否存在且大小不为零。
-r <路径>readable - 文件是否存在且当前用户可读。
-w <路径>writable - 文件是否存在且当前用户可写。
-x <路径>xecutable - 文件是否存在且当前用户可执行。
CONFIG_FILE="/etc/myapp.conf"if [[ -f "$CONFIG_FILE" && -r "$CONFIG_FILE" ]]; thenecho "正在读取配置文件..."# ... 读取文件内容 ...
elseecho "错误:配置文件不存在或不可读!" >&2exit 1
fi
  1. 字符串测试
表达式描述
$str1" = “$str2”字符串内容是否相等。(== 在 [ 和 [[ 中效果相同)
“$str1” != “$str2”字符串内容是否不相等。
-z “$str”zero length - 字符串长度是否为零(即是否为空)。
-n “$str”non-zero length - 字符串长度是否不为零(即是否非空)。
read -p "请输入您的名字: " user_nameif [[ -z "$user_name" ]]; thenecho "错误:名字不能为空!"
elseecho "你好, $user_name!"
fi
  1. 整数测试

用于比较整数大小时,必须使用下面的专属操作符。

表达式描述
$int1 -eq $int2equal - 是否相等。
$int1 -ne $int2not equal - 是否不相等。
$int1 -gt $int2greater than - 是否大于。
$int1 -ge $int2greater than or equal - 是否大于等于。
$int1 -lt $int2less than - 是否小于。
$int1 -le $int2less than or equal - 是否小于等于。
# 检查传入脚本的参数数量是否为 2
if [ "$#" -ne 2 ]; thenecho "用法: $0 <源文件> <目标文件>"exit 1
fi
组合条件
  1. 在 [ … ] 中(不推荐)
  • -a: and (与)
  • -o: or (或)
  • !: not (非)
# 判断 num 是否在 0 到 100 之间
if [ "$num" -ge 0 -a "$num" -le 100 ]; thenecho "有效范围"
fi
  1. 在 [[ … ]] 中(推荐)
  • &&: and (与)
  • ||: or (或)
  • !: not (非)
# 同样是判断 num 是否在 0 到 100 之间,可读性更高
if [[ "$num" -ge 0 && "$num" -le 100 ]]; thenecho "有效范围"
fi
test的使用基本注意事项
  • 优先使用 [[ … ]]:在编写 Bash 脚本时,只要不需要考虑兼容古老的 sh,就应该优先使用 [[ … ]],它更健壮、功能更强、语法更友好。
  • 永远用双引号包裹变量:在进行字符串比较或文件测试时,务必将变量用双引号 “” 包裹起来,即 [[ -f “file"]]和[["file" ]] 和 [[ "file"]][["str1” == “$str2” ]]。这可以防止当变量为空或包含空格时,test 命令解析出错。
  • 区分字符串和整数比较:比较字符串用 == 或 =,比较整数用 -eq, -gt 等。混用会导致意想不到的结果。
  • test的结果是状态码:牢记 test 命令本身不产生任何可见输出,它的价值在于其退出状态码 $?,if 和 while 语句正是依赖这个状态码来工作的。
let命令
let的基本功能

在 Bash Shell 中,变量默认是被当作字符串来处理的。这意味着你不能直接用常规的方式进行数学计算。let 命令的核心作用就是告诉Bash把接下来的表达式当作一个或多个整数算术运算来处理,而不是当作字符串。

let的基本语法结构和示例:

let <算术表达式1> [算术表达式2] ...a=10
b=5let result=a+b   # 加法
echo $result   # 输出: 15
let result=a-b   # 减法
echo $result   # 输出: 5
let result=a*b   # 乘法
echo $result   # 输出: 50
let result=a/b   # 除法 (整数除法)
echo $result   # 输出: 2
let result=a%b   # 取余
echo $result   # 输出: 0

let表达式有一个突出的优点,那就是变脸引用可以不加上$:

a=10
b=5
let result=a+b     # 推荐,更简洁
let result=$a+$b   # 也可以,效果相同

但let也有一个突出的缺点,如果你的表达式中包含空格,必须用引号将整个表达式引起来。

# 错误写法,Shell 会把 =、5、+、3 当成多个独立参数
let x = 5 + 3 # 正确写法
let "x = 5 + 3"
echo $x # 输出: 8
let支持的运算符
  • 赋值:=
  • 算术:+ (加), - (减), * (乘), / (除), % (取余), ** (幂运算)
  • 自增/自减:var++ (后增), var-- (后减), ++var (前增), --var (前减)
  • 复合赋值:+=, -=, *=, /=, %=
  • 位运算:<< (左移), >> (右移), & (按位与), | (按位或), ^ (按位异或), ~ (按位非)
  • 逻辑运算:! (逻辑非), && (逻辑与), || (逻辑或), , (逗号,用于分隔多个表达式)
  • 三元运算符:?: (例如 let “a = 1 > 0 ? 1 : 0” )
let的改进写法:双括号算术运算 ((…))

这是目前最推荐的进行整数运算的方式。它是一个命令,也可作为一个表达式

  • 作为命令,进行赋值:(( 算术表达式 ))
  • 作为表达式,获取结果:$(( 算术表达式 ))
a=10
b=5# 1. 赋值
(( c = a + b * 2 ))
echo $c  # 输出: 20# 2. 获取结果
result=$(( a + b ))
echo $result # 输出: 15# 3. 在 if 语句中直接使用
if (( a > b )); thenecho "a 大于 b"
fi

注意:

  • ((…))相比于let的巨大优势:表达式内可以随意使用空格,不需要引号;
  • 变量引用无需$:和let 一样,内部的变量无需 $ 前缀。
  • 使用((…))处理整数判断,进而替代[[…]]的一部分功能,使用起来更接近C语言,可以直接用于流程控制:if 和 while 语句可以直接使用双括号进行判断。
if-then结构

首先还是要强调if语句的核心本质:检查命令的退出状态码。在很多编程语言(如 Python,Java)中,if 后面跟的是一个布尔值(true/false)。但在 Bash 中,if 的工作方式有本质不同:if 后面跟的是一个或多个 命令,它检查的是这些命令执行后的 退出状态码 (Exit Status)。

# -q 选项让 grep "安静"执行,不输出任何内容,只通过退出状态码报告结果
if grep -q "root" /etc/passwd; thenecho "文件中找到了 'root' 用户。"
fi
if的三种结构
  1. 单分支
if <命令或条件>;then<要执行的代码块>
fi# 使用我们之前学过的 [[ ... ]] 作为判断条件
if [[ -d "/var/log" ]]; thenecho "/var/log 是一个目录。"
fi
  1. 双分支
if <命令或条件>;then<条件为真时执行的代码块>
else<条件为假时执行的代码块>
fiif [[ "$USER" == "root" ]]; thenecho "当前用户是 root,拥有最高权限。"
elseecho "当前用户是 $USER,一个普通用户。"
fi
  1. 多分支
if <条件1>;then<条件1为真时执行的代码块>
elif <条件2>;then<条件2为真时执行的代码块>
elif <条件3>;then<条件3为真时执行的代码块>
...
else<所有条件都为假时执行的代码块>
firead -p "请输入你的分数 (0-100): " score
if (( score >= 90 )); thenecho "优秀 (A)"
elif (( score >= 80 )); thenecho "良好 (B)"
elif (( score >= 60 )); thenecho "及格 (C)"
elseecho "不及格 (F)"
fi
if的判断条件类型

if后面可以跟任何“命令”,但最常用的是以下三种:

  1. 使用 test, [] 或 [[ … ]]
  • 文件测试: if [[ -f “$file” ]]
  • 字符串测试: if [[ “str1"=="str1" == "str1"=="str2” ]]
  • 整数测试: if [[ “num1"−gt"num1" -gt "num1"gt"num2” ]]

强烈推荐在 Bash 脚本中使用 [[ … ]],因为它更健壮、功能更强。

  1. 使用算术运算 ((…))
    双括号 ((…)) 用于整数算术运算。它同样会返回一个退出状态码,这使得它可以非常直观地用于 if 语句中的数字比较。
count=10
if (( count > 5 )); thenecho "count 大于 5。"
fi# 注意:(( 0 )) 会被认为是 false
if (( 0 )); thenecho "这句不会被打印"
elseecho "0 在 if 中被视为 false"
fi
  1. 使用任意普通命令
# 检查网络连通性
if ping -c 1 -W 1 "google.com" &> /dev/null; thenecho "网络连接正常。"
elseecho "无法连接到外部网络。"
fi
# &> /dev/null 的作用是将 ping 的所有输出(标准和错误)都丢弃,我们只关心它的退出状态码。
if的一些使用注意
  • 优先使用 [[…]] 和 ((…)):在编写 Bash 脚本时:进行文件和字符串判断,优先使用 [[ … ]]。进行整数判断,优先使用 (( … ))。
  • 变量引用要加双引号 (“$var”):在 [[ … ]] 和特别是 [ … ] 中,对变量进行引用时,务必加上双引号。这能防止当变量值包含空格或为空时,产生意想不到的错误。
case结构

和if的高度灵活不同,case语句是根据变量的不同取值来选择分支的。

case的基本结构
case <变量> in<模式1>)<命令块1>;;<模式2>)<命令块2>;;<模式3> | <模式4>) # 模式3或模式4<命令块3>;;*)<默认命令块>;;
esac
  • case <变量> in: 语句的开始。<变量> 通常是一个变量的引用,如 $action。in 是关键字。
  • <模式n>): 每一个分支的匹配模式。模式后面的 ) 是必需的。
  • <命令块n>: 如果变量的值匹配了该模式,则执行这里的命令。
  • ;; (双分号): 这是 case 语句中至关重要的部分。它标志着一个命令块的结束,其作用类似于其他语言中 switch 的 break。执行完命令块后,遇到 ;; 就会直接跳到 esac,结束整个 case 语句。
  • *): 这是一个特殊的通配符模式,可以匹配任何没有被前面模式捕获到的值。它通常作为默认分支,放在最后。
  • esac: case 语句的结束标志 (case 的反写)。

case的强大之处:它天然支持通配符匹配,灵活性很强:

  • *: 匹配任意长度的任意字符序列。
  • ?: 匹配任意单个字符。
  • […]: 匹配方括号中任意一个字符。例如 [0-9] 匹配任意数字,[a-z] 匹配任意小写字母。
  • |: 或操作符,用于在一个分支中匹配多个模式

举一些例子:

read -p "请输入一个字符: " char
case "$char" in[a-z])echo "你输入了一个小写字母。";;[A-Z])echo "你输入了一个大写字母。";;[0-9])echo "你输入了一个数字。";;?)echo "你输入了一个特殊符号。";;*)echo "你输入了多个字符。";;
esac
#!/bin/bash
# 用法: ./classify_file.sh document.pdffilename="$1"case "$filename" in*.jpg | *.jpeg | *.png | *.gif)echo "'$filename' 是一个图片文件。";;*.tar.gz | *.zip | *.rar)echo "'$filename' 是一个压缩包文件。";;*.sh)echo "'$filename' 是一个 Shell 脚本文件。";;*)echo "无法识别 '$filename' 的文件类型或它没有扩展名。";;
esac
case使用的一些注意事项
  • 别忘了 ;;:这是初学者最容易犯的错误。忘记写 ;; 会导致“穿透”(fall-through),即匹配成功后,脚本会继续执行下一个分支的命令块,直到遇到 ;; 或 esac 为止。这种行为在绝大多数情况下都不是我们想要的,所以请务必在每个命令块的末尾加上 ;;。
  • 引用变量 (case “$var” in):在 case 语句中,总是用双引号把要测试的变量包起来。这可以防止当变量为空或包含特殊字符时出现问题。
  • *) 默认分支的重要性:强烈建议总是提供一个 *) 默认分支。这能让你的脚本更健壮,可以优雅地处理所有未预料到的输入,而不是静默失败或产生错误。
  • 模式的顺序:case 语句会从上到下依次匹配,一旦找到第一个匹配的模式,就会执行对应的代码块然后跳出。因此,应该将更具体的模式放在前面,更通用的模式(如 *)放在后面。
&&和||

我们知道&&和||可以在[[]]中用作逻辑判断,但如果单独使用它们也可以起到命令短路的作用。和if一样,&&和||也是根据命令执行后的状态码来工作的:
基本语法:

command1 && command2 # 只有当command1成功执行(退出状态码为0)时,command2 才会被执行。
command1 || command2 # 只有当command1执行失败(退出状态码为1)时,command2 才会被执行。

例如:

# 如果 mkdir 由于权限等问题失败,cd 就不会执行,避免了进入错误目录的风险
mkdir -p /var/data/new_project && cd /var/data/new_project# 尝试 ping 谷歌,如果失败(比如没网),就打印错误信息
ping -c 1 google.com &> /dev/null || echo "错误:网络连接中断。"

二者还可以组合起来用,实现简单的 if-then-else 逻辑:

command1 && command2 || command3# 执行逻辑:
# 执行 command1。
# 如果 command1 成功 (0),则执行 command2。 
# 如果 command1 失败 (非 0),则执行 command3。

但是,这种组合不完全等价于if-else。因为它的行为依赖于 command2 的退出状态。看这个例子: true && false || echo “This gets printed”

true 执行成功。于是 && 后面的 false 被执行。false 命令执行失败(退出状态码为1)。于是 || 后面的 echo 被执行了!这通常不是我们想要的结果。在 if/else 中,如果 true 成立,else 部分是绝对不会执行的。

对于复杂的逻辑判断,使用标准的 if/then/else/fi 结构更清晰、更安全。

Bash脚本的循环结构
for循环
for循环的两种基本结构

Bash 中的 for 循环主要有两种风格:一种是传统的列表式循环,另一种是类似 C 语言的数值计算循环。

  1. 形式一:列表式for循环。
    这是Shell脚本中最经典、最常用、最灵活的for循环形式。
for <变量名> in <项目列表>;do<循环体代码,使用 $变量名>
done

这样形式的for循环使用起来灵活多变,以下是项目列表的多种形式

# 显式列表(或数组)
for color in "red" "green" "blue"
doecho "当前颜色是: $color"
done# 范围序列(花括号扩展)
for i in {1..10..2}
doecho "奇数: $i"打印 1 3 5 7 9
done# 文件名通配符 —> 这是极其常用的方式,用于处理当前目录下的文件。
for file in *.txt
do# 引用变量时一定要加双引号,以防文件名包含空格mv "$file" "${file}.bak"echo "已将 '$file' 重命名为 '${file}.bak'"
done# 命令替换
for user in $(cat user_list.txt)
doecho "正在为用户 $user 创建家目录..."
done
# 注意:这种方式对于包含空格或特殊字符的输出处理不佳,容易出错。对于逐行读取文件内容,更健壮的方法是使用 while read 循环。# 脚本参数 ("$@")
echo "你传入了 $# 个参数,它们是:"
for arg in "$@"
doecho "参数: $arg"
done
  1. 形式二:C语言风格for循环。
    这种形式对于有其他编程语言背景的开发者来说非常熟悉,主要用于数值计算和控制循环次数。
for (( 初始化; 条件; 迭代表达式 ));do<循环体代码>
done

举个例子:

# 计算 1 到 100 的和
sum=0
for (( i=1; i<=100; i++ ))
do(( sum += i ))
done
echo "1 到 100 的总和是: $sum"

这种写法语法直观,类似C。并且在双括号 ((…)) 内,变量引用可以不加$。

for循环的注意事项
  • 引用循环变量时加双引号:这是最重要的规则之一。在循环体中使用循环变量时,请务必用双引号包裹,如 “$item”。这能防止因项目内容包含空格或特殊字符而导致命令执行失败。
  • 避免 for file in $(ls) 的写法:这是一个非常经典的反面教材。ls 的输出格式不稳定,且当文件名包含空格时,$(ls) 的结果会被“分词”,导致循环出错。直接使用文件名通配符 for file in * 是更安全、更高效的做法。
while循环

while 循环用于只要某个条件持续为真(命令返回退出状态码 0),就重复执行一段代码块。

while的基本语法:
while <条件命令> ;do<循环体代码>
done

while 的条件命令和if非常像,<条件命令> 最常见的形式是 [[ … ]](测试条件)和 (( … ))(算术条件),但也可以是任何能返回退出状态码的命令。

while的常见用法
  1. 逐行读取文件(王牌用法)
while IFS= read -r line
doecho "正在处理行: $line"
done < "filename.txt"

说明:

  • read 是一个内建命令,用于从标准输入读取一行数据。当它成功读到一行时,返回退出状态码 0;当它读到文件末尾(EOF)时,返回非 0。这恰好完美契合 while 循环的机制。
  • -r: (raw read)选项,防止 read 命令对反斜杠 \进行转义。在处理文件路径或包含特殊字符的行时,强烈建议总是加上 -r。
  • line: 你定义的变量名,read 命令会将读到的整行内容(除了行尾的换行符)赋值给这个变量。
  • IFS 是“内部字段分隔符”的缩写,Shell 默认用它来分割单词(默认为空格、制表符、换行符)。在read命令前加上 IFS=(将其临时设置为空),可以防止 read 命令修剪掉行首行尾的空格和制表符,保证读到的 line 变量内容与文件中的行内容完全一致。这也是一个最佳实践。
  • < “filename.txt”:这是输入重定向。它将 filename.txt 文件的内容作为整个 while 循环的标准输入。这样,read 命令就能从这个文件中逐行读取数据。
# servers.list文件如下
google.com
github.com
# local.server
192.168.1.1#!/bin/bash
while IFS= read -r server
do# 跳过空行和注释行if [[ -z "$server" || "$server" == \#* ]]; thencontinuefiecho "--- 正在检查服务器: $server ---"ping -c 1 "$server"
done < "servers.list"

特别注意:不能使用管道代替重定向cat file.txt | while …; do …; done。使用管道时,| 右边的 while 循环会在一个新的子 Shell中执行。这意味着在循环内部对变量做的任何修改,在循环结束后都会丢失。

  1. 无限循环 ->用于需要持续运行的服务、监控或主菜单程序。
# 使用 true 命令,它永远返回 0
while true
doecho "系统正在运行... 按 [CTRL+C] 退出。"# ... 执行监控任务 ...sleep 5
done
while的注意事项
  • 避免死循环:确保你的循环体内部有逻辑能最终改变 while 的判断条件,使其变为假,除非你有意编写无限循环。
until循环

until循环就是while循环的反意词:until 循环会只要某个条件持续为假(命令返回非 0 退出状态码),就重复执行一段代码块。当条件第一次变为真(命令返回退出状态码 0)时,循环就会停止。

until的基本语法:

until <条件命令>;do<循环体代码>
done

其等效为:

while !<条件命令>;do<循环体代码>
done

! 是一个逻辑非操作符,它会反转后面命令的退出状态码(0 变成 1,非 0 变成 0)。

循环控制指令

和C语言一样,Bash也使用continue和break控制循环,并且功能类似。

break

基本语法:

break [n]

[n] 是一个可选的整数参数,代表要跳出的循环层数。如果省略,n 默认为 1,即跳出当前最内层的循环。

示例:

#!/bin/bash
TARGET_FILE="sshd_config"echo "开始在 /etc/ 目录中搜索文件 '$TARGET_FILE'..."for file in /etc/*
do# basename 命令用于提取路径中的文件名部分if [[ "$(basename "$file")" == "$TARGET_FILE" ]]; thenecho ""echo "找到了!文件路径是: $file"break # 任务完成,立即跳出 for 循环fi# -n 让 echo 不换行,制造一个动态的搜索效果echo -n "."sleep 0.05
doneecho ""
echo "搜索过程结束。"
continue

基本语法:

continue [n]

[n] 同样是可选参数,代表要“继续”的是第几层循环。如果省略,n 默认为 1,即作用于当前最内层的循环。

#!/bin/bash
sum=0for i in {1..10}
do# (( i % 2 != 0 )) 判断是否为奇数if (( i % 2 != 0 )); then# 如果是奇数,就跳过本次循环的剩余部分(即下面的 sum+=i)# 直接开始下一次循环(i会变成下一个值)continuefiecho "将偶数 $i 加入总和。"(( sum += i ))
doneecho "1到10之间所有偶数的和是: $sum"

注意:

  • 对于 while true 或 until false 这样的无限循环,break 是唯一的程序化退出方式(不包括 exit 或 kill)。必须在循环体内设计一个或多个 if 条件来触发 break,否则循环将永不停止。
Bash脚本的函数
函数的定义

Bash 中,定义函数有两种常见的方式,它们在功能上是等价的。

函数名() {<命令块>
}# 或者function 函数名 {<命令块>
}

在脚本中,函数必须先被定义,然后才能被调用。Shell 是自上而下解释执行的,它需要先“学习”到函数的存在,之后才能执行它。

函数的调用

直接使用函数名即可:

#!/bin/bash
# 定义一个问候函数
greet() {echo "你好, 欢迎来到 Bash 函数的世界!"
}echo "准备调用函数..."
greet # 调用 greet 函数
echo "函数调用完毕。"
函数的传参

函数内部处理参数的方式与脚本处理位置参数的方式完全相同。
例如:

  • $1, $2, $3…: 代表传递给函数的第 1、2、3 个参数。
  • $#: 代表传递给函数的参数总数。
  • $@: 代表传递给函数的所有参数列表。
# 定义一个可以向特定人问好的函数
greet_someone() {# $1 指的是传递给 greet_someone 的第一个参数echo "你好, $1! 祝你有美好的一天。"
}# 调用函数并传递参数
greet_someone "Alice"
greet_someone "Bob"
函数变量的作用域
  1. 局部变量
    在 Bash 中,默认情况下,你在任何地方(包括函数内部)定义的变量都是全局变量。想要使用局部变量需要local关键字,local 声明的变量只在当前函数的作用域内有效,函数执行完毕后,该变量就会被销毁。
#!/bin/bash
count=100 # 全局变量update_count() {local count=10 # 这是一个全新的、只属于函数的局部变量(( count++ ))echo "函数内的 count: $count"
}echo "调用前, 全局 count: $count"
update_count
echo "调用后, 全局 count: $count" # 全局变量并未被修改# 输出
调用前, 全局 count: 100
函数内的 count: 11
调用后, 全局 count: 100
  1. 引用变量
    默认情况下,我们输入给函数的参数在执行后不会影响原来的参数的,想要实现C语言中参数引用的效果,可以使用local -n关键字。
#/bin/bash
modify_var() {local -n ref=$1  # 引用变量名ref="新值"
}
value="旧值"
modify_var value
echo "$value"  # 输出: 新值# 输出
新值 # 不加上-n输出是"旧值"
函数的执行结果
  1. 返回状态
    return 命令用于设置函数的退出状态码 ($?),它和普通命令的退出码作用一样,用于表示函数执行的成功或失败。
is_file_exist() {if [[ -f "$1" ]]; thenreturn 0 # 文件存在,返回成功elsereturn 1 # 文件不存在,返回失败fi
}# 使用 if 来捕获函数的退出状态
if is_file_exist "/etc/hosts"; thenecho "状态检查成功:文件 /etc/hosts 存在。"
elseecho "状态检查失败:文件 /etc/hosts 不存在。"
fi
  1. 返回数据:使用 echo 和命令替换
    Shell 函数不能像其他语言那样直接返回一个字符串、数组或其他复杂数据。
    要从函数中获取数据(比如一个计算结果或处理过的字符串),标准做法是:
    在函数中,使用 echo 将结果打印到标准输出;在调用函数的地方,使用命令替换 $(…) 来捕获这个输出,并赋值给一个变量。
get_timestamp() {# 将 date 命令的结果打印到标准输出echo "$(date +%Y-%m-%d_%H:%M:%S)"
}# 使用命令替换来“接收”函数的返回值
log_prefix=$(get_timestamp)
echo "[$log_prefix] 应用程序已启动。"## 输出
[2025-07-16_17:11:05] 应用程序已启动。
总结函数的注意事项
  • 先定义后调用:始终确保在调用函数之前,它已经被 Shell 读取和定义。通常的做法是将所有函数定义放在脚本的开头。
  • 函数内部多用 local:这是编写高质量函数的黄金法则。除非你刻意要修改一个全局状态,否则函数内的所有变量都应该用 local 声明。
  • return 用于状态,echo 用于数据:清晰地区分这两种“返回”方式。不要试图用 return 来返回一个字符串(例如 return “some string” 是无效的)。
  • 保持函数单一职责:一个好的函数应该只做一件事,并把它做好。这使得函数更容易理解、测试和复用。
  • 传递参数使用 “@":如果需要将脚本收到的所有参数原封不动地传递给一个函数,请使用myfunction"@":如果需要将脚本收到的所有参数原封不动地传递给一个函数,请使用 my_function "@":如果需要将脚本收到的所有参数原封不动地传递给一个函数,请使用myfunction"@”。
Bash脚本的Debug
语法检查
  • 使用shellcheck工具对Bash脚本进行语法检测
脚本逻辑的debug
  • 注释大法,把可疑的问题代码注释一下确定问题来源
  • 打印大法,对关键变量echo一下
  • 把输出重定向到log文件中处理
  • 借助调试命令set、trap、strace。
  1. set命令
    set 主要用来开启“严格模式”和“追踪模式”,让脚本在出错时立即失败,或者清晰地展示出每一行代码的执行情况。可以在脚本的任何地方使用 set -<选项> 来开启,用 set +<选项> 来关闭。
  • set -e :错误退出
    作用:当脚本中的任何命令执行后返回非 0 的退出状态码时(即执行失败),立即退出整个脚本。这能防止错误像滚雪球一样越滚越大。例如,如果 cd到一个不存在的目录失败了,后续的 rm -rf * 就不会在错误的目录下执行,避免了灾难。
#!/bin/bash
set -eecho "准备创建一个不存在的目录..."
ls /nonexistent_directory # 这个命令会失败# 因为 set -e,下面这行代码将永远不会被执行
echo "脚本执行完毕。" 

在某些情况下,set -e 不会立即退出,例如在 if 或 while 的条件判断中,或者命令后面跟着 &&, || 时,因为 Shell 认为这些错误已经被“处理”了。

  • set -u:未定义变量视为错误
    当脚本尝试使用一个未被定义的变量时,视为错误并立即退出。这能帮你捕捉到变量名的拼写错误。
#!/bin/bash
set -uMY_NAME="Alex"
# 假设手误,将 MY_NAME 写成了 MY_NAM
echo "你好, $MY_NAM" # 这行会触发错误,脚本退出
  • set -x:执行追踪
    这是最常用的调试工具。在执行每一行命令之前,Shell 会先把它打印到标准错误输出,并且是变量和通配符都已展开后的最终样子。你可以清晰地看到每个变量在执行时的实际值,以及命令最终是以何种形态被执行的。
#!/bin/bash
set -x # 开启追踪USER_COUNT=$(who | wc -l)
echo "当前有 $USER_COUNT 个用户登录。"set +x # 关闭追踪
echo "追踪已关闭。"
  • set -o pipefail:管道失败
    在管道命令中(如 cmd1 | cmd2),只要有任何一个命令失败,整个管道的退出状态码就是失败(非 0)。默认情况下,只有最后一个命令的退出码才算数。保证了管道中任何一步的失败都能被捕获到。

这些选项可以组合在一起使用,set -euxo pipefail 是一个非常流行的“非官方严格模式”的开头。

  1. trap 命令
    介绍trap前需要先讲一下Linux的信号。信号是操作系统与进程之间通信的一种方式。对于 trap,最关心的几个信号是:
  • EXIT: 脚本退出时触发(无论正常退出、出错退出还是被中断)。这是最适合用于清理工作的信号。
  • ERR: 当任何命令执行失败(返回非 0 状态码)时触发(set -e 可能会影响其行为)。
  • INT: 当用户按下 Ctrl+C 中断脚本时触发。
  • TERM: 当脚本被 kill 命令(默认信号)终止时触发。

trap的语法和示例

trap '<要执行的命令或函数>' <信号1> [<信号2> ...]#!/bin/bash
# 创建一个临时文件,文件名包含进程ID以保证唯一性
TEMP_FILE="/tmp/my_app_temp_$$"
touch "$TEMP_FILE"# 定义一个清理函数
cleanup() {echo "捕获到退出信号,正在执行清理..."rm -f "$TEMP_FILE"echo "临时文件 '$TEMP_FILE' 已删除。"
}# 设置 trap:无论脚本如何退出(正常、出错、被中断),都调用 cleanup 函数
trap cleanup EXITecho "脚本正在执行,临时文件是 $TEMP_FILE"
echo "你可以尝试用 Ctrl+C 中断此脚本来测试 trap。"
sleep 20
echo "脚本正常结束。"# 当你运行这个脚本,无论你是等它自然结束,还是中途按 Ctrl+C,最后的清理函数总会被调用。

如果你想让脚本在运行时不被 Ctrl+C 中断,可以设置一个空的 trap。:

trap '' INT
echo "你无法用 Ctrl+C 中断我..."
sleep 10

注意:trap 的位置:trap 命令应该放在脚本的开头部分,以确保它能尽早生效。

  1. strace命令(初学者不推荐)
    strace 用于追踪一个程序在运行期间发生的所有系统调用和接收到的信号。
其他

其实还有一些交互式的debug工具,由于我们写的脚本一般比较简单,这里先略过。

习题部分

题目一:

ls -l -a -h -t --color=yes

题目二:
首先说明一下我的工作目录:/home/michael/Documents/YSYX/Yuxuexi/StudyProj/2/homework

以下是我的脚本:

## macro.sh#/bin/bash
echo "$PWD" > ~/Documents/YSYX/Yuxuexi/StudyProj/2/homework/macro_dst.txt # 保存当前路径## polo.sh#!/bin/bash
cat ~/Documents/YSYX/Yuxuexi/StudyProj/2/homework/macro_dst.txt # 先显示一下保存的路径
cd "$(cat ~/Documents/YSYX/Yuxuexi/StudyProj/2/homework/macro_dst.txt)" # 载入保存的路径

在运行脚本的时候也需要注意:

export PATH="$PATH:$PWD" # 在工作目录下运行
macro.sh # 在任意目录下运行,保存路径
source polo.sh # 在任意目录下运行,并且使用source在当前shell中执行

值得说明的是答案的思路,它直接使用了source,并在marco下编写这样的脚本:

!/bin/bashmarco(){echo "$(pwd)" > $HOME/Documents/YSYX/Yuxuexi/StudyProj/2/homework/answer/marco_history.logecho "save  pwd $(pwd)"
}polo(){cd "$(cat "$HOME/Documents/YSYX/Yuxuexi/StudyProj/2/homework/answer/marco_history.log")"
}

这样使用时也很方便。我的方法麻烦地多,主要是因为我之前不知道source的机制,以为source只会让脚本在当前shell执行,其实source还有其他功能: ​让脚本中的变量、函数或环境变更直接影响当前 Shell 会话​​。

题目三
我先将题干的代码放在一个debug.sh的脚本中,然后在同一个目录下写下如下dedebug.sh的脚本:

#!/bin/bashi=0
echo "" > debug.txt #清空文件
while true; do(( i++ ))echo "***第${i}次执行***" >>  debug.txt./debug.sh >> debug.txt 2>> debug.txtif [[ $? -eq 0 ]]; then #执行成功echo "现在共运行${i}次"elseecho "在第${i}次运行出现错误,现在打印报告"cat "./debug.txt"breakfi
done

题目四

find -name "*.html" -print0 | xargs -0 tar -cf html.tar
  • find自带递归
  • print0用来防止文件中的空格
  • xargs用来讲输出的文件转成路径参数给tar
  • -0是配合-print0使用的
  • -cf是用来产生.tar的常用命令

上面实现的效果其实有一些不足,就是它会把包好html文件的文件夹也一起压缩,不是纯粹的提取html文件

题目五
这一题没太读懂,想着不是使用ls -R -l -t就可以按时间列出文件了吗,后来明白这一题只能输出一行,并且只能是文件(不包含文件夹),看了答案如下:

find -type f -print0 | xargs -0 ls -l -t |head -1

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/89855.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/89855.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

教育科技内容平台的用户定位与产品方案:从需求到解决方案的精准匹配

教育科技内容平台的用户定位与产品方案&#xff1a;从需求到解决方案的精准匹配打造一款成功的内容平台&#xff0c;核心在于 “懂用户”—— 明确不同用户的需求场景、使用目的&#xff0c;才能设计出真正有价值的产品功能。本文以面向互联网从业者的教育科技内容平台为例&…

网络之路16:认识虚拟化环境H3C CAS

正文共&#xff1a;3888 字 54 图&#xff0c;预估阅读时间&#xff1a;6 分钟目录网络之路第一章&#xff1a;Windows系统中的网络0、序言1、Windows系统中的网络 1.1、桌面中的网卡 1.2、命令行中的网卡 1.3、路由表 1.4、家用路由器网络之路第二章&#xff1a;认识企业设备2…

Sklearn 机器学习 IRIS数据 理解分类报告

💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Sklearn 机器学习 IRIS 数据分类报告解读 鸢尾花(Iris)数据集是机器学习入门中最经典…

ni-app 对鸿蒙的支持现状

自 HBuilderX 4.27 版本开始&#xff0c;uni-app 支持 Harmony Next 平台的 App 开发&#xff0c;目前仅支持 Vue3 项目编译到鸿蒙平台。uni-app x 从 4.61 版本起支持纯血鸿蒙&#xff0c;即 Harmony NEXT&#xff0c;其组件、API、CSS 与 Android 和 iOS 基本拉齐。 开发与配…

docker 容器学习

笔者来介绍一下docker 容器的学习1、docker容器背景 docker 里面有两个概念&#xff0c;镜像可看成一个类&#xff0c;而容器则是镜像的一个实例&#xff0c;从这个来看&#xff0c;那么一般镜像是一个&#xff0c;而容器可以有很多个。 镜像&#xff1a;带一堆工具链的操作系统…

MongoDB社区版安装(windows)

下载地址 官网&#xff1a; MongoDB: The World’s Leading Modern Database | MongoDB 8.0.11版本下载地址&#xff1a; https://fastdl.mongodb.org/windows/mongodb-windows-x86_64-8.0.11.zip 安装 解压安装包 解压结果目录如下&#xff1a; bin目录介绍 文件名称作用…

Git上传与下载GitHub仓库

新建GitHub仓库 … 本地上传GitHub 第一步&#xff1a;git add .第二步&#xff1a;git commit -m your content第三步&#xff1a;git push xxx main或者git push xxx master 对于第三步&#xff0c;首先看自己建立的仓库是master分支&#xff0c;还是main分支。以前都是默认m…

OpenCV 官翻 3 - 特征检测 Feature Detection

文章目录理解特征目标解释Harris角点检测目标理论OpenCV 中的 Harris 角点检测器亚像素级精度角点检测练习Shi-Tomasi角点检测器与优质跟踪特征目标理论基础代码SIFT&#xff08;尺度不变特征变换&#xff09;简介目标理论1、尺度空间极值检测2、关键点定位3、方向分配4、关键点…

2️⃣处理文本数据

本章介绍 为大语言模型的训练准备文本数据集将文本分割成词和子词token字节对编码(Byte Pair Encoding,BPE):一种更为高级的文本分词技术使用滑动窗口方法采样训练示例将tokens转换为向量&#xff0c;输入到大语言模型中 文章目录本章介绍2.1 理解词嵌入2.2 文本分词2.3 将tok…

TestCase Studio - 自动生成测试用例详解

你是否也曾为编写测试用例而头疼&#xff1f;点击按钮、填写表单、截图说明——这些重复操作让人心生倦意。 而现在&#xff0c;只需动动鼠标&#xff0c;TestCase Studio 就能自动录制你的 Web 操作&#xff0c;生成清晰的“Plain English”步骤、截图和定位器&#xff0c;彻…

Rust+ChatBoxAI:实战

Chatbox AI Chatbox AI 是一款基于人工智能技术的智能助手工具,旨在通过自然语言交互帮助用户完成多种任务。以下是其核心功能与特点: 功能概述 多模型支持:可连接 OpenAI、Claude、Gemini 等主流大语言模型,用户能自由切换不同 AI 服务。 本地运行:支持离线使用,数据隐…

服务器与工控机的区别解析

服务器和工控机虽然都是计算机&#xff0c;但它们的设计目标、使用环境和核心特性有本质的区别&#xff0c;就像轿车和越野车虽然都是车&#xff0c;但用途和构造截然不同。以下是它们的主要区别&#xff1a;核心设计目标&#xff1a;服务器&#xff1a; 数据处理、存储、网络服…

【大模型】深入解析大模型推理架构之 Prefill-Decode Disaggregation (PD分离)

深入解析大模型推理架构之 Prefill-Decode Disaggregation (PD分离) 文章目录深入解析大模型推理架构之 Prefill-Decode Disaggregation (PD分离)1 从统一到分离&#xff0c;推理架构为何演进&#xff1f;2 什么是Prefill-Decode分离&#xff1f;3 PD分离系统的工作流程4 PD分离…

D3动画--动态绘制文本下划线,支持自定义曲线

前言&#xff1a;在现实生活中&#xff0c;看书的时候&#xff0c;在文本的下面画个波浪线&#xff0c;画个横线&#xff0c;是很常见的行为。本篇文章使用D3动画来实现一个给文本绘制下划线的效果&#xff0c;可以暂停绘制&#xff0c;继续绘制&#xff0c;重新绘制&#xff0…

单表查询-分页提前获取数据

1、 问题 以下的例子如何优化呢&#xff1f; SELECT * FROM(SELECT INNER_TABLE.*, ROWNUM OUTER_TABLE_ROWNUM FROM (SELECT t1.* FROM ( SELECT * FROM T1 ) t1 WHERE 1 1 ORDER BY T1.TTIME DESC)INNER_TABLE ) OUTER_TABLE WHERE OUTER_TABLE_ROWNUM<25AND OUTER_TA…

Oracle触发器:数据世界的“隐形守护者“

今天&#xff0c;我想和大家聊一个在Oracle数据库领域既强大又神秘的话题——触发器&#xff08;Trigger&#xff09;​。在座的各位可能都写过SQL语句&#xff0c;做过表结构设计&#xff0c;甚至用过存储过程&#xff0c;但有很多人对触发器的态度可能是"既爱又怕"…

Python桌面版数独游戏(三版)-增加难易度模式

数独游戏难度模式解析 在数独游戏中&#xff0c;难度通常由已知数字&#xff08;提示数&#xff09;的数量决定。难度越高&#xff0c;已知数字越少&#xff0c;玩家需要推理的步骤越多。以下是不同模式下的算法区别和核心代码解析。 文章目录数独游戏难度模式解析1. **难度模…

k8s查看某个pod的svc

在 Kubernetes 中&#xff0c;要查看与特定 Pod 相关的 Service&#xff0c;可以通过以下方法&#xff1a;#### 方法一&#xff1a;通过标签匹配1. **获取 Pod 的标签**bashkubectl get pod <pod-name> --show-labels输出示例&#xff1a;NAME READY STATUS RESTARTS AGE…

通俗易懂卷积神经网络(CNN)指南

本文用直观类比和可视化方法&#xff0c;帮你彻底理解CNN的工作原理&#xff0c;无需深厚数学基础也能掌握计算机视觉的核心技术。卷积神经网络&#xff08;CNN&#xff09;是深度学习中革命性的架构&#xff0c;它彻底改变了计算机"看世界"的方式。本文将用最直观的…

AV1平滑缓冲区

对于解码的每一帧视频数据&#xff0c;解码器都必须从缓冲池中找到一个尚未被使用的帧缓冲区插槽来存储解码后的数据。分配的帧缓冲区插槽用于临时保存解码过程中生成的帧数据&#xff0c;直到它们被用于显示或进一步的处理。函数get_free_buffer的作用是在缓冲池中搜索尚未被分…