在Java中如何更改当前工作目录?

190

如何在Java程序中更改当前工作目录?关于这个问题,我查到的所有信息都声称你无法更改当前工作目录,但我不相信这是真的。

我有一段代码,使用一个硬编码的相对文件路径打开一个文件,该文件位于正常启动的目录下,我想要能够在不必从特定目录启动它的情况下,在另一个Java程序中使用该代码。调用 System.setProperty("user.dir", "/path/to/dir") 应该可以解决此问题,但据我所知,调用该行代码只会悄无声息地失败并且没有任何效果。

如果Java不允许你这样做,我会理解的,但事实上Java允许获取当前工作目录,甚至允许使用相对文件路径打开文件....


1
获取和使用信息与更改信息是不同的。例如,在Windows上,您可以轻松获取环境变量,但更改它们(以系统范围的方式)则更加困难。 - PhiLho
1
https://bugs.java.com/bugdatabase/view_bug.do?bug_id=4045688 在评估部分指出:“自那时以来,没有进一步的客户站出来或被识别出来...”,截至2018年,我们已经有了大约175,000次查看这个问题 :-( - Wolfgang Fahl
14个回答

163

在纯Java中没有可靠的方法来做到这一点。通过System.setProperty()java -Duser.dir=...设置user.dir属性似乎会影响后续对Files的创建,但不会影响例如FileOutputStreams

如果您将目录路径与文件路径分开构建,则File(String parent, String child)构造函数可以帮助您,从而更容易地进行交换。

另一种选择是设置脚本以从不同的目录运行Java,或使用JNI本机代码如下所建议

相关的OpenJDK错误在2008年被关闭为"不会修复"。


23
我认为我没有发现Java和C#之间的任何一个差异,使我想到,“那些Java的人真的知道他们在做什么”。 - Jake
3
很难相信 Java 至少没有一个参数“在此目录中启动...” - rogerdpack
10
为Java辩护一下(我是一个喜欢这个特性的UNIX用户)……它是一个虚拟机,旨在对操作系统的具体细节保持中立。在某些操作系统中,“当前工作目录”习惯用语是不可用的。 - Tony K.
14
公平地说,Java必须率先完成这项工作,而C#则在许多领域受益于从Java的错误中吸取教训。 - Ryan Leach
3
经进一步调查,似乎user.dir只对某些类有效,包括我最开始测试的那个类。即使程序更改了user.dir,使用new FileOutputStream("foo.txt").close();仍会在原始工作目录创建文件。 - Michael Myers
显示剩余4条评论

43

