大家好。作为一名从Java开发转行来AI大模型应用开发的工程师,经常会犯的一个错误:很多新入行的算法同学或者开发小伙伴,常常陷入一个误区:只学Python,忽略了底层运维与自动化的基石——Shell。

在真实的大模型开发场景中,无论是动辄几十GB的语料数据清洗、分布式训练的环境配置,还是模型推理服务的自动化打包部署,其实都离不开那黑乎乎的终端窗口。很多时候,用Python写几十行代码才能搞定的文本截取和统计,用Shell的awkcut配合正则表达式,仅仅一行命令就能优雅秒杀!今天,我们就以此篇博客为契机,结合大模型技术栈中的Shell应用场景,从零开始,带大家深度剖析Shell脚本编程的核心技术 。

本篇内容非常详尽,不仅有严谨的专业表述,我也尽量加入了通俗易懂的“大白话”解释。建议大家先点赞、收藏,再跑上一杯咖啡,慢慢享用!

🌟 一、 认识Shell:不仅仅是一个终端

1.1 Shell概述与解析器

很多初学者搞不清Linux内核、终端和Shell的关系。通俗来讲,Linux内核是底层干活的“苦力”,而Shell就是一个“翻译官” 。它接收我们人类输入的命令,将其解析、翻译成内核能听懂的指令去执行。在Linux系统中,提供了多种Shell解析器 。

常见的解析器我们可以通过命令 cat /etc/shells 查看 :

Bash

steven@ubuntu-1:~$ cat /etc/shells
# /etc/shells: valid login shells
/bin/sh
/bin/bash
/usr/bin/bash
/bin/rbash
/usr/bin/rbash
/usr/bin/sh
/bin/dash
/usr/bin/dash

系统会列出包括 /bin/sh/bin/bash/bin/dash 等多种解析器 。在我们最常用的Ubuntu系统中,默认的解析器就是强大且易用的 bash 。你可以随时通过 echo $SHELL 命令来确认当前使用的环境 。

1.2 脚本入门与执行的“暗坑”

一个标准的Shell脚本通常以 #!/bin/bash 开头,这行特殊的注释被称为Shebang,用于显式地指定系统的解析器 。

比如我们编写第一个Shell脚本 helloworld.sh ,需求是输出 helloworld 。 可以使用 vim helloworld.sh ,在文件中输入如下内容:

Bash

#!/bin/bash
echo "Hello shell!"

保存退出即可 。

避坑指南:执行脚本的两种姿势 写好脚本后,大家常在执行环节碰壁。Shell脚本的常用执行方式分为两大流派 :

第一种:采用bash或sh+脚本的相对/绝对路径(不用赋予脚本 +x 权限)

  • 相对路径执行:sh ./helloworld.shbash ./helloworld.sh

  • 绝对路径执行:sh /home/steven/helloworld.sh

    原理揭秘: 这种方式的本质是 bash 解析器帮你执行脚本,所以脚本本身不需要执行权限 。

