Gibt es in Java eine Möglichkeit, festzustellen, ob ein Pfad gültig ist, ohne zu versuchen, eine Datei zu erstellen?

77

Ich muss feststellen, ob eine vom Benutzer angegebene Zeichenfolge ein gültiger Dateipfad ist (dh ob dies createNewFile()erfolgreich ist oder eine Ausnahme auslöst), aber ich möchte das Dateisystem nicht mit nutzlosen Dateien aufblähen, die nur zu Validierungszwecken erstellt wurden.

Gibt es eine Möglichkeit festzustellen, ob die Zeichenfolge, die ich habe, ein gültiger Dateipfad ist, ohne zu versuchen, die Datei zu erstellen?

Ich weiß, dass die Definition des "gültigen Dateipfads" je nach Betriebssystem unterschiedlich ist, aber ich habe mich gefragt, ob es eine schnelle Möglichkeit gibt, zu akzeptieren C:/foooder /fooabzulehnen banana.

Ein möglicher Ansatz könnte darin bestehen, die Datei zu erstellen und sie schließlich zu löschen, wenn die Erstellung erfolgreich war. Ich hoffe jedoch, dass es eine elegantere Möglichkeit gibt, dasselbe Ergebnis zu erzielen.

Raibaz
quelle
zugehörige Prüferdokumentation von klocwork: SV.PATH : enthält nützliche Richtlinien
boly38

Antworten:

39

Dies würde auch die Existenz des Verzeichnisses überprüfen.

File file = new File("c:\\cygwin\\cygwin.bat");
if (!file.isDirectory())
   file = file.getParentFile();
if (file.exists()){
    ...
}

Es scheint, dass file.canWrite () Ihnen keinen eindeutigen Hinweis gibt, ob Sie zum Schreiben in das Verzeichnis berechtigt sind.

Krosenvold
quelle
16
Ähm ... ich möchte nicht überprüfen, ob die Datei existiert, ich möchte überprüfen, ob die Datei auf dem aktuellen Dateisystem erstellt werden kann
Raibaz
Das Problem dort ist, dass sich die Fähigkeit zum Erstellen der Datei ändern kann, nachdem Sie überprüft haben, ob sie erstellt werden kann
tddmonkey
3
Der Code von krosenvold bewirkt Folgendes: Existiert der übergebene Dateiname bereits als Verzeichnis, und wenn nicht, existiert das Verzeichnis, in dem er sich befinden würde? Dies ist sinnvoll, da Sie die Datei erstellen können, wenn das Verzeichnis vorhanden ist (Berechtigungen zulässig). Beachten Sie, dass eine neue Datei ("foo") keine Datei erstellt.
mtruesdell
Fügen Sie dies in den Konstruktor der Datei "c: [] [] cygwin \\ cygwin.bat" ein. Fügen Sie dann print-Anweisungen in beide ifs ein - "dir found" in 1st if und "file found" in second if. Die Ausgabe ist dir gefunden, was offensichtlich nicht mit der Überprüfung identisch ist, ob ein Pfadname gültig / legal ist.
David Blaine
3
Anmerkung: Sie erstellen mit tatsächlich keine Datei im Dateisystem mit new File("some/path"). Sie können diese also zu Validierungszwecken verwenden. Um Dateien auf dem Dateisystem zu erstellen, müssen createNewFileusw.
Mike
38

Die in Java 7 eingeführte Pfadklasse fügt neue Alternativen hinzu, wie die folgenden:
(Funktioniert unter Linux nicht ordnungsgemäß - gibt immer true zurück)

/**
 * <pre>
 * Checks if a string is a valid path.
 * Null safe.
 *  
 * Calling examples:
 *    isValidPath("c:/test");      //returns true
 *    isValidPath("c:/te:t");      //returns false
 *    isValidPath("c:/te?t");      //returns false
 *    isValidPath("c/te*t");       //returns false
 *    isValidPath("good.txt");     //returns true
 *    isValidPath("not|good.txt"); //returns false
 *    isValidPath("not:good.txt"); //returns false
 * </pre>
 */
