Runtime.exec 字符串给出语法错误

Runtime.exec String gives syntax error

我正在使用 runtime.exec 尝试将 .SQL 脚本执行到我本地的 SQLite 数据库文件中。我目前在 MacOS 上 运行 这个。

    Runtime runtime = Runtime.getRuntime();
    try
    {
        Process process = runtime.exec("sqlite3 /Users/Documents/Uni/Music\ Player/src/Common/database.db  < /Users/Documents/Uni/Music\ Player/src/Common/database.sql");

        BufferedReader stdError = new BufferedReader(newInputStreamReader(process.getErrorStream()));

        try {
            if (process.waitFor() != 0) {
                String s = "";
                System.err.println("exit value = " + process.exitValue());
                System.out.println("Here is the standard error of the command (if any):");
                while ((s = stdError.readLine()) != null) {
                    System.out.println(s);
                }
            }
        } catch (InterruptedException e) {
            System.err.println(e);
        } 

这是我目前使用的代码。如果我通过终端执行该命令,它将成功执行并且我的数据库将更新为 .SQL 脚本的内容。

但是,当我通过代码执行此操作时,出现错误:

Error: near "Player": syntax error 随着进程退出

我这辈子都找不到 String 语法有什么问题。我尝试了多种不同的方法来成功转义字符串中的 space 。有任何想法吗?

如果您查看 `Runtime.exec() 的文档,并遵循实用方法的扩展,您将阅读:

"More precisely, the command string is broken into tokens using a StringTokenizer created by the call new StringTokenizer(command) with no further modification of the character categories."

现在,如果您尝试自己用 StringTokenizer 拆分字符串,您会看到它被拆分为:

sqlite3
/Users/Documents/Uni/Music\
Player/src/Common/database.db
<
/Users/Documents/Uni/Music\
Player/src/Common/database.sql

不要将整个命令传递给 exec,而是使用接受命令及其参数的 exec 版本 String[]:

runtime.exec(new String[]{
    "sqlite3",
    " /Users/Documents/Uni/Music Player/src/Common/database.db"
    });

但是,像那样重新定向输入是行不通的。当您键入命令时,shell 会对其进行解释。如 Running external program with redirected stdin and stdout from Java.

所示,您可能想要使用 Process 而不是 exec

而不是尝试使用手动硬编码 String, let's make a Path 生成文件路径并让它为您生成文件路径。这使用起来要容易得多,而且无论操作系统或文件系统是什么,它都有生成正确文件路径的好处。

用这个替换当前的 exec 命令以实现该更改:

Path parentDirectory = Paths.get("Users","Documents","Uni","Music Player","src","Common");
String sqlCommand = "sqlite3 database.db < database.sql";        
Process process = runtime.exec(sqlCommand, null, parentDirectory.toFile());

这使用从父进程(JVM)继承的环境变量运行 "sqlite3 database.db < database.sql" 命令,并从 parentDirectory 指定的目录中运行该命令,该目录恰好是您提出了你的问题。

但是,尽管解决了原始问题,您的代码还包含另一个与使用文件重定向相关的问题。通过 <> 的文件重定向是一个 shell 结构,在 shell 之外不起作用。使用 Runtime.exec() is basically like using the Run... GUI on Windows, and since file redirection doesn't work there it won't work here. You will need to fix the command used in order to address this problem. Luckily, there is a Whosebug question addressing this exact problem here.

这是修复了两个问题的代码:

Path parentDirectory = Paths.get("Users","Documents","Uni","Music Player","src","Common");
String sqlCommand = "sqlite3 database.db"; 
File inputSqlFile = new File("database.sql");        

Process process = new ProcessBuilder()
        .directory(parentDirectory.toFile())
        .command(sqlCommand)
        .redirectInput(Redirect.from(inputSqlFile))
        .start();

这段代码使用了ProcessBuilder to set the directory to the same parent directory as before, but the command has been modified to remove the file redirection and instead the file redirection is achieved using the redirectinput()方法。如果在 shell 中执行,这将完成与 "sqlite3 database.db < database.sql" 相同的事情,但此代码将在 Java.

中运行

编辑:

以上代码无效,因此我已尝试修复它并添加了调试语句以帮助查找问题:

Path databasePath = FileSystems.getDefault().getPath("Users", "Documents", "Uni", "Music Player", "src", "Common", "database.db");
Path sqlPath = FileSystems.getDefault().getPath("Users", "Documents", "Uni", "Music Player", "src", "Common", "database.sql");

File database = databasePath.toFile().getAbsoluteFile();
File sqlFile = sqlPath.toFile().getAbsoluteFile();

System.out.println("Database location:\n\t" + database.getCanonicalPath());
System.out.println("SQL file location:\n\t" + sqlFile.getCanonicalPath());

assert database.exists() && database.isFile() && database.canRead() && database.canWrite(): "No such database file!";
assert sqlFile.exists() && sqlFile.isFile() && database.canRead() : "No such SQL file!";

String[] sqlCommand = {"sqlite3", database.getCanonicalPath()};
Redirect sqlInput = Redirect.from(sqlFile);
File workingDirectory = database.getParentFile().getCanonicalFile();

System.out.println("Running this command:\n\t" +
sqlCommand[0] + sqlCommand[1] + "<" + sqlInput.file().getCanonicalPath());

Process process = new ProcessBuilder()
        .directory(workingDirectory)
        .command(sqlCommand)
        .redirectInput(sqlInput)
        .start();

下面是相同的代码,删除了调试代码并稍作清理:

File database = FileSystems.getDefault()
    .getPath("Users", "Documents", "Uni","Music Player", "src", "Common", "database.db")
    .toFile().getAbsoluteFile();
File sqlFile = FileSystems.getDefault()
    .getPath("Users", "Documents", "Uni","Music Player", "src", "Common", "database.sql")
    .toFile().getAbsoluteFile();

String[] sqlCommand = {"sqlite3", database.getCanonicalPath()};
Redirect sqlInput = Redirect.from(sqlFile);
File workingDirectory = database.getParentFile().getCanonicalFile();

Process process = new ProcessBuilder()
        .directory(workingDirectory)
        .command(sqlCommand)
        .redirectInput(sqlInput)
        .start();