Java로 임시 디렉토리 / 폴더를 작성하는 방법은 무엇입니까?


답변:


390

JDK 7을 사용하는 경우 새 Files.createTempDirectory 클래스를 사용하여 임시 디렉토리를 작성하십시오.

Path tempDirWithPrefix = Files.createTempDirectory(prefix);

JDK 7 이전에는 이렇게해야합니다.

public static File createTempDirectory()
    throws IOException
{
    final File temp;

    temp = File.createTempFile("temp", Long.toString(System.nanoTime()));

    if(!(temp.delete()))
    {
        throw new IOException("Could not delete temp file: " + temp.getAbsolutePath());
    }

    if(!(temp.mkdir()))
    {
        throw new IOException("Could not create temp directory: " + temp.getAbsolutePath());
    }

    return (temp);
}

원하는 경우 더 나은 예외 (하위 클래스 IOException)를 만들 수 있습니다.


12
이것은 위험합니다. Java는 파일을 즉시 삭제하지 않는 것으로 알려져 있으므로 mkdir이 때때로 실패 할 수 있습니다.
— Demiurg

4
@Demiurg 파일이 이미 열려있을 때 파일이 즉시 삭제되지 않는 유일한 경우는 Windows입니다 (예 : 바이러스 스캐너로 열 수 있음). 다른 방법으로 보여줄 다른 문서가 있습니까? 정기적으로 발생하면 위의 코드가 작동하지 않으며 드문 경우 삭제가 발생할 때까지 (또는 최대 시도 횟수에 도달 할 때까지) 위 코드를 호출하면 작동합니다.
— TofuBeer

6
@Demiurg Java는 파일을 즉시 삭제하지 않는 것으로 알려져 있습니다. 당신이 그것을 열지 않더라도 그것은 사실입니다. 더 안전한 방법은 temp.delete(); temp = new File(temp.getPath + ".d"); temp.mkdir(); ..., temp.delete();입니다.
— Xiè Jìléi

102
이 코드는 사이에 경쟁 조건이 delete()과를 mkdir(): 악성 프로세스가 그 동안 대상 디렉토리 (최근에 생성 된 파일의 이름을 복용)을 만들 수 있습니다. 참조 Files.createTempDir()대안을 위해.
— Joachim Sauer

11
나는 좋아한다! 너무 쉽게 놓칠 수 있습니다. (! 난) 난 ... 경우 :-) 성가신 것이 일반적인 충분 학생들에 의해 작성된 코드를 많이 읽고
— TofuBeer

182

Google Guava 라이브러리에는 유용한 유틸리티가 많이 있습니다. 여기서 주목할 것은 파일 클래스 입니다. 다음과 같은 유용한 방법이 있습니다.

File myTempDir = Files.createTempDir();

이것은 정확히 한 줄로 요청한 것을 수행합니다. 여기 에서 설명서를 읽으면 제안 된 적응이 File.createTempFile("install", "dir")일반적으로 보안 취약점을 유발한다는 것을 알 수 있습니다 .


당신이 어떤 취약점을 참조하는지 궁금합니다. 이러한 접근 방식은 File.mkdir ()이 해당 디렉토리가 이미 존재하는 경우 (공격자에 의해 생성 된 경우) File.mkdir ()이 실패하도록 경쟁 조건을 생성하는 것으로 보이지 않습니다. 이 전화가 악의적 인 심볼릭 링크를 따라갈 것이라고는 생각하지 않습니다. 당신이 의미 한 것을 명확히 할 수 있습니까?
— abb

3
@ abb : Guava 문서에 언급 된 경쟁 조건에 대한 세부 정보를 모르겠습니다. 특히 문제를 불러 일으킨 문서가 정확하다고 생각합니다.
— Spina

1
@abb 당신이 맞아요. mkdir ()의 리턴이 확인되는 한 안전합니다. Spina 코드는이 mkdir () 메소드를 사용하도록 지정합니다. grepcode.com/file/repo1.maven.org/maven2/com.google.guava/guava/… . 이는 고정 비트가 활성화되어 있기 때문에 / tmp 디렉토리를 사용할 때 Unix 시스템에서만 발생할 수있는 문제입니다.
— Sarel Botha

