← 文章 / 云原生与基础设施
freeCodeCamp 6天前 · 2026-11-04 09:50:41 · 8 阅读

如何用 Shell 命令自动化创建 Postgres 的 Docker 镜像

如果项目里有些代码反复出现,有个很标准的解决办法:把它封装成函数。

但要是项目的文件配置一次次重复出现,又该怎么办呢?

答案就是 bash 脚本。Bash 脚本可以在你的机器上自动执行一系列命令,全程无需人工干预。这就是所谓的工作流自动化,也是本文要教你的内容。

目录

前置条件

要跟着本文操作,你需要:

  • 在机器上安装 Git-bash
  • 有一定使用终端或命令行执行命令的经验(有帮助但非必需)
  • 理解基本编程概念(如条件判断和循环)
  • 在机器上安装 Docker
  • 对 Docker 基本概念(如镜像和容器)有基本了解
  • 用过 Postgres(可选但推荐)

这个项目的由来

我最近在几个项目里都要搭建 Postgres 的 Docker 镜像,发现每次创建镜像的流程几乎一样,唯一的区别只是数据库凭证和数据库名不同。

我还注意到,如今很多快速开发的应用都倾向于用 Supabase 做后端,但它可能存在资源限制,比如数据库存储空间、带宽、并发连接数和 API 请求配额(具体取决于服务套餐和项目规模)。而自己搭建数据库就能解决这些问题。

于是我想,何不作个 CLI 工具,来自动化创建 Postgres 的 Docker 镜像?正是在这时,我学到了如何用 Bash 编写可执行的 Shell 命令,实现工作流自动化。

本指南所用的完整可运行示例,见此处 GitHub 仓库。

简介

Bash 是一种 Shell 和脚本语言,用于编写和执行在计算机上执行操作的命令或脚本,例如创建目录和文件、处理文件、自动化任务、管理系统进程等。

本指南将教你自动化创建 Postgres 数据库的 Docker 镜像,免去每个新项目都从零构建镜像的麻烦。

Bash 脚本文件设置

首先,打开你常用的文本编辑器,在其中创建一个目录。然后在该目录下新建一个后缀为 .sh 的文件。

在文件开头输入:

#!/bin/bash

#!/bin/bash(即“shebang”)会告知操作系统使用 Bash Shell 运行此脚本,使文件内的命令以 Bash/终端命令的形式执行。

Bash 中的条件

建好脚本文件并加上 shebang 后,我们先来看看 Bash 中 if 语句的用法。

if 的语法如下:

if [[ condition ]];
    then
        command to execute
fi

Bash 里的 if 和其他编程语言中的用法并无二致:

  • 用双中括号([])加入要检查的条件,并以分号(;)结尾。
  • 在 if 声明后使用 then,指定条件为真时应执行的命令。
  • 使用 fi 结束条件语句。

在中括号内书写条件时,务必在条件的前后各留一个空格(如上文代码所示)。若省略这些空格,就会报错。

示例说明:

既然我们要自动化创建 Postgres 数据库的 Docker 镜像,先检查目标文件是否存在且包含内容。若有内容,则结束脚本;若无,则继续执行下一段脚本。

#!/bin/bash

if [[ -s docker-compose.yml ]];
    then
        echo "The file already exists"
        exit 0
fi

echo "Next command"

在 if 条件中,我们用 -s 标志来检查文件是否存在且非空。这类文件标志用于检查文件的状态和权限,常见的有:

  • -f:检查文件是否存在
  • -s:检查文件是否存在且非空
  • -d:检查目录是否存在
  • -r:检查文件是否可读
  • -w:检查文件是否可写
  • -x:检查文件是否可执行

在这个脚本中,我们用 -s 检查当前执行命令的目录下是否存在名为 docker-compose.yml 的文件。条件为假时,通过 echo 关键字打印文本 "Next command";条件为真时,打印文本 "The file already exists"。

exit 相当于 bash 脚本中的 return 关键字,0 表示成功,1 表示失败。

我们来测试一下:在编写 bash 脚本的同一目录下创建一个 Docker Compose 文件并写入一些内容。然后在终端输入 bash name-of-file.sh,应该会看到 "The file already exists":

