← 返回蜂巢洞察

如何使用Shell命令自动化生成用于Postgres的Docker镜像

如果在项目中某些代码行需要反复被执行,那么有一个相当标准的解决方法:使用函数。 但当同一个项目文件的结构需要被多次重复使用时,该如何解决这个问题呢? 这时就可以使用bash脚本了。Bash脚本的作用是自动在你的机器上执行一系列命令,而你无需亲自操作。这就是所谓的“工作流程自动化”,而在本文中,我将会教你如何实现这一点。 目录 先决条件 这个项目的起源是什么? 引言 Bash中的条件语句 交互式Shell 如何创建文件及向其中写入内容 Bash参数与标志 如何使你的脚本可执行,并使其出现在PC的路径中 结论 先决条件 要顺利阅读本文,你需要满足以下条件: 你的机器上已经安装了Git-bash 具

如果在项目中某些代码行需要反复被执行,那么有一个相当标准的解决方法:使用函数。

但当同一个项目文件的结构需要被多次重复使用时,该如何解决这个问题呢?

这时就可以使用bash脚本了。Bash脚本的作用是自动在你的机器上执行一系列命令,而你无需亲自操作。这就是所谓的“工作流程自动化”,而在本文中,我将会教你如何实现这一点。

目录

先决条件

要顺利阅读本文,你需要满足以下条件:

  • 你的机器上已经安装了Git-bash
  • 具备使用终端或命令提示符运行命令的经验(这会有帮助,但并非必需)
  • 了解基本的编程概念,比如条件语句和循环结构
  • 你的机器上已经安装了Docker
  • 对Docker的基本概念有所了解,例如镜像和容器
  • 有使用Postgres的经验(可选,但推荐)

这个项目的起源是什么?

最近我在几个项目中为Postgres创建Docker镜像时,发现自己在创建这些镜像的过程中遵循着相同的步骤。唯一的区别在于它们使用的数据库凭据和名称不同。

我还注意到,如今许多发展迅速的应用程序都会选择使用Supabase作为后端服务,而这类服务通常会存在一些资源限制,比如数据库存储空间、带宽、并发连接数以及API请求次数上限(这些限制会根据服务等级和项目规模有所不同)。而通过合理配置数据库,就可以解决这些问题。

因此,我想到:为什么不开发一个CLI工具来自动化创建Postgres的Docker镜像呢?正是这时,我开始学习如何使用bash编写可执行的shell命令,从而实现工作流程的自动化。

你可以在这个git仓库中找到本指南中使用的完整示例代码。

引言

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 "文件已经存在"
        exit 0
fi

echo "接下来执行下一条命令"

在if条件判断中,我们使用了-s标志来检查文件是否存在且内容是否非空。-s这个标志用于检测文件的可用性及权限信息,类似的标志还包括:

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

在这段脚本中,我们使用-s来确认名为docker-compose.yml的文件是否存在于当前命令执行的目录中。如果文件不存在,就会输出“接下来执行下一条命令”;如果文件存在且有内容,就会输出“文件已经存在”。

exit在bash脚本中起着类似于“return”关键字的作用:0表示成功,1表示失败。

让我们通过创建一个Docker Compose文件,并在同一目录下添加一些内容来测试这些功能。在终端中输入bash 文件名.sh,你应该会看到“该文件已存在”的提示:

使用bash创建现有文件后的输出

但如果我们删除文件中的内容,然后再运行这个命令,就会看到“下一个命令”的提示:

使用bash成功创建文件后的输出

交互式Shell

为了让自动化脚本具有动态性和灵活性,你就需要接收用户的输入。例如,数据库的名称可能不会一直保持不变,它会根据具体的项目需求而发生变化。

在为新的Docker镜像创建数据库时,你确实需要指定数据库的名称;为此,你可以使用read关键字来获取用户输入。

根据不同的操作类型,存在多种类型的输入方式。还有一些标志用于指定你期望接收哪种类型的输入,例如:

  • -p:用于提示用户输入文本
  • -s:在输入密码时使用,这样用户在终端中输入密码的内容就不会被显示出来
  • -a:当你需要用户提供多个输入值,或者期望用户输入一个数组时使用。

以下是获取用户输入的语法:

read -p '输入标签: ' 变量名