1
这是我采用的路线。我能够通过以下方式从不同的工作目录运行可执行文件: File WorkingDir = new File("C:\path\to\working\dir\"); ProcessBuilder pBuilder = new ProcessBuilder("C:\path\to\working\dir\executable.exe"); pBuilder.directory(WorkingDir); Process p = pBuilder.start(); - CatsAndCode

30

使用系统属性"user.dir"可以实现这一点。关键是要理解必须调用getAbsoluteFile()(如下所示),否则相对路径将相对于默认的"user.dir"值解析。

import java.io.*;

public class FileUtils
{
    public static boolean setCurrentDirectory(String directory_name)
    {
        boolean result = false;  // Boolean indicating whether directory was set
        File    directory;       // Desired current working directory

        directory = new File(directory_name).getAbsoluteFile();
        if (directory.exists() || directory.mkdirs())
        {
            result = (System.setProperty("user.dir", directory.getAbsolutePath()) != null);
        }

        return result;
    }

    public static PrintWriter openOutputFile(String file_name)
    {
        PrintWriter output = null;  // File to open for writing

        try
        {
            output = new PrintWriter(new File(file_name).getAbsoluteFile());
        }
        catch (Exception exception) {}

        return output;
    }

    public static void main(String[] args) throws Exception
    {
        FileUtils.openOutputFile("DefaultDirectoryFile.txt");
        FileUtils.setCurrentDirectory("NewCurrentDirectory");
        FileUtils.openOutputFile("CurrentDirectoryFile.txt");
    }
}

5
但它不会改变当前的工作目录,只会改变user.dir的值。绝对路径变得关键的事实证明了这一点。 - user207421

23

使用JNA/JNI调用libc函数库,可以更改PWD。JRuby的开发者们有一个方便的Java库来进行POSIX系统调用,称作jnr-posix。这里是Maven信息


2
似乎没有现代版本的jna-posix。我进行了分支并添加了一个:http://github.com/pbiggar/jnr-posix。我可以确认使用这个库可以更改PWD。 - Paul Biggar
是的。抱歉,我重构了那个文件,忘记了这个答案链接到该文件。已修复。 - Allen Rohner
2
你可以这样做,但如果你不改变user.dir系统属性,那么File.getAbsolutePath()将会解析为user.dir,而在File中的路径名则会解析为操作系统的工作目录。 - Morrie
关于原问题,在Java中如何使用jnr-posix更改当前工作目录。我需要创建哪个类的实例来使用chdir方法?我并没有真正理解给出的Clojure示例。提前致谢。 - Sam Saint-Pettersen

20

正如前面提到的,你不能改变JVM的当前工作目录,但是如果您使用Runtime.exec()启动另一个进程,则可以使用重载方法来指定工作目录。这并不是为了在另一个目录中运行您的Java程序,而是针对许多情况,例如需要启动另一个程序(比如Perl脚本)时,您可以指定该脚本的工作目录,同时保持JVM的工作目录不变。

参见Runtime.exec javadocs

具体来说,

public Process exec(String[] cmdarray,String[] envp, File dir) throws IOException

其中dir是运行子进程的工作目录


2
这个答案似乎比被接受的答案更好 (被接受的答案开头是"There is no reliable way to do this in pure Java.")。是否有办法申请将这个答案作为被接受的答案? - John

10

如果我理解正确的话,Java程序启动时会生成一个当前环境变量的副本。通过System.setProperty(String, String)方法所做的任何更改都是修改该副本,而不是原始的环境变量。这并不能完全解释Sun为什么选择了这种行为,但或许能够让人对此有一些初步的认识...


4
你似乎混淆了环境变量和属性。前者会从操作系统继承,而后者可以使用“-D”在命令行上定义。但我同意,在JVM启动时,像“user.dir”这样的预定义属性会从操作系统复制,并且稍后更改它们是无效的。 - maaartinus
1
更改 user.dir 会影响 File.getAbsolutePath()File.getCanonicalPath(),但不会影响操作系统的工作目录概念,该概念决定了访问文件时如何解析文件路径名。 - Morrie

6

工作目录是操作系统的一个特性(在进程启动时设置)。为什么不直接传递您自己的系统属性(-Dsomeprop=/my/path),并在代码中将其用作文件的父级呢?

File f = new File ( System.getProperty("someprop"), myFilename)

因为这段代码包含了一个硬编码的文件路径,并且可能使用了一个硬编码的构造函数,没有指定父目录来工作。至少,这是我所面临的情况 :) - David Mann

4
更聪明/更简单的做法是改变你的代码,不要假设文件存在于当前工作目录中(我猜你正在做类似于new File("blah.txt")的事情),而是自己构建文件路径。
让用户传递基本目录,从配置文件中读取它,如果找不到其他属性,则回退到user.dir。但是改进程序逻辑比改变环境变量的工作方式要容易得多。

2

System.setProperty("user.dir", "/some/directory")

之后,您可以使用

new File("relative/path").getAbsoluteFile()

来获取绝对路径。

System.setProperty("user.dir", "C:/OtherProject");
File file = new File("data/data.csv").getAbsoluteFile();
System.out.println(file.getPath());

将会打印

C:\OtherProject\data\data.csv

1
请注意,这种行为在Java 11中发生了变化,请参见https://bugs.openjdk.java.net/browse/JDK-8202127。他们不建议使用`System.setProperty("user.dir", "/some/directory")`。 - Martin

2
我尝试调用 String oldDir = System.setProperty("user.dir", currdir.getAbsolutePath()); 它似乎有效。但是, File myFile = new File("localpath.ext"); InputStream openit = new FileInputStream(myFile); 抛出了FileNotFoundException异常,尽管 myFile.getAbsolutePath() 显示了正确的路径。 我已经阅读过这个。我认为问题是:
  • Java使用新设置知道当前目录。
  • 但是文件处理是由操作系统完成的。它不知道新设置的当前目录,不幸的是。
解决方法可能是: File myFile = new File(System.getPropety("user.dir"), "localpath.ext"); 它将创建一个文件对象作为绝对路径,并使用JVM所知道的当前目录。但是,该代码应存在于一个使用的类中,需要更改重用的代码。
~~~~JcHartmut

“它创建了一个文件对象” - 是的。但它并没有在文件系统中创建文件。你忘记在此之后使用file.createNewFile()了。 - Gangnus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接