output after creating the already existing file using bash

但如果删掉文件内容后再运行命令,就会看到输出 "Next command":

output of a successful file creation in bash

交互式 Shell

要让自动化脚本更动态、灵活,就需要接收用户输入。以数据库名称为例:数据库名并非固定不变,它会随项目而变化。

因此,在为项目创建新的 Docker 镜像时,需要能使用指定的数据库名称,这可以通过 read 关键字来读取输入实现。

根据操作的不同,输入也分为不同类型,可以用相应的标志来指定预期的输入类型,例如:

  • -p:提示用户输入文本
  • 翻译如下:
  • -s:用于密码场景,在终端输入时隐藏用户输入内容
  • -a:当需要用户传入多个输入或期望接收一个数组时使用

读取用户输入的语法如下:

read -p 'input_label: ' variable_name

实例演示:

接下来我们编写脚本,接收用户关于要创建的 Postgres 数据库镜像的输入。

#!/bin/bash

# 数据库凭证
read -p 'DataBase-name(*): ' db
read -p 'DataBase-user(*): ' user
read -sp 'DataBase-password(*): ' pwd
echo
echo "your database is -> $db with a username of $user and a password of $pwd"

脚本在 read 命令上使用 -p 参数来提示一段文本,使用 -sp 则提示用户输入并隐藏内容(因为 -s 会隐藏所键入的字符)。

如果不同时使用 -p 和 -s,将无法显示提示用户输入密码的标签。单独使用 -s 虽能隐藏输入,但若不通过 echo 或 printf 打印,就不会显示任何提示。

要打印或使用用户添加的输入,在变量名前加美元符号($)。例如,上面我们将获取数据库名称的变量命名为 db,因此访问用户输入的数据库名时使用 $db。

如果在终端中运行该脚本,你会看到一系列等待输入的终端提示。填写完毕后,所有输入值都会显示在终端中:

output on how to get and display user inputs

如何验证和清理输入

在执行任何操作前,你还需要验证输入。这一点很重要,因为关键字段可能会缺失或拼写错误。例如,你可能忘记输入正在创建的镜像所需的数据库名称。

验证输入时,你可以使用以下标志之一(以及其他标志):

  • -z:检查变量值是否为空
  • -n:检查变量值是否不为空
  • -lt:检查一个数是否小于给定数字
  • -gt:检查一个数是否大于给定数字
  • -eq:检查两个值是否相等

对于需要确保非空的每一项输入,你都可以用 -z 标志来检查,通过后再进入下一步:

#!/bin/bash

# 数据库凭据
read -p 'DataBase-name(*): ' db
if [[ -z $db ]]
    then
        echo "The database field cannot be empty!"
        exit 1
fi

read -p 'DataBase-user(*): ' user
if [[ -z $user ]]
    then
        echo "The user field cannot be empty!"
        exit 1
fi

read -sp 'DataBase-password(*): ' pwd
if [[ -z $pwd ]]
    then
        echo "The password field cannot be empty!"
        exit 1
fi

这个脚本可以确保在进行下一项输入之前,所有关键的数据库信息都已填写。

默认值

有些字段你可能希望使用默认值(比如镜像的容器名、容器的端口或卷)。这时可以用 := 给变量设置默认值:

#!/bin/bash

# 容器信息
read -p 'Container-name: ' container_name
read -p 'Container-port(if omitted, the container will use default postgresql default port of 5432): ' port
read -p 'Volume-name: ' vol_name

echo "container-name-> ${container_name:=cont_one}, port->${port:=5432}, volume_name->${vol_name:=backend_data}"

运行脚本时如果这三项输入都留空,bash 就会使用你设置的默认值作为兜底。

Output for setting up a default value on variables

而如果为这些输入提供了值,bash 就会使用你传入的值。比如三项全部填写,看看输出的结果:

Receiving user inputs and displaying the recevied input

如何创建文件并写入内容

了解了如何为 Postgres 数据库创建 Docker 镜像之后,接下来需要创建一个 docker-compose.yml 文件,用于在启动和停止容器时执行。

