简体   繁体   English

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

[英]Runtime.exec String gives syntax error

I am using the runtime.exec to try and execute a .SQL script into an SQLite database file I have locally. 我正在使用runtime.exec尝试将.SQL脚本执行到本地具有的SQLite数据库文件中。 I am currently running this on MacOS. 我目前正在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);
        } 

This is the code I am currently using. 这是我当前正在使用的代码。 If I were to execute the command through the terminal, it would execute successfully and my database would be updated with the contents of the .SQL script. 如果我要通过终端执行命令,它将成功执行,并且我的数据库将使用.SQL脚本的内容进行更新。

However, when I execute this through the code, I get the error: 但是,当我通过代码执行此操作时,出现错误:

Error: near "Player": syntax error along with a process exit Error: near "Player": syntax error以及进程退出

I can't for the life of me find out what is wrong with the String syntax. 我一辈子都无法找出String语法出了什么问题。 I've tried multiple different ways to escape the space in the string unsuccessfully. 我尝试了多种不成功的方法来转义字符串中的空格。 Any ideas? 有任何想法吗?

If you look at the documentation for `Runtime.exec(), and follow the expansion of utility methods, you'll read: 如果查看`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(command)创建的StringTokenizer将命令字符串分解为令牌,而无需进一步修改字符类别。”

Now, if you try to break your string with a StringTokenizer on your own you'll see that it's split into: 现在,如果您尝试自己使用StringTokenizer破坏字符串,则会看到它被拆分为:

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

Instead of passing the whole command to exec , use the version of exec which accepts the command and its argument as String[] : 不要将整个命令传递给exec ,而应使用exec的版本,该版本接受命令及其参数为String[]

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

however, readirecting the input like that won't work. 但是,这样重定向输入是行不通的。 When you type a command, that's interpreted by the shell. 键入命令时,shell会解释该命令。 Instead of exec , you may want to user Process as shown in Running external program with redirected stdin and stdout from Java . 除了使用exec ,您可能还想使用通过重定向Java的stdin和stdout运行外部程序中所示的Process

Instead of trying to generate the path to the file using manual hard-coded String , let's make a Path and have it generate the file path for you. 与其尝试使用手动的硬编码String生成文件的路径,不如让我们创建一个Path并让它为您生成文件路径。 This is much less difficult to use, and it has the benefit of generating the correct file path no matter what the operating system or filesystem happen to be. 它使用起来不那么困难,并且无论生成何种操作系统或文件系统,它都有生成正确文件路径的好处。

Replace the current exec command with this to implement that change: 以此替换当前的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());

This runs the "sqlite3 database.db < database.sql" command using the environmental variables inherited from the parent process (the JVM) and runs that command from within the directory specified by parentDirectory , which happens to be the directory you gave in your question. 这将使用从父进程(JVM)继承的环境变量运行“ sqlite3 database.db <database.sql”命令,并从parentDirectory指定的目录中运行该命令,该目录恰好是您在问题中提供的目录。

However, despite fixing the original problem, your code also contains another problem related to the use of file redirection. 但是,尽管解决了原始问题,您的代码也包含与使用文件重定向有关的另一个问题。 File redirection via < or > is a shell construct, and won't work outside of the shell. 通过<>进行的文件重定向是一个shell构造,在shell外部无法使用。 Using Runtime.exec() is basically like using the Run... GUI on Windows, and since file redirection doesn't work there it won't work here. 使用Runtime.exec()基本上就像在Windows上使用Run... GUI一样,由于文件重定向在那里不起作用,因此在这里将不起作用。 You will need to fix the command used in order to address this problem. 您将需要修复用于解决此问题的命令。 Luckily, there is a StackOverflow question addressing this exact problem here . 幸运的是,这里有一个StackOverflow 问题可以解决这个确切的问题

Here is the code with both issues fixed: 这是已修复两个问题的代码:

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();

This code uses 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() method. 此代码使用ProcessBuilder将目录设置为与以前相同的父目录,但是已修改命令以删除文件重定向,而是使用redirectinput()方法实现文件重定向。 This accomplishes the same things as "sqlite3 database.db < database.sql" would if executed within a shell, but this code will work within Java. 如果在外壳程序中执行此操作,将完成与"sqlite3 database.db < database.sql"相同的操作,但是此代码将在Java中运行。


Edit: 编辑:

The above code is not working, so I have attempted to fix it and have also added debugging statements to help track down the problem: 上面的代码无法正常工作,因此我尝试对其进行修复,并添加了调试语句以帮助查找问题:

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();

And here is the same code with the debugging code removed and a slight cleanup: 这是除去调试代码并稍作清除的相同代码:

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();

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM