Zmiana bieżącego katalogu roboczego w Javie?

169

Jak mogę zmienić bieżący katalog roboczy z poziomu programu Java? Wszystko, co udało mi się znaleźć na temat problemu, mówi, że po prostu nie możesz tego zrobić, ale nie mogę uwierzyć, że tak jest naprawdę.

Mam fragment kodu, który otwiera plik za pomocą zakodowanej na stałe względnej ścieżki pliku z katalogu, w którym normalnie jest uruchomiony, i chcę tylko móc używać tego kodu z innego programu Java bez konieczności uruchamiania go od wewnątrz określony katalog. Wygląda na to, że powinieneś móc po prostu zadzwonić System.setProperty( "user.dir", "/path/to/dir" ), ale o ile mogę się zorientować, dzwonienie na tę linię po prostu cicho zawodzi i nic nie robi.

Zrozumiałbym, gdyby Java nie pozwoliła ci tego zrobić, gdyby nie fakt, że pozwala ci uzyskać bieżący katalog roboczy, a nawet umożliwia otwieranie plików za pomocą względnych ścieżek plików ...

Nacięcie
źródło
1
Pobieranie i używanie informacji różni się od ich zmiany. Na przykład w systemie Windows można łatwo pobrać zmienne środowiskowe, ale trudniej jest je zmienić (w sposób systemowy).
PhiLho
1
bugs.java.com/bugdatabase/view_bug.do?bug_id=4045688 stwierdza w sekcji oceny „Od tego czasu żaden inny klient nie zgłosił się ani nie został zidentyfikowany w inny sposób. ...”, a od 2018 r. mamy około 175 000 widoki tego pytania :-(
Wolfgang Fahl

Odpowiedzi:

146

Nie ma niezawodnego sposobu na zrobienie tego w czystej Javie. Ustawienie user.dirwłaściwości za pośrednictwem System.setProperty()lub java -Duser.dir=...wydaje się mieć wpływ na kolejne kreacje Files, ale nie np FileOutputStreams.

File(String parent, String child)Konstruktor może pomóc, jeśli budować swoją ścieżkę oddzielnie od ścieżki do pliku, co umożliwia łatwiejsze swapping.

Alternatywą jest skonfigurowanie skryptu do uruchamiania Javy z innego katalogu lub użycie natywnego kodu JNI, jak zasugerowano poniżej .

Odpowiedni błąd Sun został zamknięty w 2008 roku jako „nie zostanie naprawiony”.

Michael Myers
źródło
12
nie sądzę, że znalazłem jedną różnicę między Javą i C #, która każe mi myśleć: „ci faceci java na pewno wiedzą, co robią”
Jake
2
Trudno uwierzyć, że java nie ma parametru "start w tym katalogu ..." przynajmniej ...
rogerdpack
5
W obronie Javy (a jestem facetem od UNIX, który pragnie tej funkcji) ... jest to maszyna wirtualna, która ma być agnostyczna w stosunku do szczegółów systemu operacyjnego. Idiom „obecny katalog roboczy” nie jest dostępny w niektórych systemach operacyjnych.
Tony K.
4
Aby być uczciwym wobec Javy, musieli to zrobić najpierw, C # miał tę zaletę, że mógł uczyć się na swoich błędach w wielu obszarach.
Ryan The Leach
3
@VolkerSeibt: Po dalszych badaniach wydaje się, że user.dir działa tylko dla niektórych klas, w tym tej, z którą testowałem na początku. new FileOutputStream("foo.txt").close();tworzy plik w oryginalnym katalogu roboczym, nawet jeśli plik user.dir zostanie zmieniony przez program.
Michael Myers
37

Jeśli uruchomisz swój starszy program z ProcessBuilder , będziesz mógł określić jego katalog roboczy .

PhiLho
źródło
1
Trasa to ta, którą wybrałem. Udało mi się uruchomić plik wykonywalny z innego katalogu roboczego z następującymi danymi: File WorkingDir = new File ("C: \\ ścieżka \\ do \\ pracujący \\ katalog \\"); ProcessBuilder pBuilder = nowy ProcessBuilder ("C: \\ ścieżka \\ do \\ działający \\ katalog \\ plik wykonywalny.exe"); pBuilder.directory (WorkingDir); Proces p = pBuilder.start ();
CatsAndCode
29

Nie jest to sposób, aby to zrobić za pomocą właściwości systemu „user.dir”. Kluczową kwestią do zrozumienia jest to, że należy wywołać metodę getAbsoluteFile () (jak pokazano poniżej), w przeciwnym razie ścieżki względne zostaną rozstrzygnięte na podstawie domyślnej wartości „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");
    }
}
Steve K.
źródło
3
bezwzględna ścieżka jest krytyczna
HackNone
5
Ale nie zmienia bieżącego katalogu roboczego. Tylko wartość user.dir. Dowodzi tego fakt, że ścieżka absolutna staje się krytyczna.
Markiz Lorne
18