@SarelBotha 빈칸을 채워 주셔서 감사합니다. 나는 이것에 대해 꽤 오랫동안 궁금해하고있었습니다.
— Spina

168

테스트를 위해 임시 디렉토리가 필요하고 jUnit을 사용중인 경우 문제점 @Rule을 TemporaryFolder해결하십시오.

@Rule
public TemporaryFolder folder = new TemporaryFolder();

로부터 문서 :

TemporaryFolder 규칙을 사용하면 테스트 방법이 완료 될 때 (통과 여부에 관계없이) 파일 및 폴더를 삭제할 수 있습니다.


최신 정보:

JUnit Jupiter (버전 5.1.1 이상)를 사용하는 경우 JUnit 5 Extension Pack 인 JUnit Pioneer를 사용할 수 있습니다.

프로젝트 문서 에서 복사 :

예를 들어, 다음 테스트는 단일 테스트 메소드에 대한 확장자를 등록하고 파일을 작성하여 임시 디렉토리에 쓰고 내용을 확인합니다.

@Test
@ExtendWith(TempDirectory.class)
void test(@TempDir Path tempDir) {
    Path file = tempDir.resolve("test.txt");
    writeFile(file);
    assertExpectedFileContent(file);
}

JavaDoc 및 TempDirectory 의 JavaDoc 에 대한 추가 정보

그레들 :

dependencies {
    testImplementation 'org.junit-pioneer:junit-pioneer:0.1.2'
}

메이븐 :

<dependency>
   <groupId>org.junit-pioneer</groupId>
   <artifactId>junit-pioneer</artifactId>
   <version>0.1.2</version>
   <scope>test</scope>
</dependency>

업데이트 2 :

@TempDir의 주석 실험적인 기능으로 JUnit을 목성 5.4.0 릴리스에 추가되었습니다. JUnit 5 사용 설명서 에서 복사 한 예 :

@Test
void writeItemsToFile(@TempDir Path tempDir) throws IOException {
    Path file = tempDir.resolve("test.txt");

    new ListWriter(file).write("a", "b", "c");

    assertEquals(singletonList("a,b,c"), Files.readAllLines(file));
}

8
JUnit 4.7부터 사용 가능
— Eduard Wirch 2018 년

Windows 7의 JUnit 4.8.2에서는 작동하지 않습니다! (권한 문제)
— 예외

2
@CraigRinger : 왜 이것에 의지하지 않는가?
— Adam Parkin 2012 년

2
@AdamParkin 솔직히 더 이상 기억이 없습니다. 설명 실패!
— Craig Ringer

1
이 접근법의 주요 이점은 디렉토리가 JUnit (테스트 전에 작성되고 테스트 후에 재귀 적으로 삭제됨)에 의해 관리된다는 것입니다. 그리고 그것은 작동합니다. "temp dir not created yet"이 표시되면 @Rule을 잊었거나 공개 필드가 아닌 필드 일 수 있습니다.
— Bogdan Calmac

42

이 문제를 해결하기 위해 순진하게 작성된 코드는 여기에 몇 가지 답변을 포함하여 경쟁 조건이 있습니다. 역사적으로 경쟁 조건에 대해 신중하게 생각하고 직접 작성하거나 Spina의 답변이 제안한 Google Guava와 같은 타사 라이브러리를 사용하거나 버그가있는 코드를 작성할 수 있습니다.

그러나 JDK 7부터는 좋은 소식이 있습니다! Java 표준 라이브러리 자체는 이제이 문제에 대해 올바르게 작동하는 (비 레이스가 아닌) 솔루션을 제공합니다. 당신이 원하는 java.nio.file.Files # createTempDirectory ()을 . 로부터 문서 :

public static Path createTempDirectory(Path dir,
                       String prefix,
                       FileAttribute<?>... attrs)
                                throws IOException

지정된 접두사를 사용하여 지정된 디렉토리에 새 디렉토리를 작성하여 이름을 생성합니다. 결과 경로는 주어진 디렉토리와 동일한 파일 시스템과 연관됩니다.

