返回

MySQL命令行存储图片:Bash脚本高效方案

mysql

通过命令行存储图片至 MySQL

直接在 MySQL 命令行客户端中存储图片通常会遇到一些挑战,特别是处理二进制数据时。本篇文章将分析原因并提供可行方案,以便用户在 Bash 脚本中顺利将图像数据插入 MySQL 数据库。

问题分析

主要问题在于,通过 Bash shell 直接向 mysql 客户端传递含有二进制数据的 SQL 查询字符串时,由于特殊字符的存在,容易产生语法错误。特别是图像数据中包含的引号、反斜杠等,可能会与 SQL 语法产生冲突,导致 SQL 命令解析失败。 此外,当使用 cat 命令读取图像数据时,直接将其放入 SQL 语句中的做法会因为shell处理特殊字符而引发意外。脚本中的报错ERROR at line 1: Unknown command '\l'. 就是证明:mysql将图片的二进制数据部分解析成无效的命令字符。

解决方案

下面给出几种避免此类问题的策略。

方案一: 使用十六进制编码

最有效的方式是将图片数据转换为十六进制字符串,再使用 MySQL 的 X'...' 语法插入。

步骤:

  1. 将图片转换为十六进制格式。
  2. 将生成的十六进制字符串插入到 SQL 查询中。

示例代码:

# 假定 $1 包含图像文件名
tmp="/tmp/$1"
/usr/local/bin/magick \
    "$1" \
    -thumbnail 256x256 \
    -unsharp 1.5x1+0.7+0.02 \
    ${tmp}

# 将图片转成十六进制字符串
hex_thumb=$(xxd -p < ${tmp} )

fullpath=$(realpath $1)
dirpath=$(dirname "${fullpath}")
basename=$(basename "${fullpath}")

# 构建 SQL 语句, 使用 X'...' 表示十六进制字符串
sql="INSERT INTO Bird_Images SET \
    \`Master_Guide\` = \"${handle}\", \
    \`Thumb\` = X'${hex_thumb}', \
    \`Path\` = \"${dirpath}\", \
    \`Filename\` = \"${basename}\""

# 执行 SQL 查询
/usr/local/bin/mysql \
    --user=GPS_User \
    --password=ReSu_SpG \
    --host=localhost \
    --database=Personal \
    --execute="${sql}"

解释:
xxd -p 命令用于生成十六进制表示的二进制数据。将输出的内容插入SQL查询中的 X'${hex_thumb}' 部分,使其被MySQL识别为二进制数据。这种方法绕过了shell中的字符串转义问题。

方案二: 使用 LOAD_FILE() 函数 (仅限本地)

如果 MySQL 服务与脚本在同一主机运行,且数据库服务器对执行权限有控制,可以使用 LOAD_FILE() 函数,该函数读取指定路径的本地文件并将其内容插入到数据库中。
使用此方案需要确保MySQL配置中允许该操作。检查 secure_file_priv 配置项设置,可能需要在 MySQL 的配置文件(如 my.cnf)中调整,并重启 MySQL 服务生效。

步骤:

  1. 将图片存储到一个MySQL能够访问的位置。
  2. 在SQL语句中使用 LOAD_FILE()函数读取并插入图像。

示例代码:

# 假设 $1 是图像文件名
tmp="/tmp/$1"
/usr/local/bin/magick \
    "$1" \
    -thumbnail 256x256 \
    -unsharp 1.5x1+0.7+0.02 \
    ${tmp}


fullpath=$(realpath $1)
dirpath=$(dirname "${fullpath}")
basename=$(basename "${fullpath}")
#  将图像临时拷贝到mysql可以读取到的路径, 
mysql_temp="/tmp/mysql_image_temp"
cp $tmp $mysql_temp

#  使用LOAD_FILE 函数构建 SQL 语句
sql="INSERT INTO Bird_Images SET \
    \`Master_Guide\` = \"${handle}\", \
    \`Thumb\` = LOAD_FILE(\"${mysql_temp}\"), \
    \`Path\` = \"${dirpath}\", \
    \`Filename\` = \"${basename}\""

# 执行 SQL
/usr/local/bin/mysql \
    --user=GPS_User \
    --password=ReSu_SpG \
    --host=localhost \
    --database=Personal \
    --execute="${sql}"
#清理临时的图像文件
rm "$mysql_temp"

解释:
LOAD_FILE() 函数直接从文件系统读取图像内容并存入字段中,这种方式避免了在 Bash 脚本中处理二进制数据带来的复杂性。cp 命令拷贝图像文件到数据库服务有访问权限的路径。 注意使用完成后需要删除临时文件,以保持整洁和避免安全风险。

方案三: 使用占位符与准备语句

准备语句允许将参数(如二进制数据)以占位符的方式插入,可以避免参数解析错误,但MySQL命令行本身不支持,可以通过简单的编程脚本处理,例如 Python脚本。

步骤:

  1. 将相关内容写入Python脚本并传递参数,使用Python脚本连接mysql并进行数据操作。
  2. 数据库中接收占位符绑定参数的数据。

Python示例代码(需要安装mysql-connector-python):

#!/usr/bin/env python3
import mysql.connector
import sys
import os
import subprocess


def execute_sql(handle, file_path, user, password, host, database):
    try:
        tmp_file = "/tmp/" + os.path.basename(file_path)
        cmd = ['/usr/local/bin/magick', file_path,
               '-thumbnail', '256x256',
               '-unsharp', '1.5x1+0.7+0.02',
               tmp_file
        ]
        subprocess.run(cmd, check=True)

        with open(tmp_file, "rb") as f:
           image_data = f.read()
        fullpath = os.path.realpath(file_path)
        dirpath = os.path.dirname(fullpath)
        basename = os.path.basename(fullpath)
        os.remove(tmp_file)


        mydb = mysql.connector.connect(
            user=user,
            password=password,
            host=host,
            database=database
        )

        mycursor = mydb.cursor()

        sql = """
        INSERT INTO Bird_Images (Master_Guide, Thumb, Path, Filename) 
        VALUES (%s, %s, %s, %s)
        """
        val = (handle, image_data, dirpath, basename)
        mycursor.execute(sql, val)

        mydb.commit()

    except mysql.connector.Error as err:
        print(f"Error: {err}")

    finally:
       if(mydb and mydb.is_connected()):
         mycursor.close()
         mydb.close()
       


if __name__ == "__main__":
    if len(sys.argv) != 3:
       print(f"Usage: python script.py <file_path> <handle>")
       sys.exit(1)


    file_path=sys.argv[1]
    handle = sys.argv[2]


    user="GPS_User"
    password = "ReSu_SpG"
    host = "localhost"
    database = "Personal"


    execute_sql(handle,file_path, user,password,host, database)

解释:
此脚本使用 mysql-connector-python 库与 MySQL 数据库建立连接,并执行准备好的 SQL 语句。通过使用占位符 (%s) 与值结合,避免了由于图像二进制数据引起的语法错误。此方案增加了Python的依赖。

安全建议

  • 无论采用哪种方法,都要避免将数据库密码直接硬编码在脚本中。使用环境变量或外部配置文件管理密码。
  • 对于LOAD_FILE()方案,请确保数据库配置正确且对路径访问有合适的权限控制,减少安全风险。
  • 请仔细验证所有输入,避免SQL注入。
  • 图片的二进制数据最好事先检查一下是否是真实的图片文件。

通过上述方案,可以在 MySQL 命令行界面下有效地处理图像存储,而不需要使用复杂的脚本或第三方工具,根据实际情况,选择合适的解决方案。