Możliwa jest zmiana PWD przy użyciu JNA / JNI do wykonywania wywołań libc. Faceci z JRuby mają przydatną bibliotekę java do wykonywania wywołań POSIX o nazwie jna-posix Oto informacje o maven

Możesz zobaczyć przykład jego użycia tutaj (kod Clojure, przepraszam). Spójrz na funkcję chdirToRoot

Allen Rohner
źródło
1
Wydaje się, że nie ma nowoczesnej wersji jna-posix. Rozwidliłem i dodałem jeden: github.com/pbiggar/jnr-posix . Mogę potwierdzić, że mogę za pomocą tego zmienić PWD.
Paul Biggar,
Tak. Przepraszam, zrefaktorowałem ten plik i zapomniałem odpowiedzi związanej z plikiem. Naprawiony.
Allen Rohner
Możesz to zrobić, ale jeśli nie zmienisz również user.dirwłaściwości systemowej, File.getAbsolutePath()zostanie rozwiązane przeciwko user.dir, podczas gdy nazwa ścieżki w pliku zostanie rozpoznana w katalogu roboczym systemu operacyjnego.
Morrie
W odniesieniu do pierwotnego pytania, używając jnr-posix, jak mogę zmienić bieżący katalog roboczy w Javie. Jaką klasę muszę utworzyć, aby użyć metody chdir? Tak naprawdę nie rozumiałem podanego przykładu Clojure. Z góry dziękuję.
Sam Saint-Pettersen
12

Jak wspomniano, nie możesz zmienić CWD maszyny JVM, ale jeśli chcesz uruchomić inny proces za pomocą Runtime.exec (), możesz użyć przeciążonej metody, która pozwala określić katalog roboczy. Nie służy to tak naprawdę do uruchamiania programu Java w innym katalogu, ale w wielu przypadkach, gdy trzeba uruchomić inny program, na przykład skrypt Perla, można określić katalog roboczy tego skryptu, pozostawiając niezmieniony katalog roboczy maszyny JVM.

Zobacz Runtime.exec javadocs

Konkretnie,

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

gdzie dirjest katalog roboczy, w którym ma zostać uruchomiony podproces

Bizmarck
źródło
1
Wydaje się, że jest to lepsza odpowiedź niż akceptowana odpowiedź (która zaczyna się od „Nie ma niezawodnego sposobu na zrobienie tego w czystej Javie”). Czy istnieje sposób, aby petycja została uznana za zaakceptowaną?
John
11

Jeśli dobrze rozumiem, program Java zaczyna się od kopii aktualnych zmiennych środowiskowych. Wszelkie zmiany za pośrednictwem System.setProperty(String, String)modyfikują kopię, a nie oryginalne zmienne środowiskowe. Nie chodzi o to, że dostarcza to dokładnego powodu, dla którego Sun wybrał takie zachowanie, ale być może rzuca trochę światła ...

Adam Paynter
źródło
2
Wydaje się, że mieszasz zmienne środowiskowe i właściwości. Ten pierwszy jest dziedziczony z systemu operacyjnego, podczas gdy drugi można zdefiniować w wierszu poleceń za pomocą -D. Ale zgadzam się, że w JVM uruchamianie predefiniowanych właściwości, takich jak user.dirskopiowanie z systemu operacyjnego i ich późniejsza zmiana, nie pomaga.
maaartinus
Zmiana user.dirwpływa File.getAbsolutePath()i File.getCanonicalPath(), ale nie na pomysł systemu operacyjnego dotyczący katalogu roboczego, który dyktuje sposób rozwiązywania nazw ścieżek plików podczas uzyskiwania dostępu do plików.
Morrie
5

Katalog roboczy jest funkcją systemu operacyjnego (ustawianą podczas uruchamiania procesu). Dlaczego po prostu nie przekażesz własnej właściwości System ( -Dsomeprop=/my/path) i nie użyjesz jej w swoim kodzie jako elementu nadrzędnego pliku:

File f = new File ( System.getProperty("someprop"), myFilename)
raphaëλ
źródło
Ponieważ fragment kodu zawiera zakodowaną na stałe ścieżkę do pliku i prawdopodobnie używa zakodowanego na stałe konstruktora, który nie określa katalogu nadrzędnego do pracy. Tak przynajmniej mam :)
David Mann
4