在 Bash 中,创建文件有多种方式:

  • 第一种是 > filename:文件不存在就创建,存在则清空重写。
  • 还有 >> filename:往文件追加内容,文件不存在则先创建。
  • 最后是 touch filename:文件不存在就创建,存在则保留原内容,只更新文件的时间戳。

如何用 > 创建并写入文件

如果想在创建文件的同时写入内容,可以在 > 前面加上 echo,后面跟上文件名,像这样:

#!/bin/bash

echo Hello> test.txt

这会创建一个 test.txt 文件,内容为 "hello"。

如何用 >> 向已有文件追加内容

要向已有文件追加内容,把内容放在 >> 前面,后面跟上目标文件名即可。比如在上一步创建的 test.txt 文件中,往 Hello 后面追加 Africa:

#!/bin/bash

echo Africa!>> test.txt

现在 test.txt 里会有 Hello,换行后是 Africa!。

接下来就用这些符号创建一个 docker-compose.yml 文件,用来存放 Postgres 数据库的相关配置。

#!/bin/bash

> docker-compose.yml

这样,目录里就生成了 docker-compose 文件。

如何向文件写入多段不同内容

你还可以用 cat 来写入文件。通常 cat 是用来读取文件内容的,但配合 <<EOF 和刚才讲到的追加符号 >>,就可以按自己想要的格式往文件里写入多段内容。语法如下:


# writing docker compose contents

cat <<EOF>> docker-compose.yml

  # file contents
EOF

<<EOF 告诉 bash 一直往文件写入内容,直到遇到 EOF(end of file)关键字为止,再用 >> filename 把这些内容追加到文件中。用在 Docker compose 配置上就是这样的:

#!/bin/bash

# 创建并写入文件
 >  docker-compose.yml

cat<<EOF>>docker-compose.yml
 services:
    postgres:
        image: postgres:18-alpine
        container_name: ${container_name:= $db}

        environment:
            POSTGRES_USER: $user
            POSTGRES_PASSWORD: $pwd
            POSTGRES_database: $db

        ports:
            - ${port:=5432}:5432
        volumes:
            - ${vol_name:=database-data}:/var/postgresql

 volumes:
    ${vol_name:=database-data}:

EOF

Bash 参数与选项

使用 Bash 参数,可以在执行脚本的同一行命令中传入值。具体做法就是在执行脚本时把值直接跟在后面,像这样:

bash script val1 val2 

要读取参数的值,使用美元符号($)加上对应值的位置编号(位置从 1 开始,而不是 0)。

在上面的例子中,如果想输出第二个参数的值,可以使用 echo $2。

为了更好地理解,我们来优化一下数据输入体验:通过参数在执行时直接传入数据库凭据:

#!/bin/bash

# database credentials

if [[ -z $1 ]]; then
    read -p 'DataBase-name(*): ' db
 else
    db="$1"
fi

# database checking and validating

if [[ -z $2 ]]; then
    read -p 'DataBase-user(*): ' user
 else
    user="$2"
fi

# user checking and validating

if [[ -z $1 ]]; then
    read -sp 'DataBase-password(*): ' pwd
 else
    pwd=$3
fi

# password vaidation and checking

这段代码会先检查是否传入了参数。如果没有,就通过提示符让用户输入值;如果有,就把参数值赋给对应的变量(数据库名、密码等)。这样就可以带参数运行脚本了:

bash image.sh customers ABC-Company secret123!

运行上述脚本后创建的镜像将包含一个名为 customers 的数据库,用户为 ABC-Company,密码为 secret123!。

Bash 标志/选项

脚本里有一段用于收集镜像相关信息,但这部分信息可以省略。借助 bash 的选项/标志(flags),你可以通过 -D 选项跳过提示输入容器信息的步骤,让脚本在运行前直接沿用默认的容器信息。

举个例子:


#!/bin/bash

while getopts "ny" options; do
	case "$options" in
		n) echo "You choose no";;
		y) echo "You choose yes";;
		*) echo "No such option/flag"; exit 1;;
    esac
done

shift $((OPTIND - 1))