public static boolean isValidPath(String path) {
    try {
        Paths.get(path);
    } catch (InvalidPathException | NullPointerException ex) {
        return false;
    }
    return true;
}
c0der
quelle
Wunderschönen! Schöne und saubere Lösung, ohne dass Apache-Bibliotheken enthalten sein müssen.
Tim Visée
Vielen Dank an Tim Visée. Ich bin froh, dass Sie es eine gute Lösung finden (0:
c0der
Beachten Sie, dass dies immer trueunter Linux zurückgegeben wird. In meinen eigenen Tests Paths.get("file\u0000")wirft nicht einmal InvalidPathException.
Groostav
2
@nllsdfx - Abgesehen von Linux funktioniert es nicht. Was ist falsch daran, dass es dafür true zurückgibt? Es ist vollkommen gültig, einen Verzeichnisnamen mit
Punkten
1
Das einzige unzulässige Zeichen in einem Dateinamen unter Linux ist das NUL-Zeichen. Dies funktioniert unter Linux
Ferrybig
17

File.getCanonicalPath()ist für diesen Zweck sehr nützlich. IO Ausnahmen sind für bestimmte Arten von ungültigen Dateinamen (zB geworfen CON, PRN, *?*in Windows) , wenn sie gegen das Betriebssystem oder Dateisystem zu lösen. Dies dient jedoch nur als vorläufige Überprüfung; Sie müssen beim Erstellen der Datei noch andere Fehler behandeln (z. B. unzureichende Berechtigungen, mangelnder Speicherplatz, Sicherheitsbeschränkungen).

Zach Scrivena
quelle
AFAICT, es gibt nichts, was das programmgesteuerte Erstellen einer Windows 10-Datei mit dem Namen CON verhindern könnte, obwohl sich einige Shell-Umgebungen möglicherweise beschweren. Möglicherweise bezieht sich dies nur darauf, wie CON (und andere Zeichenfolgen) von CMD.exe (und möglicherweise anderen) interpretiert werden.
Philwalk
15

Beim Versuch, eine Datei zu erstellen, kann eine Reihe von Fehlern auftreten:

  • Ihnen fehlen die erforderlichen Berechtigungen.
  • Auf dem Gerät ist nicht genügend Speicherplatz vorhanden.
  • Auf dem Gerät ist ein Fehler aufgetreten.
  • Einige Richtlinien zur benutzerdefinierten Sicherheit verbieten Ihnen das Erstellen einer Datei eines bestimmten Typs.
  • etc.

Genauer gesagt können sich diese zwischen dem Versuch und der Abfrage, ob Sie dies können, und dem Zeitpunkt, zu dem Sie es tatsächlich können, ändern. In einer Multithread-Umgebung ist dies eine der Hauptursachen für Rennbedingungen und kann eine echte Sicherheitslücke einiger Programme darstellen.

Grundsätzlich müssen Sie nur versuchen, es zu erstellen und zu sehen, ob es funktioniert. Und das ist der richtige Weg. Aus diesem Grund ConcurrentHashMapist putIfAbsent()das Überprüfen und Einfügen eine atomare Operation und leidet nicht unter den Rennbedingungen. Genau das gleiche Prinzip spielt hier eine Rolle.

Wenn dies nur ein Teil eines Diagnose- oder Installationsprozesses ist, tun Sie es einfach und prüfen Sie, ob es funktioniert. Auch hier gibt es keine Garantie, dass es später funktioniert.

Grundsätzlich muss Ihr Programm robust genug sein, um ordnungsgemäß zu sterben, wenn es keine relevante Datei schreiben kann.

Cletus
quelle
3
DON "T DIE (in einigen Fällen), erlauben Sie dem Benutzer, ein anderes Volume oder Medium auszuwählen. Ich habe eine IDE, die stirbt, wenn es seine Projektdatei nicht schreiben kann. Nun, das Volume ist offline - erlauben Sie mir, einen anderen Ort auszuwählen und fortzufahren .
Jim
1
boolean canWrite(File file) {
  if (file.exists()) {
    return file.canWrite();
  }
  else {
    try {
      file.createNewFile();
      file.delete();
      return true;
    }
    catch (Exception e) {
      return false;
    }
  }
}
Sullivan-
quelle
5
Dies funktioniert nicht vollständig, da File.canWrite()es unter Windows nicht zuverlässig ist. Eine Problemumgehung finden Sie in diesem Beitrag und im ersten Kommentar von Peter Tseng.
Sullivan
1

Hier ist etwas, das Sie für alle Betriebssysteme tun können

Verwenden der Regex-Übereinstimmung, um nach vorhandenen bekannten ungültigen Zeichen zu suchen.

if (newName.matches(".*[/\n\r\t\0\f`?*\\<>|\":].*")) {
    System.out.println("Invalid!");
} else {
    System.out.println("Valid!");
}

Vorteile

  • Dies funktioniert betriebssystemübergreifend
  • Sie können es beliebig anpassen, indem Sie diesen regulären Ausdruck bearbeiten.

Nachteile

  • Dies ist möglicherweise keine vollständige Liste und erfordert weitere Nachforschungen, um ungültigere Muster oder Zeichen auszufüllen.
Fangming
quelle
0

Mach es einfach (und räum nach dir auf)

Ein möglicher Ansatz könnte darin bestehen, die Datei zu erstellen und sie schließlich zu löschen, wenn die Erstellung erfolgreich war. Ich hoffe jedoch, dass es eine elegantere Möglichkeit gibt, dasselbe Ergebnis zu erzielen.

Vielleicht ist das der robusteste Weg.

Im Folgenden wird canCreateOrIsWritablefestgelegt, ob Ihr Programm eine Datei und ihre übergeordneten Verzeichnisse unter einem bestimmten Pfad erstellen oder, falls dort bereits eine Datei vorhanden ist, in diese Datei schreiben kann.

Dazu werden die erforderlichen übergeordneten Verzeichnisse sowie eine leere Datei im Pfad erstellt. Danach werden sie gelöscht (wenn eine Datei im Pfad vorhanden war, wird sie in Ruhe gelassen).

So können Sie es verwenden:

var myFile = new File("/home/me/maybe/write/here.log")

if (canCreateOrIsWritable(myFile)) {
    // We're good. Create the file or append to it
    createParents(myFile);
    appendOrCreate(myFile, "new content");
} else {
    // Let's pick another destination. Maybe the OS's temporary directory:
    var tempDir = System.getProperty("java.io.tmpdir");
    var alternative = Paths.get(tempDir, "second_choice.log");
    appendOrCreate(alternative, "new content in temporary directory");
}

Die wesentliche Methode mit ein paar Hilfsmethoden:

static boolean canCreateOrIsWritable(File file) {
    boolean canCreateOrIsWritable;

    // The non-existent ancestor directories of the file.
    // The file's parent directory is first
    List<File> parentDirsToCreate = getParentDirsToCreate(file);

    // Create the parent directories that don't exist, starting with the one
    // highest up in the file system hierarchy (closest to root, farthest
    // away from the file)
    reverse(parentDirsToCreate).forEach(File::mkdir);

    try {
        boolean wasCreated = file.createNewFile();
        if (wasCreated) {
            canCreateOrIsWritable = true;
            // Remove the file and its parent dirs that didn't exist before
            file.delete();
            parentDirsToCreate.forEach(File::delete);
        } else {
            // There was already a file at the path → Let's see if we can
            // write to it
            canCreateOrIsWritable = java.nio.file.Files.isWritable(file.toPath());
        }
    } catch (IOException e) {
        // File creation failed
        canCreateOrIsWritable = false;
    }
    return canCreateOrIsWritable;
}

static List<File> getParentDirsToCreate(File file) {
    var parentsToCreate = new ArrayList<File>();
    File parent = file.getParentFile();
    while (parent != null && !parent.exists()) {
        parentsToCreate.add(parent);

        parent = parent.getParentFile();
    }
    return parentsToCreate;
}

static <T> List<T> reverse(List<T> input) {
    var reversed = new ArrayList<T>();
    for (int i = input.size() - 1; i >= 0; i--) {
        reversed.add(input.get(i));
    }
    return reversed;
}

static void createParents(File file) {
    File parent = file.getParentFile();
    if (parent != null) {
        parent.mkdirs();
    }
}

Beachten Sie, dass sich zwischen dem Aufrufen canCreateOrIsWritableund Erstellen der eigentlichen Datei möglicherweise der Inhalt und die Berechtigungen Ihres Dateisystems geändert haben.

Matthias Braun
quelle