Shell脚本学习笔记

shell脚本学习

前言

在学习一生一芯的途中遇上了shell脚本的使用,这块知识对我来说是空白,所以打算记录一些学习笔记,未来在后面可能也会记录一些shell的使用技巧(也许)

学习资料来自Shell 变量 | 菜鸟教程

shell变量

注意,变量名和等号之间不能有空格,这可能和你熟悉的所有编程语言都不一样

变量名的命名须遵循如下规则:

  • 只包含字母、数字和下划线: 变量名可以包含字母(大小写敏感)、数字和下划线 _,不能包含其他特殊字符。
  • 不能以数字开头: 变量名不能以数字开头,但可以包含数字。
  • 避免使用 Shell 关键字: 不要使用Shell的关键字(例如 if、then、else、fi、for、while 等)作为变量名,以免引起混淆。
  • 使用大写字母表示常量: 习惯上,常量的变量名通常使用大写字母,例如 PI=3.14
  • 避免使用特殊符号: 尽量避免在变量名中使用特殊符号,因为它们可能与 Shell 的语法产生冲突。
  • 避免使用空格: 变量名中不应该包含空格,因为空格通常用于分隔命令和参数。

有效的变量命名:

1
2
3
4
RUNOOB="www.runoob.com"
LD_LIBRARY_PATH="/bin/"
_var="123"
var2="abc"

无效的变量命名:

1
2
3
4
5
6
7
8
# 避免使用if作为变量名
if="some_value"
# 避免使用 $ 等特殊符号
variable_with_$=42
?var=123
user*name=runoob
# 避免空格
variable with space="value"

使用变量

使用一个定义过的变量,只要在变量名前面加美元符号即可,如:

1
2
3
your_name="qinjx"
echo $your_name
echo ${your_name}

变量名外面的花括号是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界,比如下面这种情况:

1
2
3
for skill in Ada Coffe Action Java; do
    echo "I am good at ${skill}Script"
done

如果不给skill变量加花括号,写成echo “I am good at $skillScript”,解释器就会把$skillScript当成一个变量(其值为空),代码执行结果就不是我们期望的样子了。

推荐给所有变量加上花括号,这是个好的编程习惯。

第二次赋值的时候不能写第二次赋值的时候不能写$your_name="alibaba",使用变量的时候才加美元符($)。

只读变量

使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变。

1
2
3
myUrl="https://www.google.com"
readonly myUrl
myUrl="https://www.runoob.com"

运行脚本,结果如下:

/bin/sh: NAME: This variable is read only.

删除变量

使用 unset 命令可以删除变量。语法:

1
2
3
4
5
#!/bin/sh

myUrl="https://www.runoob.com"
unset myUrl
echo $myUrl

以上实例执行将没有任何输出。

变量类型

字符串变量: 在 Shell中,变量通常被视为字符串。你可以使用单引号 或双引号 " 来定义字符串

整数变量: 在一些Shell中,你可以使用 declaretypeset 命令来声明整数变量。

这样的变量只包含整数值,例如:

1
declare -i my_integer=42

这样的声明告诉 Shell 将 my_integer 视为整数,如果尝试将非整数值赋给它,Shell会尝试将其转换为整数。

数组变量: Shell 也支持数组,允许你在一个变量中存储多个值。

数组可以是整数索引数组或关联数组,以下是一个简单的整数索引数组的例子:

1
2
3
declare -a my_array
my_array=( 1 2 3 4 5 )
# 可以不显示申明,直接my_array=( 1 2 3 4 5 )

或者关联数组:

1
2
3
declare -A associative_array
associative_array["name"]="John"
associative_array["age"]=30
特性普通数组(Indexed Array)关联数组(Associative Array)
索引(键)类型非负整数(0, 1, 2, …)任意字符串(包括数字字符串)
声明方式arr=(value1 value2)declare -a arrdeclare -A arr(必须显式声明)
赋值示例arr[0]="hello"arr["name"]="John"
长度获取${#arr[@]}${#arr[@]}(同样返回键值对个数)
遍历所有元素for i in "${arr[@]}"; do ...for key in "${!arr[@]}"; do ...
键列表获取不适用(或 ${!arr[@]} 返回索引)${!arr[@]} 返回所有键
内存/性能更紧凑,访问快哈希表实现,稍占更多内存,访问稍慢
有序性按整数索引顺序存储顺序依赖于内部哈希算法实际上,Bash 的关联数组遍历顺序是基于哈希顺序,不保证与插入顺序一致;而普通数组保证按索引升序。
适用场景列表、序列、固定偏移的数据映射、字典、配置项、需要字符串键的查找表

环境变量: 这些是由操作系统或用户设置的特殊变量,用于配置 Shell 的行为和影响其执行环境。

例如,PATH 变量包含了操作系统搜索可执行文件的路径:

1
echo $PATH

特殊变量: 有一些特殊变量在 Shell 中具有特殊含义,例如 $0 表示脚本的名称,$1, $2, 等表示脚本的参数。

$#表示传递给脚本的参数数量,$? 表示上一个命令的退出状态等。

shell字符串

单引号

1
str='this is a string'

单引号字符串的限制:

  • 单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的;
  • 单引号字符串中不能出现单独一个的单引号(对单引号使用转义符后也不行),但可成对出现,作为字符串拼接使用。

双引号

1
2
3
your_name="runoob"
str="Hello, I know you are \"$your_name\"! \n"
echo -e $str

输出结果为:

Hello, I know you are "runoob"!

双引号的优点:

  • 双引号里可以有变量
  • 双引号里可以出现转义字符

拼接字符串

在 Bash 中,字符串拼接非常简单:直接并排写在一起。不需要像其他语言那样使用 +. 运算符。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
your_name="runoob"
# 使用双引号拼接
greeting="hello, "$your_name" !"
greeting_1="hello, ${your_name} !"
echo $greeting  $greeting_1

# 使用单引号拼接
greeting_2='hello, '$your_name' !'
greeting_3='hello, ${your_name} !'
echo $greeting_2  $greeting_3

输出结果为:

1
2
hello, runoob ! hello, runoob !
hello, runoob ! hello, ${your_name} !

获取字符串长度

使用 ${#变量名} 可以快速获得字符串的长度(字符数)。

1
2
string="abcd"
echo ${#string}   # 输出 4

变量为字符串时,${#string} 等价于 ${#string[0]}:

提取子字符串

1
2
string="runoob is a great site"
echo ${string:1:4} # 输出 unoo

注意:第一个字符的索引值为 0

查找子字符串

查找字符 io 的位置(哪个字母先出现就计算哪个):

1
2
string="runoob is a great site"
echo `expr index "$string" io`  # 输出 4

注意: 以上脚本中 ` 是反引号,而不是单引号 ,不要看错了哦。

反引号 ... 用于命令替换,将 expr 的输出赋值给 echo

  • expr index 的参数:第一个是源字符串 "runoob is a great site",第二个是字符集 "io"(包含两个字符 io)。

  • expr index逐个字符扫描源字符串,一旦遇到与字符集中任意字符匹配的字符,就返回该字符的位置(从 1 计数)。

  • 源字符串 "runoob is a great site"

    • 位置 1: r → 不是 io
    • 位置 2: u → 不是
    • 位置 3: n → 不是
    • 位置 4: o o,匹配字符集 "io"
  • 因此返回 4

shell数组

bash支持一维数组(不支持多维数组),并且没有限定数组的大小。

类似于 C 语言,数组元素的下标由 0 开始编号。获取数组中的元素要利用下标,下标可以是整数或算术表达式,其值应大于或等于 0。

实例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
echo "Running unit tests:"

for i in tests/*_tests
do
    if test -f $i
    then
        if $VALGRIND ./$i 2>> tests/tests.log
        then
            echo $i PASS
        else
            echo "ERROR in test $i: here's tests/tests.log"
            echo "------"
            tail tests/tests.log
            exit 1
        fi
    fi
done

echo ""

逐行解析

echo "Running unit tests:"

echo 是 shell 命令,将后面的文字原样打印到终端。这一行只是提示,运行时屏幕先显示:

1
Running unit tests:

for i in tests/*_tests

for ... in ... 是循环结构。把后面列出的东西一个一个取出来,每次把当前值赋给变量 i,然后执行 do ... done 之间的代码。

tests/*_tests 中的 * 是通配符。Shell 在执行前会把模式展开成实际文件名列表。如果 tests/ 下有 foo_testsbar_tests,展开后等价于:

1
for i in tests/foo_tests tests/bar_tests

整体含义:把 tests/ 下所有以 _tests 结尾的文件名,挨个放进变量 i,对每个文件执行一次循环体。

dodone

do 标记循环体开始,done 标记结束。两者之间的代码针对 for 取出的每一个值执行一次。

if test -f $i

$i$ 取变量里的内容。如 i 中存的是 tests/foo_tests$i 被替换成 tests/foo_tests

test -ftest 是条件检查命令。-f 检查后面的路径是否是一个普通文件(不是目录等)。

合起来:如果 $i 是真实存在的普通文件,才执行后续代码。 保护机制,避免通配符意外匹配到目录。

if $VALGRIND ./$i 2>> tests/tests.log

核心逻辑,拆成几块:

$VALGRIND — 变量取值。正常 make testsVALGRIND 未设置,展开为空。make valgrind 时它被设为 valgrind --log-file=/tmp/valgrind-%p.log,测试就在 valgrind 监控下运行。

./$i — 执行当前目录下的测试程序。当前目录一般不在 PATH 中,所以必须加 ./ 前缀。

2>> tests/tests.log2 是标准错误输出(stderr),>> 是追加重定向。合起来:把测试程序的标准错误输出追加到 tests/tests.log,不在终端显示。

if ... then ... else ... fi — 检查命令退出码。0(成功)走 then,非 0(失败)走 else。整体含义:运行测试,检查退出码,通过则打印 PASS,否则打印错误并退出。

echo $i PASS

测试通过时输出。如变量 itests/foo_tests,显示:

1
tests/foo_tests PASS

失败分支

测试返回非 0 退出码时依次执行:

  1. echo "ERROR in test $i: here's tests/tests.log" — 提示哪个测试失败
  2. echo "------" — 分隔线
  3. tail tests/tests.log — 显示日志文件最后 10 行,让用户看到具体错误
  4. exit 1 — 结束脚本并返回退出码 1(非 0 表示失败),make 看到后会停止后续操作

fi

fiif 倒过来写,结束 if 代码块。

echo ""

打印空行,让输出末尾整洁一点。

整体执行流程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
打印 "Running unit tests:"

对于 tests/ 下每个以 _tests 结尾的东西:
    如果是普通文件(不是目录等):
        运行它(可能用 valgrind 包裹)
        错误信息记入 tests/tests.log(不显示在屏幕)
        如果退出码是 0(测试通过):
            打印 "文件名 PASS"
        否则(测试失败):
            打印 "ERROR in 文件名: here's tests/tests.log"
            打印 "------"
            打印日志文件最后几行
            退出脚本,返回错误码 1(告诉 make 出错了)

VALGRIND 环境变量机制

1
2
valgrind:
	VALGRIND="valgrind --log-file=/tmp/valgrind-%p.log" $(MAKE)

临时环境变量赋值

VAR_NAME="值" 命令 是 Shell 的临时环境变量赋值。执行命令时临时设置变量,命令结束后变量自动消失。

valgrind 参数说明

Valgrind 是 Linux 内存调试工具,检测内存泄漏、use-after-free、数组越界等问题。

--log-file=/tmp/valgrind-%p.log 指定检测结果写入文件:

  • /tmp/ — 系统临时目录,重启后清空
  • %p — 占位符,valgrind 替换为当前进程的 PID,避免多测试输出互相覆盖

$(MAKE) 的作用

make 的内置变量,值为 make 命令本身。使用它可保留父 make 的并行参数 -j 等特殊状态。

实际效果

运行 make valgrind 后 Makefile 递归调用自身,runtests.sh 看到 VALGRIND 不为空,每个测试实际变为:

1
valgrind --log-file=/tmp/valgrind-%p.log ./tests/foo_tests 2>> tests/tests.log

检测结果写入 /tmp/valgrind-xxx.log,可通过以下查看:

1
cat /tmp/valgrind-*.log

典型泄漏报告示例:

1
2
3
4
5
6
7
8
==1234== HEAP SUMMARY:
==1234==     in use at exit: 42 bytes in 3 blocks
==1234==   total heap usage: 5 allocs, 2 frees, 1,034 bytes allocated
==1234==
==1234== 42 bytes in 3 blocks are definitely lost in loss record 1 of 1
==1234==    at 0x4A06A18: malloc (vg_replace_malloc.c:236)
==1234==    by 0x4005E4: create_person (person.c:12)
==1234==    by 0x40061A: main (test_person.c:8)

表示 person.c:12malloc 分配的内存未释放。

Licensed under CC BY-NC-SA 4.0
七弦绕明宫,知音且闻音,写作智音读作知音
使用 Hugo 构建
主题 StackJimmy 设计