在上面的脚本中:

  1. while 循环会一直运行,直到 getopts` 为空
  2. getopts 负责获取脚本接收到的选项
  3. "ny" 是我们期望用户在运行脚本时传入的选项
  4. options 是一个变量,用来记录用户在 "n" 和 "y" 之间输入的选项
  5. do 表示循环开始
  6. case 通过 in 关键字检查所选选项是否在已定义的选项列表中
  7. 接着我们定义调用脚本时传入 -n 或 -y 选项后要做的事,并用双分号 (;;) 结束每个选项的定义
  8. * 用于处理未定义的输入选项
  9. esac 结束 case 语句
  10. done 结束循环,避免循环无限运行下去
  11. shift $((OPTIND - 1)) 会把剩余参数左移,越过那些标志位,从而将其从参数列表中丢弃,避免脚本还需要接收其他参数时可能出现的冲突。

你可以在脚本里套用这个语法,在执行时传入 -D 选项来跳过询问容器信息的步骤。


#!/bin/bash

ask_container_info=true

while getopts "D" options; do
	case "$options" in
		D) ask_container_info=false;;
		*) echo "No such option/flag"; exit 1;;
    esac
done

shift $((OPTIND - 1))

if [[ $ask_container_info == true ]]; then
	#Prompt the user to input custom container information
    read -p 'Container-name: ' container_name
    read -p 'Container-port(if omitted, the container will use default postgresql default port of 5432): ' port
    read -p 'Volume-name: ' vol_name
fi

这里只有一个选项,所以选项部分只有 D。ask_container_info 初始值为 true,也就是说,除非传入该选项,脚本都会提示用户输入容器信息。当执行脚本时传入 -D,我们就把 ask_container_info 的值改为 false。

如何让脚本可执行并添加到电脑的 Path 中

下一步是让脚本在你电脑上的每个终端都能使用和执行。目前,脚本只能在你编写它的当前目录下运行。

我们先把脚本文件设为可执行,并去掉文件扩展名,这样就不必每次都带着 .sh 扩展名来调用脚本。

如何让文件可执行

要让文件可执行,可以使用 Linux 的 chmod 命令(change mode 的缩写)。Mode 包含写、读、执行等文件权限。

chmod +x image

这里,x 表示把文件设为可执行,后面是你想要设为可执行的文件名(image)。

如何把你的目录添加到 system/machine Path 中

要让脚本在你打开的任何终端、任何目录下都能使用,就需要把你的脚本注册到存放全局脚本和命令的系统路径中。

为此,先复制你目录的完整路径,然后在终端输入以下命令:

export PATH="$PATH:<YOUR_DIRECTORY_LOCATION>"

把 <YOUR_DIRECTORY_LOCATION> 替换成你的目录。这条命令会把脚本目录添加到当前终端中。但如果你切换到另一个终端再运行脚本,就会失败。

现在我们把目录配置为全局的,让每个终端都能执行脚本:

nano ~/.bash_profile

执行后你应该会看到类似这样的内容:

Nano editor for bash profiles and its contents

从上图可以看到,我已经在文件中声明了自己的路径(见选中文本部分)。

如果文件里没有这行内容,可以直接把 export PATH="$PATH:<YOUR_DIRECTORY_LOCATION>" 复制粘贴进去。完成后按 Ctrl+O 再按 enter 保存,然后按 Ctrl+x 退出 nano 编辑器。

接着重新加载机器的路径配置,让脚本目录生效:

source ~/.bash_profile

这样之后,你就可以在任何终端里直接用脚本文件名来运行脚本了(我的文件名是 image):

成功执行 Docker 镜像创建脚本的输出结果

结语

在本指南中,你学会了如何编写自己的脚本来实现工作流自动化。希望能帮你减少在重复性任务上浪费的时间和精力,这些其实都可以通过脚本命令来自动完成。

本指南示例中使用的完整 CLI 工具可以在这个 git 仓库中找到,里面有安装指南和使用说明。如果觉得有帮助,欢迎给仓库点个 Star,也可以分享给可能有需要的同事。

谢谢!

原始来源: freeCodeCamp

评论 (0)