第二种:采用输入脚本的绝对路径或相对路径执行脚本(必须具有可执行权限 +x

  • 首先需要赋予脚本执行权限:chmod +x helloworld.sh

  • 接着通过相对路径 ./helloworld.sh 或绝对路径 /home/steven/helloworld.sh 执行 。

    原理揭秘: 这种方式的本质是脚本需要自己执行,所以必须需要执行权限 。

📦 二、 变量体系:数据在Shell世界中的流转

在大模型部署脚本中,我们需要定义模型路径、环境变量、端口号等,这些都离不开Shell的变量系统 。

2.1 系统预定义变量

系统预先给我们配置好了一些全局变量,比如 PATHHOMEPWDSHELLUSER 等 。获取这些变量的值非常简单,使用 $变量名 语法即可,切记 $ 和变量名之间绝对不能有空格 。

Bash

steven@ubuntu-1:~$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
steven@ubuntu-1:~$ echo $HOME
/home/steven

如果想显示当前Shell中所有的变量,可以使用 set 命令 。

2.2 自定义变量的“铁律”

当我们需要自己造轮子时,定义变量也有着严格的规则:

定义格式严苛:语法为 变量名=变量值,注意等号 = 前后不能有空格 。

撤销变量:使用 unset 变量名 即可撤销一个变量 。

声明静态变量:通过 readonly 变量名 声明静态变量,静态变量不能重新赋值,也不能被 unset

变量定义规则:变量名称可以由字母、数字和下划线组成,但绝对不能以数字开头 。环境变量名建议大写 。

字符串类型:在bash中,变量默认类型都是字符串类型,无法直接进行数值运算 。例如输入 C=1+2 后再 echo $C,输出结果是文本 1+2

遇空格必引:变量的值如果有空格,需要使用双引号或单引号括起来 。如果不加引号,例如 D=I love banzhang,系统会报错提示找不到命令 。正确写法为 D="I like banzhang"

最右侧分号:最右侧的分号可有可无,一般为了简洁都不写 。

💡 进阶技巧:全局环境变量 普通自定义变量只能在当前的Shell环境中使用 。如果我们想把它提升为全局环境变量,供其他Shell程序使用,需要使用 export 变量名 语法 。

2.3 极度重要的特殊变量

这部分是重中之重,不仅是面试常客,更是编写灵活脚本的关键 。

$n(参数获取)n 为数字,$0 代表该脚本名称,$1-$9 代表第一到第九个参数 。十以上的参数需要用大括号包含,如 ${10}

$#(参数个数):获取所有输入参数的个数,常用于循环、判断参数个数是否正确以及加强脚本的健壮性 。

$\*$@(参数集合):它们都代表命令行中所有的参数 。

**区别在哪?** 不被双引号包含时,它们表现一致 。但当它们被双引号 `""` 包含时:`"$*"` 会将所有的参数作为一个整体,以 `"$1 $2 … $n"` 的形式输出 ;而 `"$@"` 会将各个参数分开,以 `"$1" "$2" … "$n"` 的形式输出,每个参数都是独立的 。  

$?(最后命令的返回状态):最后一次执行的命令的返回状态 。如果这个变量的值为 0,证明上一个命令正确执行;如果值为非 0,则证明上一个命令执行不正确 。

🧠 三、 算术与条件判断:赋予脚本“逻辑思考”能力

单纯的按顺序执行命令不叫编程,加入逻辑判断才是脚本的灵魂 。

3.1 突破字符串限制:算术运算符

前面提到bash默认都是字符串,想做数学题怎么办?我们需要请出特殊的运算符号:$((运算式))$[运算式]

Bash

steven@ubuntu-1:~$ S=$[(2+3)*4]
steven@ubuntu-1:~$ echo $S
20

3.2 严谨的条件判断

Shell中的条件判断基本语法有两种:test condition 或者更为常用的 [ condition ](注意 condition 前后必须要有空格) 。条件成立(数据非空)返回值为 0(真),否则为 1(假) 。

常用的判断条件有:

两个整数之间比较[ ] 中不能直接使用 =>< 。需要使用以下操作符 :

 `-eq` 等于(equal)   

 `-ne` 不等于(not equal)   

 `-lt` 小于(less than)   

 `-le` 小于等于(less equal)   

 `-gt` 大于(greater than)   

 `-ge` 大于等于(greater equal) *(注:`(( ))` 主要用于执行算术运算与条件判断,其内部能运用常见的 C 语言风格算术和比较运算符)* 。  
  1. 按照文件权限进行判断

    -r 有读的权限(read)

    -w 有写的权限(write)

    -x 有执行的权限(execute)

  2. 按照文件类型进行判断

    -e 文件存在(existence)

    -f 文件存在并且是一个常规的文件(file)

    -d 文件存在并且是一个目录(directory)

多条件判断

&& 表示前一条命令执行成功时,才执行后一条命令;|| 表示上一条命令执行失败后,才执行下一条命令 。

Bash

steven@ubuntu-1:~$ [ steven ] && echo OK || echo notOK
OK

⚙️ 四、 流程控制:让代码自动化运转

掌握了判断,我们就可以编织复杂的业务流程了 。

4.1 if 分支决策

Shell的 if 语句可以分为单分支与多分支结构,注意括号间以及 if 后的空格 。

Bash

# 多分支基本语法
if [ 条件判断式 ]
then
    程序
elif [ 条件判断式 ]
then
    程序
else
    程序
fi

案例实操: 输入一个年龄数字,如果小于18,则输出“未成年人”,如果小于60,则输出“成年人”,否则输出“老年人”,如果没有指定年龄,提示“请携带年龄” 。

Bash

#!/bin/bash
if [ $# -eq 0 ]
then
    echo '请携带年龄'
elif [ $1 -lt 18 ]
then
    echo '未成年人'
elif [ $1 -lt 60 ]
then
    echo '成年人'
else
    echo '老年人'
fi

4.2 case 模式匹配

当分支过多时,case 语句比 if 更加结构化 。

Bash

case $变量名 in
"值1")
    如果变量的值等于值1,则执行程序1
    ;;
"值2")
    如果变量的值等于值2,则执行程序2
    ;;
*)
    如果变量的值都不是以上的值,则执行此程序
    ;;
esac

注意事项:case 行尾必须为单词 in,每一个模式匹配必须以右括号 ) 结束 。双分号 ;; 表示命令序列结束,相当于 C 中的 break 。最后的 *) 表示默认模式,相当于 C 中的 default

4.3 循环引擎:for 与 while

for 循环基本语法1(传统风格)

Bash

for ((初始值;循环控制条件;变量变化))
do
    程序
done

常用于数学累加,例如从1加到100 :

Bash

#!/bin/bash
sum=0
for((i=1;i<=100;i++))
do
    sum=$[$sum+$i]
done
echo $sum

for 循环基本语法2(迭代集合风格)

Bash

for 变量 in 值1 值2 值3…
do
    程序
done

while 循环 : 只要条件判断式成立,循环就会一直继续 。

Bash

while [ 条件判断式 ]
do
    程序
done

4.4 read命令:与用户交互