디렉토리 이름의 구성 방법에 대한 세부 사항은 구현에 따라 다르므로 지정되지 않았습니다. 가능한 경우 접두사가 후보 이름을 구성하는 데 사용됩니다.

이를 통해 Sun 버그 추적기에서 매우 오래된 버그 보고서 를 효과적으로 해결하여 그러한 기능을 요구했습니다.


35

이것은 Guava 라이브러리의 Files.createTempDir ()에 대한 소스 코드입니다. 생각만큼 복잡하지 않습니다.

public static File createTempDir() {
  File baseDir = new File(System.getProperty("java.io.tmpdir"));
  String baseName = System.currentTimeMillis() + "-";

  for (int counter = 0; counter < TEMP_DIR_ATTEMPTS; counter++) {
    File tempDir = new File(baseDir, baseName + counter);
    if (tempDir.mkdir()) {
      return tempDir;
    }
  }
  throw new IllegalStateException("Failed to create directory within "
      + TEMP_DIR_ATTEMPTS + " attempts (tried "
      + baseName + "0 to " + baseName + (TEMP_DIR_ATTEMPTS - 1) + ')');
}

기본적으로:

private static final int TEMP_DIR_ATTEMPTS = 10000;

여기를 봐


28

deleteOnExit()나중에 명시 적으로 삭제하더라도 사용하지 마십시오 .

더 많은 정보를 얻으 려면 Google 'deleteonexit is evil' 이지만 문제의 요지는 다음과 같습니다.

  1. deleteOnExit() 일반적인 JVM 종료에 대해서만 삭제하며 JVM 프로세스가 중단되거나 종료되지 않습니다.

  2. deleteOnExit() JVM 종료시에만 삭제-장기 실행 서버 프로세스에는 적합하지 않습니다.

  3. 가장 악한 것- deleteOnExit()각 임시 파일 항목에 대한 메모리를 사용합니다. 프로세스가 몇 달 동안 실행되거나 짧은 시간에 많은 임시 파일을 작성하는 경우 메모리가 소비되고 JVM이 종료 될 때까지 해제하지 마십시오.


1
우리는 클래스와 jar 파일이 JVM에 의해 생성 된 숨겨진 파일을 얻는 JVM을 가지고 있으며이 추가 정보는 삭제하는 데 꽤 오랜 시간이 걸립니다. WAR를 폭발시키는 웹 컨테이너에서 핫 재배치를 수행하는 경우, 완료 후 몇 시간 동안 실행될 때 종료하기 전에 JVM이 문자 그대로 정리하는 데 몇 분이 걸릴 수 있습니다.
— Thorbjørn Ravn Andersen

20

자바 1.7로 createTempDirectory(prefix, attrs)와 createTempDirectory(dir, prefix, attrs)에 포함되어 있습니다java.nio.file.Files

예: File tempDir = Files.createTempDirectory("foobar").toFile();


14

이것은 내 자신의 코드로하기로 결정한 것입니다.

/**
 * Create a new temporary directory. Use something like
 * {@link #recursiveDelete(File)} to clean this directory up since it isn't
 * deleted automatically
 * @return  the new directory
 * @throws IOException if there is an error creating the temporary directory
 */
public static File createTempDir() throws IOException
{
    final File sysTempDir = new File(System.getProperty("java.io.tmpdir"));
    File newTempDir;
    final int maxAttempts = 9;
    int attemptCount = 0;
    do
    {
        attemptCount++;
        if(attemptCount > maxAttempts)
        {
            throw new IOException(
                    "The highly improbable has occurred! Failed to " +
                    "create a unique temporary directory after " +
                    maxAttempts + " attempts.");
        }
        String dirName = UUID.randomUUID().toString();
        newTempDir = new File(sysTempDir, dirName);
    } while(newTempDir.exists());

    if(newTempDir.mkdirs())
    {
        return newTempDir;
    }
    else
    {
        throw new IOException(
                "Failed to create temp dir named " +
                newTempDir.getAbsolutePath());
    }
}

/**
 * Recursively delete file or directory
 * @param fileOrDir
 *          the file or dir to delete
 * @return
 *          true iff all files are successfully deleted
 */