Sprytniejszą / łatwiejszą rzeczą do zrobienia jest po prostu zmiana kodu, aby zamiast otwierać plik, zakładając, że istnieje w bieżącym katalogu roboczym (zakładam, że robisz coś takiego new File("blah.txt"), po prostu sam zbuduj ścieżkę do pliku.

Pozwól użytkownikowi przejść do katalogu podstawowego, przeczytaj go z pliku konfiguracyjnego, cofnij się, user.dirjeśli inne właściwości nie mogą zostać znalezione itp. Ale o wiele łatwiej jest poprawić logikę w programie niż zmienić sposób zmienne środowiskowe działają.

matowe b
źródło
2

Próbowałem przywołać

String oldDir = System.setProperty("user.dir", currdir.getAbsolutePath());

Wydaje się, że działa. Ale

File myFile = new File("localpath.ext"); InputStream openit = new FileInputStream(myFile);

rzuca FileNotFoundExceptionchoć

myFile.getAbsolutePath()

pokazuje poprawną ścieżkę. Mam to przeczytać . Myślę, że problem jest taki:

  • Java rozpoznaje bieżący katalog z nowym ustawieniem.
  • Ale obsługa plików jest wykonywana przez system operacyjny. Niestety nie zna nowego zestawu bieżącego katalogu.

Rozwiązaniem może być:

File myFile = new File(System.getPropety("user.dir"), "localpath.ext");

Tworzy obiekt plikowy jako absolutny z bieżącym katalogiem, który jest znany maszynie JVM. Ale ten kod powinien istnieć w używanej klasie, wymaga zmiany kodów ponownie używanych.

~~~~ JcHartmut

Hartmut Schorrig
źródło
„Tworzy plik Object” - tak. Ale nie tworzy pliku w systemie plików. Po tym zapomniałeś użyć file.createNewFile ().
Gangnus
2

Możesz użyć

nowy plik („względna / ścieżka”). getAbsoluteFile ()

po

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

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

Wydrukuje

C:\OtherProject\data\data.csv
javacommons
źródło
1
Zwróć uwagę, że to zachowanie zmieniło się w Javie 11, zobacz bugs.openjdk.java.net/browse/JDK-8202127 . Nie zalecają używaniaSystem.setProperty("user.dir", "/some/directory")
Martin
0

Inna możliwa odpowiedź na to pytanie może zależeć od powodu, dla którego otwierasz plik. Czy jest to plik właściwości czy plik, który ma konfigurację związaną z Twoją aplikacją?

W takim przypadku możesz rozważyć próbę załadowania pliku przez program ładujący ścieżki klas, w ten sposób możesz załadować dowolny plik, do którego Java ma dostęp.

Nathan Feger
źródło
0

Jeśli uruchamiasz swoje polecenia w powłoce, możesz napisać coś takiego jak „java -cp” i dodać dowolne katalogi, które chcesz oddzielone znakiem „:”. Jeśli java nie znajdzie czegoś w jednym katalogu, spróbuje znaleźć je w innych katalogach, jest tym, co robię.

lesolorzanov
źródło
0

Możesz zmienić rzeczywisty katalog roboczy procesu za pomocą JNI lub JNA.

Dzięki JNI możesz używać natywnych funkcji do ustawiania katalogu. Metoda POSIX to chdir(). W systemie Windows możesz użyć SetCurrentDirectory().

Dzięki JNA możesz opakować natywne funkcje w bindery Java.

Dla Windowsa:

private static interface MyKernel32 extends Library {
    public MyKernel32 INSTANCE = (MyKernel32) Native.loadLibrary("Kernel32", MyKernel32.class);

    /** BOOL SetCurrentDirectory( LPCTSTR lpPathName ); */
    int SetCurrentDirectoryW(char[] pathName);
}

Dla systemów POSIX:

private interface MyCLibrary extends Library {
    MyCLibrary INSTANCE = (MyCLibrary) Native.loadLibrary("c", MyCLibrary.class);

    /** int chdir(const char *path); */
    int chdir( String path );
}
Andy Thomas
źródło
-1

Użyj FileSystemView

private FileSystemView fileSystemView;
fileSystemView = FileSystemView.getFileSystemView();
currentDirectory = new File(".");
//listing currentDirectory
File[] filesAndDirs = fileSystemView.getFiles(currentDirectory, false);
fileList = new ArrayList<File>();
dirList = new ArrayList<File>();
for (File file : filesAndDirs) {
if (file.isDirectory())
    dirList.add(file);
else
    fileList.add(file);
}
Collections.sort(dirList);
if (!fileSystemView.isFileSystemRoot(currentDirectory))
    dirList.add(0, new File(".."));
Collections.sort(fileList);
//change
currentDirectory = fileSystemView.getParentDirectory(currentDirectory);
Borneq
źródło
import javax.swing.filechooser.FileSystemView;
Borneq
1
Ta odpowiedź nie jest związana z pytaniem.
Aaron Digulla