read 命令用于读取终端输入到指定变量中 。 基本语法:read (选项) (参数)

  • 选项 -p:指定读取值时的提示符 。
  • 选项 -t:指定读取值时等待的时间(秒),如果 -t 不加表示一直等待 。
  • 参数 变量:指定读取值的变量名 。

🛠️ 五、 模块化利器:自定义函数

随着脚本业务的复杂化,我们需要引入函数来进行代码封装 。

5.1 区别Shell命令与Shell函数

Shell命令:是构成Shell脚本的基础单位,包括预定义的操作系统命令和外部工具 。

Shell函数:是用户自定义的代码块,用于封装复杂操作,提高代码的模块化和复用性 。

5.2 自定义函数语法与天条

Bash

[ function ] funname[()]
{
    Action;
    [return int;]
}

经验技巧:

必须在调用函数的地方之前先声明函数 。因为Shell脚本是逐行运行的,不会像其他语言一样先编译 。

函数的返回值只能通过 $? 系统变量获得 。可以显示地通过 return 返回(后跟数值0-255) ;如果不加,将以最后一条命令运行结果作为返回值 。

⚔️ 六、 大模型数据预处理工具:cut 与 awk

在大模型应用开发中,清洗原始语料或解析日志数据是高频任务 。Shell提供的文本分析工具可以极大地提升生产力 。

6.1 锋利的“剪刀”:cut

cut 的工作就是“剪”,负责从文件的每一行剪切字节、字符和字段并将它们输出 。 基本用法:cut [选项参数] filename 。默认分隔符是制表符 。

-f:列号,提取第几列 。

-d:分隔符,按照指定分隔符分割列 。

案例实操: 选取系统 PATH 变量值,第2个“:”开始后的所有路径 :

Bash

steven@ubuntu-1:~$ echo $PATH | cut -d ":" -f 3-
/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin

切割 ifconfig 后打印的IP地址 :

Bash

steven@ubuntu-1:~$ ifconfig ens33 | grep netmask | cut -d "i" -f 2 | cut -d " " -f 2
192.168.10.150

6.2 文本分析“手术刀”:awk

awk 是一个强大的文本分析工具,它将文件逐行读入,以空格为默认分隔符将每行切片,切开的部分再进行分析处理 。 基本用法:awk [选项参数] '/pattern1/{action1} /pattern2/{action2}...' filename

pattern:表示 awk 在数据中查找的内容,即匹配模式 。

action:在找到匹配内容时所执行的一系列命令 。

-F:指定输入文件的分隔符 。

-v:赋值一个用户定义变量 。

重要特性:

BEGIN 在所有数据读取行之前执行;END 在所有数据执行之后执行 。

  • 内置变量包括 FILENAME(文件名)、NR(行号)、NF(切割后列的个数) 。

实战应用: 搜索 passwd 文件以 root 关键字开头的所有行,并输出该行的第7列 :

Bash

steven@ubuntu-1:~$ awk -F : '/^root/{print $7}' passwd
/bin/bash

查询 ifconfig 命令输出结果中的空行所在的行号 :

Bash

steven@ubuntu-1:~$ ifconfig | awk '/^$/{print NR}'
9
18

🔍 七、 数据的雷达:正则表达式入门

在Linux中,grepsedawk 等命令都支持通过正则表达式进行模式匹配 。掌握正则表达式能让我们在处理语料文本时游刃有余 。

7.1 常用特殊字符

^:匹配一行的开头 。例如 grep ^a 会匹配出所有以 a 开头的行 。

$:匹配一行的结束 。例如 grep n$ 会匹配出所有以 n 结尾的行 。

.:匹配一个任意的字符 。例如 grep r..t 会匹配包含 rootrabt 等的所有行 。

\*:不单独使用,它和上一个字符连用,表示匹配上一个字符0次或多次 。例如 grep ro*t 会匹配 rtrotroot 等所有行 。

[ ]:表示匹配某个范围内的一个字符 。

`[68]`:匹配6或者8 。  


`[0-9]`:匹配一个0-9的数字 。  


`[0-9]*`:匹配任意长度的数字字符串 。  

\:表示转义 。当你想匹配特殊字符本身时,需要使用转义,例如 grep a\$b 匹配包含 a$b 的行 。

7.2 经典正则表达式

在清洗文本数据时,常用以下经典正则进行数据过滤 :

邮箱正则^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$

手机号正则(复杂版)/^1((34[0-8])|(8\d{2})|(([35][0-35-9]|4[579]|66|7[35678]|9[1389])\d{1}))\d{7}$/

💡 总结

在这篇详细的Shell技术指南中,我们从底层的运行原理一路深入到了高级数据处理的 awk 与正则表达式 。作为AI工程领域的开发者,熟练地将 catgrepawkcut 用管道符串联在一起,能让我们在处理海量大模型语料与自动化部署时真正做到高效与游刃有余 。

大家在日常大模型应用开发与运维中如果遇到任何Shell脚本问题,欢迎在评论区留言交流!别忘了点赞、收藏并关注哦!

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