public static boolean recursiveDelete(File fileOrDir)
{
    if(fileOrDir.isDirectory())
    {
        // recursively delete contents
        for(File innerFile: fileOrDir.listFiles())
        {
            if(!FileUtilities.recursiveDelete(innerFile))
            {
                return false;
            }
        }
    }

    return fileOrDir.delete();
}

2
이것은 안전하지 않습니다. 첫 번째 (동일하게 안전하지 않은) 옵션에서 Joachim Sauer의 코멘트를 참조하십시오. 파일 또는 디렉토리의 존재를 확인하고 파일 이름을 원자 적으로 얻는 것보다 적절한 방법은 파일 또는 디렉토리를 작성하는 것입니다.
— zbyszek

1
@zbyszek javadocs는 "UUID는 암호화 적으로 강력한 의사 난수 생성기를 사용하여 생성됩니다"라고 말합니다. 악의적 인 프로세스가 exist ()와 mkdirs () 사이에 동일한 이름의 dir을 만드는 방법을 감안할 때. 사실 지금 이것을 보면 필자의 exist () 테스트가 약간 어리석은 것으로 생각됩니다.
— Keith

Keith : UUID가 안전하거나 중요하지 않은 것은이 경우 중요하지 않습니다. 쿼리 한 이름에 대한 정보는 어떻게 든 "누설"에 충분합니다. 예를 들어, 생성되는 파일이 NFS 파일 시스템에 있고 공격자가 패킷을 (수동으로)들을 수 있다고 가정 해 봅시다. 또는 무작위 생성기 상태가 누출되었습니다. 내 의견으로는 귀하의 솔루션이 받아 들여지는 대답과 동일하게 안전하지 않다고 말했지만 이것은 공평하지 않습니다. 허용 된 솔루션은 inotify로이기는 것이 쉽지 않으며,이 솔루션은이기는 것이 훨씬 더 어렵습니다. 그럼에도 불구하고, 일부 시나리오에서는 확실히 가능합니다.
— zbyszek

2
나는 같은 생각을 가지고 이와 같은 임의의 UUID 비트를 사용하여 솔루션을 구현했습니다. randomUUID 메소드에 의해 사용되는 강력한 RNG는 충돌을 거의 보장하지 않습니다 (DB 테이블에서 기본 키를 생성하고 직접 수행하며 충돌을 알 수 없음). 확실하지 않은 사람이 있으면 stackoverflow.com/questions/2513573/…을
— brabster

Java 구현을 보면 충돌이 없을 때까지 임의의 이름을 생성합니다. 그들의 최대 시도는 무한합니다. 따라서 악의적 인 누군가가 파일 / 디렉토리 이름을 계속 추측하면 영원히 반복됩니다. 여기에 소스에 대한 링크는 다음과 같습니다 hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/9fb81d7a2f16/src/share/... 나는 그것이 어떻게 든 원자의 고유 한 이름을 생성 할 수 있도록 파일 시스템을 잠그고 수 있다고 생각 디렉토리를 만들지 만 소스 코드에 따라 그렇게하지는 않습니다.
— dosentmatter

5

"createTempFile"은 실제로 파일을 만듭니다. 그렇다면 왜 먼저 삭제 한 다음 mkdir을 수행합니까?


1
mkdir ()의 리턴 값을 항상 확인해야합니다. 이것이 거짓이면 디렉토리가 이미 존재한다는 의미입니다. 보안 문제가 발생할 수 있으므로 응용 프로그램에서 오류가 발생하는지 여부를 고려하십시오.
— Sarel Botha

1
다른 답변에서 경쟁 조건에 대한 참고 사항을 참조하십시오.
— Volker Stolz

내가 좋아하는 경주를
— 막아라

4

이 코드는 합리적으로 잘 작동해야합니다.

public static File createTempDir() {
    final String baseTempPath = System.getProperty("java.io.tmpdir");

    Random rand = new Random();
    int randomInt = 1 + rand.nextInt();

    File tempDir = new File(baseTempPath + File.separator + "tempDir" + randomInt);
    if (tempDir.exists() == false) {
        tempDir.mkdir();
    }

    tempDir.deleteOnExit();

    return tempDir;
}

