这里的许多答案都有严重的缺陷......
首先不要尝试自己解析开放式 sql 脚本!如果您认为这很容易做到,那么您不知道 sql 的强大和复杂程度。严肃的 sql 脚本肯定涉及跨越多行的语句和过程定义。在脚本中间显式声明和更改分隔符也很常见。您还可以将源命令相互嵌套。由于许多原因,您希望通过 MySQL 客户端运行脚本并允许它处理繁重的工作。试图重新发明它是充满危险和巨大的时间浪费。也许如果你是唯一一个写这些脚本的人,而且你没有写任何复杂的东西,你可以逃脱惩罚,但为什么要把自己限制在这样的程度呢?机器生成的脚本或其他开发人员编写的脚本呢?
@jdferreira 的答案是正确的,但也有问题和弱点。最重要的是,通过以这种方式将连接参数发送到进程,正在打开一个安全漏洞。
这里有一个解决方案/示例,让您享受复制和粘贴的乐趣。我的扩展讨论如下:
首先,创建一个单独的配置文件来保存您的用户名和密码。
db-creds.cfg
[client]
user = XXXXXXX
password = YYYYYYY
为其设置正确的文件系统权限,以便python进程可以从中读取,但没有人可以看到谁不应该看到。
然后,使用这个 Python(在我的示例中 creds 文件与 py 脚本相邻):
#!/usr/bin/python
import os
import sys
import MySQLdb
from subprocess import Popen, PIPE, STDOUT
__MYSQL_CLIENT_PATH = "mysql"
__THIS_DIR = os.path.dirname( os.path.realpath( sys.argv[0] ) )
__DB_CONFIG_PATH = os.path.join( __THIS_DIR, "db-creds.cfg" )
__DB_CONFIG_SECTION = "client"
__DB_CONN_HOST = "localhost"
__DB_CONN_PORT = 3306
# ----------------------------------------------------------------
class MySqlScriptError( Exception ):
def __init__( self, dbName, scriptPath, stdOut, stdErr ):
Exception.__init__( self )
self.dbName = dbName
self.scriptPath = scriptPath
self.priorOutput = stdOut
self.errorMsg = stdErr
errNumParts = stdErr.split("(")
try : self.errorNum = long( errNumParts[0].replace("ERROR","").strip() )
except: self.errorNum = None
try : self.sqlState = long( errNumParts[1].split(")")[0].strip() )
except: self.sqlState = None
def __str__( self ):
return ("--- MySqlScriptError ---\n" +
"Script: %s\n" % (self.scriptPath,) +
"Database: %s\n" % (self.dbName,) +
self.errorMsg )
def __repr__( self ): return self.__str__()
# ----------------------------------------------------------------
def databaseLoginParms() :
from ConfigParser import RawConfigParser
parser = RawConfigParser()
parser.read( __DB_CONFIG_PATH )
return ( parser.get( __DB_CONFIG_SECTION, "user" ).strip(),
parser.get( __DB_CONFIG_SECTION, "password" ).strip() )
def databaseConn( username, password, dbName ):
return MySQLdb.connect( host=__DB_CONN_HOST, port=__DB_CONN_PORT,
user=username, passwd=password, db=dbName )
def executeSqlScript( dbName, scriptPath, ignoreErrors=False ) :
scriptDirPath = os.path.dirname( os.path.realpath( scriptPath ) )
sourceCmd = "SOURCE %s" % (scriptPath,)
cmdList = [ __MYSQL_CLIENT_PATH,
"--defaults-extra-file=%s" % (__DB_CONFIG_PATH,) ,
"--database", dbName,
"--unbuffered" ]
if ignoreErrors :
cmdList.append( "--force" )
else:
cmdList.extend( ["--execute", sourceCmd ] )
process = Popen( cmdList
, cwd=scriptDirPath
, stdout=PIPE
, stderr=(STDOUT if ignoreErrors else PIPE)
, stdin=(PIPE if ignoreErrors else None) )
stdOut, stdErr = process.communicate( sourceCmd if ignoreErrors else None )
if stdErr is not None and len(stdErr) > 0 :
raise MySqlScriptError( dbName, scriptPath, stdOut, stdErr )
return stdOut
如果你想测试一下,添加这个:
if __name__ == "__main__":
( username, password ) = databaseLoginParms()
dbName = "ExampleDatabase"
print "MySQLdb Test"
print
conn = databaseConn( username, password, dbName )
cursor = conn.cursor()
cursor.execute( "show tables" )
print cursor.fetchall()
cursor.close()
conn.close()
print
print "-----------------"
print "Execute Script with ignore errors"
print
scriptPath = "test.sql"
print executeSqlScript( dbName, scriptPath,
ignoreErrors=True )
print
print "-----------------"
print "Execute Script WITHOUT ignore errors"
print
try : print executeSqlScript( dbName, scriptPath )
except MySqlScriptError as e :
print "dbName: %s" % (e.dbName,)
print "scriptPath: %s" % (e.scriptPath,)
print "errorNum: %s" % (str(e.errorNum),)
print "sqlState: %s" % (str(e.sqlState),)
print "priorOutput:"
print e.priorOutput
print
print "errorMsg:"
print e.errorMsg
print
print e
print
为了更好的衡量,这里有一个示例 sql 脚本来输入它:
test.sql
show tables;
blow up;
show tables;
所以,现在开始讨论。
首先,我将说明如何使用 MySQLdb 以及此外部脚本执行,同时将凭据存储在一个共享文件中,您可以同时使用两者。
通过在命令行上使用--defaults-extra-file,您可以安全地传入您的连接参数。
--force 与 stdin 流式传输源命令或 --execute 在外部运行命令的组合让您决定脚本将如何运行。那就是忽略错误并继续运行,或者在发生错误时立即停止。
结果返回的顺序也将通过--unbuffered 保留。否则,您的 stdout 和 stderr 流将按顺序混乱且未定义,当与输入 sql 进行比较时,很难弄清楚哪些有效,哪些无效。
使用 Popen cwd=scriptDirPath 让您可以使用相对路径将源命令嵌套在彼此之间。如果您的脚本都在同一个目录中(或相对于它的已知路径),这样做可以让您引用相对于顶级脚本所在位置的那些。
最后,我加入了一个异常类,它包含您可能想要的关于发生的事情的所有信息。如果您没有使用 ignoreErrors 选项,当出现问题并且脚本因该错误停止运行时,将在您的 python 中抛出这些异常之一。