1

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?

OneCricketeer
  • 179,855
  • 19
  • 132
  • 245
Kristianasp
  • 61
  • 11
  • Can you please post the full stack trace? – Abdulgood89 Dec 22 '16 at 21:20
  • There is no stack trace. The error I posted is the full error message I get. – Kristianasp Dec 22 '16 at 21:21
  • Why do you need to escape the backslashes in the string? `"\\ Player"` – OneCricketeer Dec 22 '16 at 21:23
  • When running this through the terminal, the path I would use would be `".../Music\ Player/.."` to escape the whitespace in between. In the code, it would be double backslash to escape the 2nd backslash so that it will appear in the command. I've tried no backslashes as well, but no success. A single backslash will cause a compilation error and stop IntelliJ from executing my code – Kristianasp Dec 22 '16 at 21:27
  • Your string "prints" in Java just fine. So, I assume the "syntax error" is coming from the shell, not Java – OneCricketeer Dec 22 '16 at 21:30
  • By the way -- I think your problem is the input redirection. `<` https://stackoverflow.com/questions/2563337/how-to-redirect-stdin-to-java-runtime-exec?rq=1 – OneCricketeer Dec 22 '16 at 21:31
  • I would recommend to always use the multi-argument array variant of exec(), it reduces (a bit) the need for escaping. – eckes Dec 23 '16 at 21:32

2 Answers2

1

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.

Community
  • 1
  • 1
Roberto Attias
  • 1,883
  • 1
  • 11
  • 21
  • That makes sense. Cheers. I've tried to follow the example in the link you provided but my application stops responding straight away and the SQL script is not executed. – Kristianasp Dec 22 '16 at 22:07
  • Please see my answer, I have given example code of how to fix both problems. – Emily Mabrey Dec 22 '16 at 22:09
1

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();
Community
  • 1
  • 1
Emily Mabrey
  • 1,528
  • 1
  • 12
  • 29
  • This is giving me a `Java.IO.FileNotFoundException - database.sql (No such file or directory)`despite being able to see the file there. I can also, using the terminal, change to the directory the program is using and see the file through ls. This is however an absolute path and I am not planning on using this any more than initially trying to get the .SQL script to execute – Kristianasp Dec 22 '16 at 22:28
  • Replace `File inputSqlFile = new File("database.sql");` with `File inputSqlFile = new File(parentDirectory.toFile(),"database.sql").getAbsoluteFile();` and see if that fixes it please. – Emily Mabrey Dec 22 '16 at 23:55
  • And just to check,`database.sql` and `database.db` are both in the same directory (`/Users/Documents/Uni/Music Player/src/Common/`), right? – Emily Mabrey Dec 22 '16 at 23:57
  • Yes, both files are in the directory. I'll have a go at the proposed fix when I get home from work and give you an update then. – Kristianasp Dec 23 '16 at 12:03
  • This is now giving a `Cannot run program "sqlite3 database.db" - Error=2 No such file or directory`after implementing your solution. – Kristianasp Dec 23 '16 at 19:18
  • @Kristianasp I have added some edits to the original answer containing new fixed (_hopefully_) code with and without debugging statements. Please use the version **with debugging statements** with **assertions enabled** and tell me what the programs writes to `System.out` and also what errors, if any, the program generates. – Emily Mabrey Dec 23 '16 at 21:02
  • 1
    Cheers, this worked! The only modification I had to make was to add a `"/"`in front of `"Users"` to make it `"/","Users"`.Otherwise it wouldn't use the correct absolute path. However, I have made it work with a relative path now instead of an absolute path. Cheers for the help, much appreciated. – Kristianasp Dec 23 '16 at 21:59