Чи існує в Java спосіб визначити, чи є шлях дійсним, не намагаючись створити файл?


78

Мені потрібно визначити, чи наданий користувачем рядок є дійсним шляхом до файлу (тобто, createNewFile()вдасться чи створити виняток), але я не хочу роздувати файлову систему непотрібними файлами, створеними лише з метою перевірки.

Чи можна визначити, чи є у мене рядок дійсним шляхом до файлу, не намагаючись створити файл?

Я знаю, що визначення поняття "дійсний шлях до файлу" варіюється залежно від ОС, але мені було цікаво, чи є якийсь швидкий спосіб прийняття C:/fooабо /fooвідхилення banana.

Можливим підходом може бути спроба створити файл і врешті видалити його, якщо створення вдалося, але я сподіваюся, що є більш елегантний спосіб досягнення того ж результату.


відповідна перевірочна документація від klocwork: SV.PATH : містить корисні вказівки
boly38

Відповіді:


39

Це також перевірить наявність каталогу.

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

Здається, файл.canWrite () не дає чітких вказівок, якщо у вас є дозволи на запис в каталог.


17
М-м-м-м ... я не хочу перевіряти, чи існує файл, я хочу перевірити, чи файл можна створити в поточній файловій системі
Райбаз,

Проблема в тому, що можливість створення файлу може змінитися після того, як ви перевірили, чи можна його створити
tddmonkey

3
Код krosenvold робить наступне: Чи існує ім'я файлу, яке вже передано, вже як каталог, і якщо ні, чи існує каталог, в якому він містився б? Це має сенс, оскільки, якщо там є каталог, ви можете створити файл (дозволи дозволяють). Зверніть увагу, що новий файл ("foo") не створює файл.
mtruesdell

3
Примітка: Ви насправді не створюєте файл у файловій системі за допомогою new File("some/path"). Тож ви можете використовувати їх для цілей перевірки. Щоб створювати файли у файловій системі, потрібно createNewFileтощо
mike

1
Я не розумію, що у Java немає рідного способу перевірити правильність шляху (будь ласка, не розглядайте клас Java 7 Path, він "нещодавній" і здається невдалим у цьому під Linux). Чи є якась особлива причина цього? Чорт візьми, це не так, як шлях змінився так сильно за останні десятиліття.
Maxime Oudot

38

Клас Path, введений в Java 7, додає нові альтернативи, такі як такі:
( Не працює належним чином під Linux - завжди повертає true)

/**
 * <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;
}

Гарний! Приємне та чисте рішення без необхідності включати бібліотеки Apache.
Тім Візе

Дякую @ Tim Visée. Я радий, що ви вважаєте це хорошим рішенням (0:
c0der

Зверніть увагу, що це завжди повертається trueна Linux. У моєму власному тестуванні навіть Paths.get("file\u0000")не кидаю InvalidPathException.
Гроостав

2
@nllsdfx - Якщо в сторону не працює Linux, що поганого в тому, що це повертає істину для цього? Цілком справедливо мати назву каталогу з крапками в ній, якщо це, на вашу думку, з нею неправильно ...
ArtOfWarfare

1
Єдиним забороненим символом в назві файлу в Linux є символ NUL, це працює під Linux
Ferrybig

17

File.getCanonicalPath()цілком корисна для цієї мети. Винятки IO викидається для певних типів неприпустимих імен файлів (наприклад CON, PRN, *?*в Windows) при вирішенні проти ОС або файлової системи. Однак це служить лише попередньою перевіркою; Вам все одно доведеться обробляти інші помилки під час фактичного створення файлу (наприклад, недостатні дозволи, відсутність місця на диску, обмеження безпеки).


AFAICT, ніщо не заважає програмувати файл Windows 10 із назвою CON, хоча деякі середовища оболонки можуть скаржитися. Можливо, це стосується лише того, як COND (та інші рядки) інтерпретується CMD.exe (і, можливо, іншими).
philwalk

15

Ряд речей може піти не так, коли ви намагаєтеся створити файл:

  • Ваша відсутність необхідних дозволів;
  • На пристрої недостатньо місця;
  • У пристрої виникає помилка;
  • Деякі правила спеціальної безпеки забороняють створювати файли певного типу;
  • тощо

Більш суттєво, вони можуть змінюватися між спробами та запитом, чи можна, і коли можна насправді. У багатопотоковому середовищі це одна з основних причин расових умов і може бути реальною вразливістю деяких програм.

В основному вам просто потрібно спробувати створити його і перевірити, чи він працює. І це правильний спосіб зробити це. Ось чому такі речі, як наприклад, ConcurrentHashMapмає putIfAbsent()перевірку та вставку, є атомною операцією і не страждають від расових умов. Точно такий же принцип діє і тут.

Якщо це лише частина якогось процесу діагностики або інсталяції, просто зробіть це і перевірте, чи працює це. Знову ж таки, немає гарантії, що це спрацює пізніше.

В основному ваша програма повинна бути достатньо надійною, щоб граціозно померти, якщо вона не може написати відповідний файл.


3
DON "T DIE (в деяких випадках), дозвольте користувачеві вибрати інший том або носій. У мене є IDE, який помирає, коли не може записати файл свого проекту. Ну, том знаходиться в автономному режимі - дозвольте мені вибрати інше місце і продовжити .
джим

1
boolean canWrite(File file) {
  if (file.exists()) {
    return file.canWrite();
  }
  else {
    try {
      file.createNewFile();
      file.delete();
      return true;
    }
    catch (Exception e) {
      return false;
    }
  }
}

5
Це не працює повністю, оскільки File.canWrite()не є надійним у Windows. Дивіться цю публікацію та перший коментар Пітера Ценгу для вирішення проблем.
sullivan-

1

Ось те, що ви можете зробити, що працює в операційних системах

Використання регулярного виразу для перевірки наявних відомих неприпустимих символів.

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

Плюси

  • Це працює в усіх операційних системах
  • Ви можете налаштувати його як завгодно, редагуючи цей регулярний вираз.

Мінуси

  • Це може бути не повний список і потребує додаткових досліджень, щоб заповнити більше недійсних шаблонів або символів.

0

Просто зробіть це (і приберіть за собою)

Можливим підходом може бути спроба створити файл і врешті видалити його, якщо створення вдалося, але я сподіваюся, що є більш елегантний спосіб досягнення того ж результату.

Можливо, це найнадійніший спосіб.

Нижче це canCreateOrIsWritableвизначає, чи здатна ваша програма створити файл та його батьківські каталоги за заданим шляхом, або, якщо там уже є файл, написати на нього.

Це робиться, фактично створюючи необхідні батьківські каталоги, а також порожній файл у шляху. Потім він видаляє їх (якщо на шляху існував файл, він залишається в спокої).

Ось як ви можете ним скористатися:

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");
}

Основний метод з кількома допоміжними методами:

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();
    }
}

Майте на увазі, що між викликом canCreateOrIsWritableта створенням фактичного файлу вміст та дозволи вашої файлової системи могли змінитися.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.