3
디렉토리가 이미 있고 디렉토리에 대한 읽기 / 쓰기 액세스 권한이 없거나 일반 파일 인 경우에는 어떻게합니까? 또한 경쟁 조건이 있습니다.
— Jeremy Huiskamp가

2
또한 deleteOnExit는 비어 있지 않은 디렉토리를 삭제하지 않습니다.
— Trenton

3

이 RFE 와 그 의견 에서 논의 된 바와 같이 , tempDir.delete()먼저 전화 할 수 있습니다. 또는 System.getProperty("java.io.tmpdir")그곳에서 디렉토리를 사용 하고 만들 수 있습니다 . 어느 쪽이든을 호출해야합니다. tempDir.deleteOnExit()그렇지 않으면 파일이 완료된 후 삭제되지 않습니다.


이 속성이 "... temp"가 아닌 "java.io.tmpdir"이 아닙니까? java.sun.com/j2se/1.4.2/docs/api/java/io/File.html
— Andrew Swan

바로 그렇다. 읽은 내용을 반복하기 전에 확인해야합니다.
— Michael Myers

java.io.tmpdir은 공유되므로 다른 발가락을 밟지 않도록 모든 일반적인 부두를 수행해야합니다.
— Thorbjørn Ravn Andersen

3

완료를 위해 이것은 구글 구아바 라이브러리의 코드입니다. 내 코드는 아니지만이 스레드에서 여기에 표시하는 것이 가치 있다고 생각합니다.

  /** Maximum loop count when creating temp directories. */
  private static final int TEMP_DIR_ATTEMPTS = 10000;

  /**
   * Atomically creates a new directory somewhere beneath the system's temporary directory (as
   * defined by the {@code java.io.tmpdir} system property), and returns its name.
   *
   * <p>Use this method instead of {@link File#createTempFile(String, String)} when you wish to
   * create a directory, not a regular file. A common pitfall is to call {@code createTempFile},
   * delete the file and create a directory in its place, but this leads a race condition which can
   * be exploited to create security vulnerabilities, especially when executable files are to be
   * written into the directory.
   *
   * <p>This method assumes that the temporary volume is writable, has free inodes and free blocks,
   * and that it will not be called thousands of times per second.
   *
   * @return the newly-created directory
   * @throws IllegalStateException if the directory could not be created
   */
  public static File createTempDir() {
    File baseDir = new File(System.getProperty("java.io.tmpdir"));
    String baseName = System.currentTimeMillis() + "-";

    for (int counter = 0; counter < TEMP_DIR_ATTEMPTS; counter++) {
      File tempDir = new File(baseDir, baseName + counter);
      if (tempDir.mkdir()) {
        return tempDir;
      }
    }
    throw new IllegalStateException(
        "Failed to create directory within "
            + TEMP_DIR_ATTEMPTS
            + " attempts (tried "
            + baseName
            + "0 to "
            + baseName
            + (TEMP_DIR_ATTEMPTS - 1)
            + ')');
  }

2

나는 같은 문제가있어서 관심있는 사람들을위한 또 다른 대답이며, 위의 하나와 비슷합니다.

public static final String tempDir = System.getProperty("java.io.tmpdir")+"tmp"+System.nanoTime();
static {
    File f = new File(tempDir);
    if(!f.exists())
        f.mkdir();
}

그리고 내 응용 프로그램의 경우 종료시 온도 를 지우는 옵션을 추가하여 종료 후크 를 추가하기로 결정했습니다 .

Runtime.getRuntime().addShutdownHook(new Thread() {
        @Override
        public void run() {
            //stackless deletion
            String root = MainWindow.tempDir;
            Stack<String> dirStack = new Stack<String>();
            dirStack.push(root);
            while(!dirStack.empty()) {
                String dir = dirStack.pop();
                File f = new File(dir);
                if(f.listFiles().length==0)
                    f.delete();
                else {
                    dirStack.push(dir);
                    for(File ff: f.listFiles()) {
                        if(ff.isFile())
                            ff.delete();
                        else if(ff.isDirectory())
                            dirStack.push(ff.getPath());
                    }
                }
            }
        }
    });

