简体   繁体   中英

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. I am currently running this on 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.

However, when I execute this through the code, I get the error:

Error: near "Player": syntax error along with a process exit

I can't for the life of me find out what is wrong with the String syntax. 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:

"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."

Now, if you try to break your string with a StringTokenizer on your own you'll see that it's split into:

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[] :

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. Instead of exec , you may want to user Process as shown in Running external program with redirected stdin and stdout from Java .

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. 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:

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.

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

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. This accomplishes the same things as "sqlite3 database.db < database.sql" would if executed within a shell, but this code will work within 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();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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