示例代码:

现在,让我们编写一些脚本,来接收用户关于所要创建的Postgres数据库镜像的相关信息。

#!/bin/bash

# 数据库连接信息
read -p '数据库名称(*): ' db
read -p '数据库用户名(*): ' user
read -sp '数据库密码(*): ' pwd
echo
echo "你创建的数据库为 -> $db,用户名为 $user,密码为 $pwd"

在这个脚本中,read关键字使用了-p选项来提示用户输入文本,而-sp选项则用于让用户在隐藏输入内容的情况下输入密码。

如果你不同时使用-p和-s选项,就无法显示提示用户输入密码的界面。单独使用-s选项时,虽然用户的输入内容会被隐藏,但也不会有任何提示信息;除非你使用echo或printf来显式地输出提示信息。

<如果要打印或使用用户输入的内容,可以使用美元符号“$”后跟上表示用户需要输入的变量的名称。在上面的例子中,我们将用于获取数据库名称的变量命名为db,因此当我们需要获取用户输入的数据库名称时,就会使用$db这个变量。

现在,如果你在终端中运行这个脚本,你会看到一系列提示符,等待用户输入数据。一旦这些字段被填入相应的值,你就会在终端中看到所有输入的内容:

输出内容,展示了如何获取并显示用户的输入信息

如何验证和清理输入数据

在执行任何操作之前,你都需要先验证用户输入的内容。这一点非常重要,因为有时候某些关键字段可能会被遗漏或输入错误。例如,你可能会忘记为要创建的图像指定数据库名称。

在验证输入数据时,你可以使用以下标志之一(还有其他标志也可以使用):

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

对于每一个需要确保其内容不为空的输入字段,你都可以使用-z标志来进行验证:

#!/bin/bash

# 数据库连接信息
read -p '数据库名称(*): ' db
if [[ -z $db ]]
    then
        echo "数据库名称不能为空!"
        exit 1
fi

read -p '数据库用户名(*): ' user
if [[ -z $user."]
    then
        echo "数据库用户名不能为空!"
        exit 1
fi

read -sp '数据库密码(*): ' pwd
if [[ -z $pwd ]]
    then
        echo "密码不能为空!"
        exit 1
fi

这个脚本能帮助你在继续进行下一步操作之前,确保所有与数据库相关的关键信息都已经填写完毕。

默认值

对于某些字段,你可能希望它们使用默认值(比如图像的容器名称、容器的端口号或卷名等)。在这种情况下,你可以使用:=来为变量设置默认值:

#!/bin/bash

# 容器信息
read -p '容器名称: ' container_name
read -p '容器端口号(如果省略,则容器会使用PostgreSQL的默认端口5432): ' port
read -p '卷名: ' vol_name

echo "容器名称-> ${container_name:=cont_one}, 端口号->${port:=5432}, 卷名->${vol_name:=backend_data}"

当你运行这个脚本并跳过了这三个输入字段时,bash会使用你设置的默认值作为替代方案。

设置变量默认值的输出结果

但如果你为这些字段提供了具体的值,bash就会使用你提供的那些值。例如,如果你为这三个字段都输入了具体的数值,那么程序就会使用这些值来执行后续操作。

接收用户输入并显示所接收的内容

如何创建文件并向其中写入内容

现在您已经知道了如何为Postgres数据库创建Docker镜像,接下来需要创建一个`docker-compose.yml`文件,该文件会在启动或停止容器时被执行。

  • 首先,可以使用`> 文件名`。如果文件不存在,这种方法会创建该文件;如果文件已经存在,其内容会被覆盖。
  • 还有`>> 文件名`:这种方法是向文件中追加内容,如果文件不存在,则会创建该文件。
  • 最后是`touch 文件名`:这种方法会在文件不存在时创建它;如果文件已经存在,其内容保持不变,只会更新文件的创建时间戳。

如何使用`>`符号来创建文件并向其中写入内容

当您想要同时创建一个文件并向其写入内容时,可以在`>`符号前面加上`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.yml`文件。

如何向文件中写入多条不同的内容

您也可以使用`cat`命令来向文件中写入内容。通常情况下,`cat`用于读取文件的内容,但当它与`<>>`符号结合使用时,就可以按照您想要的格式向文件中添加多条内容了。其语法如下:


# 向文件中写入docker-compose配置内容

cat << EOF >> docker-compose.yml

  # 文件的具体内容
EOF
这个指令告诉bash将内容写入文件,直到遇到EOF关键字(即文件的结尾)。然后你可以使用<>> filename将这些内容追加到文件中。在Docker Compose环境中,代码会如下所示:
#!/bin/bash

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

cat<>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

# 数据库凭据

if [[ -z $1 ]]; then
    read -p '数据库名称(*): ' db
 else
    db="$1"
fi

# 检查并验证数据库信息

if [[ -z $2 ]; then
    read -p '数据库用户名(*): ' user
 else
    user="$2"
fi

# 检查并验证用户名

if [[ -z $1 ]]; then
    read -sp '数据库密码(*): ' pwd
 else
    pwd=$3
fi

# 验证密码

首先,脚本会检查是否有参数传入。如果没有参数,就会提示用户输入相关信息;如果有参数,这些参数的值会被赋给相应的变量,用于表示数据库的名称、密码等信息。现在你可以带着参数来运行这个脚本了:

bash image.sh customers ABC-Company secret123!

通过上述脚本创建的容器将使用customers作为数据库名称,ABC-Company作为用户名,密码为secret123!。

Bash标志/选项

脚本中有一部分会要求用户提供关于所创建容器的信息,但这些信息其实是可选的。通过使用-D标志,你可以跳过这一环节,从而让脚本直接使用默认的容器配置信息来运行。

下面是一个例子:


#!/bin/bash

while getopts "ny" options; do
	case "$options" in
		n) echo "你选择了“不”;";
		y) echo "你选择了“是”;";
		*) echo “不存在这样的选项/标志”; exit 1;;
    esac
done

shift $((OPTIND - 1))

在上述脚本中:

  1. 只要`getopts`的结果不为空,while循环就会持续运行。
  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 "没有这样的选项/标志"; exit 1;;
    esac
done

shift $((OPTIND - 1))

if [[ $ask_container_info == true ]]; then
	# 提示用户输入自定义容器信息
    read -p '容器名称: ' container_name
    read -p '容器端口(如果省略,容器将使用PostgreSQL的默认端口5432): ' port
    read -p '卷名: ' vol_name
fi

这里只有一个选项,因此选项列表中只列出了`D`。变量`ask_container_info`的初始值为`true`,这意味着除非用户明确输入了`-D`选项,否则脚本会提示用户输入容器信息。当用户执行脚本并传递`-D`选项时,`ask_container_info`的值会被设置为`false`。

如何使你的脚本可在电脑路径中被执行

下一步是让这个脚本能够在你使用的所有终端中被执行。目前,这个脚本只能在你创建和编写它的当前目录中被运行。

首先,我们需要将脚本文件设置为可执行文件,并去掉文件的扩展名,这样就不必每次运行脚本时都加上`.sh`扩展名了。

如何使文件可执行

要使一个文件可执行,可以使用Linux系统的`chmod`命令。这个命令用于修改文件的权限设置,比如写入权限、读取权限和执行权限等。

chmod +x image

在这里,x命令会使文件具有可执行性,而你指定的文件名image就是想要被设置为可执行文件的文件。

如何将你的目录添加到system/machine路径中

为了让你在打开的任何终端中都能使用这些脚本,无论当前处于哪个目录下,你都需要将这些脚本添加到系统中存储所有全局脚本和命令的路径中。

具体操作方法是:复制你的目录的完整路径,然后在终端中输入以下命令:

export PATH="$PATH:<YOUR_DIRECTORY_LOCATION>"

将替换成你的目录路径。这条命令会将该脚本目录添加到当前工作的终端环境中。但是,如果你尝试切换到另一个终端来运行这个脚本,它将会失败。

现在让我们将这个目录设置为全局路径,这样所有终端就能够执行这些脚本了:

nano ~/.bash_profile

执行这条命令后,你应该会看到类似下面的内容:

用于编辑bash配置文件的Nano编辑器及其显示的内容

从上面的图片中你可以看到,我已经在文件中配置了路径信息。