이 방법 은 callstack을 사용하지 않고 temp 를 삭제하기 전에 모든 하위 디렉토리와 파일을 삭제하지만 완전히 선택적 이며이 시점에서 재귀로 수행 할 수 있습니다.


2

다른 답변에서 볼 수 있듯이 표준 접근법은 없습니다. 따라서 이미 Apache Commons를 언급 했으므로 Apache Commons IO의 FileUtils를 사용하여 다음과 같은 접근 방식을 제안합니다 .

/**
 * Creates a temporary subdirectory in the standard temporary directory.
 * This will be automatically deleted upon exit.
 * 
 * @param prefix
 *            the prefix used to create the directory, completed by a
 *            current timestamp. Use for instance your application's name
 * @return the directory
 */
public static File createTempDirectory(String prefix) {

    final File tmp = new File(FileUtils.getTempDirectory().getAbsolutePath()
            + "/" + prefix + System.currentTimeMillis());
    tmp.mkdir();
    Runtime.getRuntime().addShutdownHook(new Thread() {

        @Override
        public void run() {

            try {
                FileUtils.deleteDirectory(tmp);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    });
    return tmp;

}

아파치는 요청 된 "표준"에 가장 근접한 라이브러리를 공통으로 사용하고 JDK 7 및 이전 버전 모두에서 작동하기 때문에 선호됩니다. 이것은 또한 "오래된"파일 인스턴스 (스트림 기반)를 반환하고 "새"경로 인스턴스 (버퍼 기반이며 JDK7의 getTemporaryDirectory () 메소드의 결과)가 아닌-> 따라서 대부분의 사람들이 필요할 때 반환합니다 임시 디렉토리를 작성하려고합니다.


1

나는 독특한 이름을 만드는 여러 번의 시도를 좋아하지만이 솔루션조차도 경쟁 조건을 배제하지는 않습니다. 다른 프로세스는 테스트 exists()및 if(newTempDir.mkdirs())메소드 호출 후 미끄러질 수 있습니다 . 나는 네이티브 코드에 의지하지 않고 이것을 안전하게 만드는 방법을 모른다. 내가 묻혀있는 것으로 추정된다 File.createTempFile().


1

Java 7 이전에는 다음을 수행 할 수도 있습니다.

File folder = File.createTempFile("testFileUtils", ""); // no suffix
folder.delete();
folder.mkdirs();
folder.deleteOnExit();

1
좋은 코드입니다. 그러나 Java가 전체 폴더를 한 번에 삭제할 수 없기 때문에 불행히도 "deleteOnExit ()"는 작동하지 않습니다. 모든 파일을 재귀 적으로 삭제해야합니다. /
— Adam Taras

1

이 작은 예를보십시오 :

암호:

try {
    Path tmpDir = Files.createTempDirectory("tmpDir");
    System.out.println(tmpDir.toString());
    Files.delete(tmpDir);
} catch (IOException e) {
    e.printStackTrace();
}


가져 오기 :
java.io.IOException
java.nio.file.Files
java.nio.file.Path

Windows 시스템의 콘솔 출력 :
C : \ Users \ userName \ AppData \ Local \ Temp \ tmpDir2908538301081367877

주석 :
Files.createTempDirectory는 고유 한 ID를 비정상적으로 생성합니다.-2908538301081367877.

참고 :
디렉토리를 재귀 적으로 삭제하려면 다음을 읽으십시오.
Java에서 디렉토리를 재귀 적으로 삭제


0

디렉토리의 고유 이름을 사용 File#createTempFile하고 delete만드는 것이 좋습니다. ShutdownHookJVM 종료시 디렉토리를 (재귀 적으로) 삭제하려면 a 를 추가해야합니다 .


종료 후크가 번거 롭습니다. File # deleteOnExit도 작동하지 않습니까?
— Daniel Hiller

2
#deleteOnExit가 작동하지 않습니다. 비어 있지 않은 디렉토리는 삭제하지 않는다고 생각합니다.
— muriloq

나는 자바 8 실행하는 빠른 테스트를 구현하지만, 임시 폴더가 삭제되지 않은 참조 pastebin.com/mjgG70KG
— 게리
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.