之前写的一个脚手架为了方便 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 命令及其功能:
-
文件测试:
e FILE
:检查文件是否存在。f FILE
:检查文件是否存在且为普通文件。d FILE
:检查文件是否存在且为目录。r FILE
:检查文件是否存在且可读。w FILE
:检查文件是否存在且可写。x FILE
:检查文件是否存在且可执行。
-
字符串测试:
z STRING
:检查字符串是否为空。n STRING
:检查字符串是否非空。STRING1 = STRING2
:检查两个字符串是否相等。STRING1 != STRING2
:检查两个字符串是否不相等。
-
整数比较:
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。
-
逻辑测试:
! 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}"