还不会用sed命令吗?

之前写的一个脚手架为了方便 Docker 部署写了一个 Dockerfile 和 build.sh 的脚本,大概长这样:

Dockerfile 复制代码
FROM openjdk:17

# 设置容器名称和重启策略
ARG CONTAINER_NAME=quick-spring
ARG RESTART="always"

# 暴露端口
EXPOSE 8888

# 挂载本地文件系统到容器中
ADD ./core-0.0.1.jar app.jar

# 设置环境变量
ENV TZ=Asia/Shanghai

# 启动命令
CMD java -jar app.jar
shell 复制代码
docker rm -f quick-spring
docker build . -t quick-spring
docker run -d -p 8888:8888 --name quick-spring quick-spring

每次需要部署的时候只需要把 Dockefile 和 build.sh 以及 jar 包上传到服务就可以运行 build.sh 一键部署了。

但是每当我用这个脚手架新开一个项目的时候,总是需要重新替换一下 Dockerfile 和 build.sh 中的名字,虽然工作量不大,但是改多了也会烦,而且有时候甚至会忘记修改。

程序员都是爱偷懒的,我也不例外。

于是我便想着把这个操作通用化,写一个 shell 脚本完成所有的步骤,实现真正的一键部署。

期间用到了许多实用的知识点,于是便写了这篇文章记录一下。

Sed命令

首先想到的是通过 pom.xml 来获取项目名以及版本号:

ini 复制代码
project_name = $(cat pom.xml |  sed -n 's/.*<artifactId>\(.*\)<\/artifactId>.*$/\1/p')

先来介绍一下 sed 命令

sed 是一个流式文本编辑器,用于在文本流中进行替换、删除、插入等操作,可以很方便地和正则表达式配合使用。

它的格式是:

arduino 复制代码
sed OPTIONS 'COMMAND' FILE

一个最基本的例子是:

arduino 复制代码
sed 's/hello/world/' file

其中 s 表示替换命令,即本次操作进行的是替换操作,将 hello 替换为 world,然后打印出来,如:

但是默认情况下只会替换每一行的第一个匹配项:

需要加上 g 命令才能替换所有匹配项:

如果我只想要打印发生替换的行,那么就加上 p 命令(打印模板块的行)和 -n 选项(仅显示处理后的结果)即:

回到上面的问题:

arduino 复制代码
sed -n 's/.*<artifactId>\(.*\)<\/artifactId>.*$/\1/p'

现在来看的话就清晰多了,首先,这里的 -n 和 p 是为了确保只输出替换后的行。

至于中中间的字符串其实应该这么看:

arduino 复制代码
's/   .*<artifactId>\(.*\)<\/artifactId>.*$/   \1   /p'

.*<artifactId>\(.*\)<\/artifactId>.*$/ 这个表示的就是要被替换的文本的正则表达式,而 \1 则表示第一个捕获组,也就是这里的 \(.*\) 。

值得注意的是这里捕获组的括号用到了转义字符,这是因为 sed 命令默认使用的是最原始的正则表达式,捕获组的括号必须是 \(\) 而不是 () 。

而这里的 \1 则表示第一个捕获组,也就是 \(.*\) 捕获到的这个 .* 内容,也就是如果我们匹配到的项目名是 quick-spring ,那么这里的 \1 则是 quick-spring 。

所以可想而知这里最后输出的其实就是尖括号中间的字符串。

但是上面的写法会有一个问题:

不难看出所有的 artifactId 都被匹配了。

于是便用了一个取巧的方法,只读取前10行:

bash 复制代码
head -n 10 pom.xml |  sed -n 's/.*<artifactId>\(.*\)<\/artifactId>.*$/\1/p'

虽然不适用所有情况(比如 artifactId 写到后面去了),但是在这个脚手架里面还是可以解决问题的(而且一般 artifactId 都是写在前十行):

然后用占位符来替换掉原来的硬编码:

ini 复制代码
FROM openjdk:17

# 设置容器名称和重启策略
ARG CONTAINER_NAME={{projectName}}
ARG RESTART="always"

# 暴露端口
EXPOSE 8888

# 挂载本地文件系统到容器中
ADD ./core-{{version}}.jar app.jar

# 设置环境变量
ENV TZ=Asia/Shanghai

# 启动命令
CMD java -jar app.jar

接着只需要通过 sed 命令进行正则匹配并替换文本即可:

bash 复制代码
sed -e "s/{{projectName}}/${project_name}/" -e "s/{{version}}/${version}/" Dockerfile.template | cat > Dockerfile

注意到这个 -e 选项可以组合多个命令,将多个命令作用于同一个文本。

接下来就是构建的操作了:

bash 复制代码
docker rm -f "${project_name}"
docker build . -t "${project_name}"
docker run -d -p 8888:8888 --name "${project_name} ${project_name}"

test命令

值得注意的是,为了防止多次重复生成 Dockerfile,脚本还对文本是否存在进行了检测,这里用到了 test 命令 -f 和 !:

bash 复制代码
if [ ! -f Dockerfile ]; then
  echo "生成Dockerfile"
  sed -e "s/{{projectName}}/${project_name}/" -e "s/{{version}}/${version}/" Dockerfile.template | cat > Dockerfile
fi

test 命令是用于测试文件或条件的工具,通常用于 shell 脚本中的条件判断。在 Unix/Linux 系统中,test 命令通常由 [ ] 表示,它们可以用于测试各种条件,并返回一个布尔值作为结果。

以下是常见的 test 命令及其功能:

  1. 文件测试:

    • e FILE:检查文件是否存在。
    • f FILE:检查文件是否存在且为普通文件。
    • d FILE:检查文件是否存在且为目录。
    • r FILE:检查文件是否存在且可读。
    • w FILE:检查文件是否存在且可写。
    • x FILE:检查文件是否存在且可执行。
  2. 字符串测试:

    • z STRING:检查字符串是否为空。
    • n STRING:检查字符串是否非空。
    • STRING1 = STRING2:检查两个字符串是否相等。
    • STRING1 != STRING2:检查两个字符串是否不相等。
  3. 整数比较:

    • INTEGER1 -eq INTEGER2:检查两个整数是否相等。
    • INTEGER1 -ne INTEGER2:检查两个整数是否不相等。
    • INTEGER1 -gt INTEGER2:检查 INTEGER1 是否大于 INTEGER2。
    • INTEGER1 -lt INTEGER2:检查 INTEGER1 是否小于 INTEGER2。
    • INTEGER1 -ge INTEGER2:检查 INTEGER1 是否大于或等于 INTEGER2。
    • INTEGER1 -le INTEGER2:检查 INTEGER1 是否小于或等于 INTEGER2。
  4. 逻辑测试:

    • ! EXPRESSION:返回 EXPRESSION 的逻辑否定值。
    • EXPRESSION1 -a EXPRESSION2:返回 AND 逻辑运算结果。
    • EXPRESSION1 -o EXPRESSION2:返回 OR 逻辑运算结果

代码

shell 复制代码
echo "解析pom.xml"
pom_xml_head10=$(head -n 10 pom.xml)
project_name=$(echo "$pom_xml_head10" | sed -n 's/.*<artifactId>(.*)</artifactId>.*$/\1/p')
version=$(echo "$pom_xml_head10" | sed -n 's/.*<version>(.*)</version>.*$/\1/p')

echo "项目名:$project_name"
echo "版本号:$version"

if [ ! -f Dockerfile ]; then
  echo "生成Dockerfile"
  sed -e "s/{{projectName}}/${project_name}/" -e "s/{{version}}/${version}/" Dockerfile.template | cat > Dockerfile
fi

echo "删除原有容器"
docker rm -f "${project_name}"

echo "构建镜像"
docker build . -t "${project_name}"

echo "启动容器"
docker run -d -p 8888:8888 --name "${project_name} ${project_name}"
相关推荐
Cosolar5 分钟前
DeepInfer 完全教程:DeepSeek 内部推理框架原理、架构与部署实战
后端·架构·github
YDS8295 分钟前
Small Spring IOC篇:应用上下文实现和Bean的扩展
java·后端·spring
喵个咪9 分钟前
GoWind ShortPlay 拆解:Ent privacy/viewer 多租户隔离——从中间件到 SQL 的零信任链
后端·架构·go
全栈弄潮儿24 分钟前
Python实战第11期:面向对象编程基础
后端·python·编程语言
用户693717500138425 分钟前
当我说 Claude 搭配 GLM-5.3-Flash 太好用了,同事却提醒我:你可能正在失去对代码的掌控
android·后端·ios
喵个咪26 分钟前
GoWind ShortPlay 拆解:不用模型的推荐闭环——埋点回流、三路召回与已看去重
后端·架构·go
喵个咪27 分钟前
GoWind ShortPlay 深度拆解:三服务 BFF + Core,一条请求的完整工程链路
后端·架构·go
devilnumber28 分钟前
springboot的保护机制怎么实现的,为何能做到服务不宕机
java·spring boot·后端
未秃头的程序猿30 分钟前
一次重复扣款的复盘:分布式锁加错了维度,比不加锁更危险
java·redis·后端
张小姐的猫31 分钟前
【MySQL】数据类型
android·linux·数据库·c++·mysql·adb·php