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을 사용중인 경우 문제점 @RuleTemporaryFolder해결하십시오.

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

JavaDocTempDirectoryJavaDoc 에 대한 추가 정보

그레들 :

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.