如果你的文件中还没有这个配置,你可以将export PATH="$PATH:<YOUR_DIRECTORY_LOCATION>"这段代码复制并粘贴到文件中。完成后,按Ctrl+O保存文件,然后按Ctrl+x退出Nano编辑器。

接下来,再次重新加载系统路径,以便让这些脚本能够被包含在系统中:

source ~/.bash_profile

这样,你就可以在机器上的任何终端中,仅通过输入脚本文件名来运行它了(我给这个文件取名为image)。

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

总结

通过本指南,你学会了如何创建自己的脚本来自动化工作流程。希望这些内容能帮助你减少在那些可以通过脚本命令自动完成的重复性任务上花费的时间和精力。

我们在这个指南中作为示例使用的完整CLI工具,你可以从这个git仓库中获取。在那里,你还可以找到安装指南和使用技巧。如果你觉得这个资源有用,请考虑给这个仓库添加星标,并与可能从中受益的同事们分享它。

谢谢!

相关文章

技术实践

如何使用JavaScript开发一款可将基于浏览器的图像故事板转换为PDF格式的工具

在任何人开始拍摄视频、制作广告或绘制漫画的最终画面之前,通常会先画出一份故事板草图:按照拍摄顺序为每一镜头绘制一幅图片,并附上简短的说明,说明该镜头中发生了什么以及持续时间有多长。 大多数人已经拥有这些图片了——它们可能是手机照片、截图、粗略的草图,或者是通过人工智能生成的图像文件,这些文件都保存在某个文件夹里。但他们所缺乏的是一种能够将这些文件快速整理成格式规范、编号整齐的文档,并将其打印出来或发送给客户的工具。 在这个教程中,你将学习如何制作这样一个基于浏览器的工具。你需要添加一组图片,为每张图片指定标题、描述和持续时间,选择合适的布局方式,预览生成的文档,然后下载可供打印的PDF文件。

阅读全文
技术实践

如何利用人工智能提示更快地构建网站用户界面

过去,创建网站的用户界面需要花费数小时的时间来手动编写HTML、CSS代码以及各种组件代码。但现在,你只需用简单的文字描述页面的结构,就可以让人工智能编码工具生成最初的代码版本。 不过,最终生成的代码质量取决于你提供的指令。如果指令过于简短或模糊,那么生成的代码可能只是包含随机颜色和重复代码的简单页面;而如果你能提供详细的说明——包括所使用的技术栈、文件结构以及设计规则,那么工具就能生成可实际使用的代码。 在本教程中,你将学习如何编写这样的指令。通过这些指令,你可以使用Next.js、Tailwind CSS以及shadcn/ui组件来构建一个带有固定导航栏的全屏页面。 完成本教程后,你将能够

阅读全文
技术实践

如何使用Next.js、AWS以及沙箱环境来构建属于自己的、类似于Lovable这样的AI应用开发工具

像Lovable、Bolt这样的工具,第一次使用的时候真的会让人觉得像是魔法一样。说实话,我第一次看到这类东西的时候简直惊呆了……而且这一切都是通过聊天窗口来完成的!真是太不可思议了。第一次尝试使用Lovable的时候,我甚至产生了一场关于“存在意义”的哲学思考。 但你们有没有想过,其实这些工具内部到底发生了什么?比如说,究竟是怎么让一个应用程序能够生成另一个可以用于测试、分享或下载的应用程序的呢? 显然,在某个地方有一个AI模型在编写代码。但这个过程具体是如何进行的呢?那些代码需要被安装、编译并运行才行。而我有点怀疑的是,现在似乎没有人会在这些代码运行之前去仔细检查它们。这些代码可能会出现错

阅读全文
技术实践

由 TypeScript 7 提供支持的 Angular 编译器已进入测试阶段,该编译器配备了基于 Rust 的预处理工具。

Angular团队推出了ngp这一编译器预处理器,该工具利用Oxc重新实现了Rust语言中的分析阶段处理流程。它取代了ngc对Transformer API的依赖——而TypeScript 7.0已经移除了对Transformer API的支持。早期的测试结果显示,使用ngp进行冷构建时,编译速度可以提高2到3倍。预计这一新技术将在Angular 22的次要版本中正式被采用;在此之前,生产环境仍然会继续使用ngc进行编译。 作者:Daniel Curtis

阅读全文