Jak mogę utworzyć plik tymczasowy z określonym rozszerzeniem w .NET?

277

Muszę wygenerować unikalny plik tymczasowy z rozszerzeniem .csv.

To, co teraz robię, to

string filename = System.IO.Path.GetTempFileName().Replace(".tmp", ".csv");

Nie gwarantuje to jednak, że mój plik .csv będzie unikalny.

Wiem, że szanse na kolizję są bardzo niskie (zwłaszcza jeśli uważasz, że nie usuwam plików .tmp), ale ten kod nie wygląda mi dobrze.

Oczywiście mogłem ręcznie generować losowe nazwy plików, aż w końcu znajdę unikalną (co nie powinno być problemem), ale jestem ciekawy, czy inni znaleźli dobry sposób na poradzenie sobie z tym problemem.

Brann
źródło
4
niektóre zastrzeżenia dotyczące GetTempFileName Metoda GetTempFileName zgłosi wyjątek IOException, jeśli jest używany do tworzenia ponad 65535 plików bez usuwania poprzednich plików tymczasowych. Metoda GetTempFileName zgłosi wyjątek IOException, jeśli nie jest dostępna unikalna nazwa pliku tymczasowego. Aby rozwiązać ten błąd, usuń wszystkie niepotrzebne pliki tymczasowe.
Max Hodges,
2
Pliki tymczasowe są wykorzystywane głównie do określonego zestawu warunków. Jeśli rozszerzenie pliku jest ważne, zastanawiam się, czy może użycie GetTempFileName nie jest rozwiązaniem do zapisu. Wiem, że minęło dużo czasu, ale jeśli powiesz nam więcej o kontekście i potrzebie tych plików, możemy zaproponować lepsze podejście. więcej tutaj: support.microsoft.com/kb/92635?wa=wsignin1.0
Max Hodges
1
Pamiętaj, że GetTempFileName() tworzy nowy plik za każdym razem, gdy go wywołujesz. - Jeśli natychmiast zmienisz ciąg znaków na coś innego, właśnie utworzyłeś nowy plik zero-bajtowy w swoim katalogu tymczasowym (i jak zauważyli inni, ostatecznie spowoduje to awarię, gdy trafisz tam 65535 plików ...) - - Aby tego uniknąć, usuń wszystkie pliki, które utworzysz w tym folderze (w tym te zwrócone przez GetTempFileName(), najlepiej w końcu).
BrainSlugs83

Odpowiedzi:

354

Gwarantujemy (statystycznie) unikalność:

string fileName = System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".csv"; 

(Cytat z artykułu wiki na temat prawdopodobieństwa kolizji:

... roczne ryzyko uderzenia meteorytu szacuje się na jedną szansę na 17 miliardów [19], co oznacza, że ​​prawdopodobieństwo wynosi około 0,00000000006 (6 × 10-11), co odpowiada prawdopodobieństwu wytworzenia kilkudziesięciu tryliony UUID w ciągu roku i posiadanie jednego duplikatu. Innymi słowy, tylko po wygenerowaniu 1 miliarda UUID co sekundę przez następne 100 lat prawdopodobieństwo utworzenia tylko jednego duplikatu wyniesie około 50%. Prawdopodobieństwo jednego duplikatu wynosiłoby około 50%, gdyby każda osoba na ziemi posiadała 600 milionów UUID

EDYCJA: Zobacz także komentarze JaredPara.

Mitch Pszenica
źródło
29
Ale nie ma gwarancji, że znajdzie się w miejscu do zapisu
JaredPar
33
I są one nie muszą być unikalne w ogóle, tylko statystycznie nieprawdopodobne.
paxdiablo
30
@Pax: masz więcej szans na wygraną w loterii 1000 razy z rzędu niż generowanie dwóch idebtycznych przewodników. To chyba dość wyjątkowe ...
Mitch Wheat
11
@Mitch powodem nie jest wyjątkowy, ponieważ jest to możliwe dla mnie po prostu utworzyć plik o tej samej nazwie w tej samej ścieżce. Identyfikatory GUID, chociaż są unikalne, są również przewidywalne, co oznacza, że ​​biorąc pod uwagę wystarczającą ilość informacji, mógłbym odgadnąć następny zestaw prowadnic wygenerowanych przez twoje urządzenie
JaredPar
207
dobrzy panowie, starajcie się bardziej trzymać głowę z dala od chmur. Podejście to: wygeneruj losową nazwę pliku, a następnie utwórz go, jeśli nie istnieje. Więc pomóż mu to ładnie zakodować. Cała ta rozmowa o pseudolosowych generatorach i uniwersalnie unikatowych liczbach jest zupełnie niepotrzebna.
Max Hodges
58

Wypróbuj tę funkcję ...

public static string GetTempFilePathWithExtension(string extension) {
  var path = Path.GetTempPath();
  var fileName = Guid.NewGuid().ToString() + extension;
  return Path.Combine(path, fileName);
}

Zwróci pełną ścieżkę z wybranym rozszerzeniem.

Uwaga: nie ma gwarancji utworzenia unikalnej nazwy pliku, ponieważ ktoś inny mógł już technicznie utworzyć ten plik. Jednak szanse, że ktoś zgadnie kolejny przewodnik utworzony przez twoją aplikację i stworzy go, są bardzo niskie. Można założyć, że będzie to wyjątkowe.

JaredPar
źródło
4
Być może Path.ChangeExtension () byłby bardziej elegancki niż Guid.NewGuid (). ToString () + rozszerzenie
Ohad Schneider
@ohadsc - rzeczywiście, Guid.NewGuid().ToString() + extensionto nawet nie jest poprawne, powinno byćGuid.NewGuid().ToString() + "." + extension
Stephen Swensen
4
Podejrzewam, że to zależy od kontraktu metody (czy tego się spodziewa, .txtczy txt), ale ponieważ ChangeExtensionobsługuje oba przypadki, nie może zaszkodzić
Ohad Schneider
1
Nazwij to Sufiksem zamiast Rozszerzenia i chyba wszyscy są zadowoleni
Chiel ten Brinke
46
public static string GetTempFileName(string extension)
{
  int attempt = 0;
  while (true)
  {
    string fileName = Path.GetRandomFileName();
    fileName = Path.ChangeExtension(fileName, extension);
    fileName = Path.Combine(Path.GetTempPath(), fileName);

    try
    {
      using (new FileStream(fileName, FileMode.CreateNew)) { }
      return fileName;
    }
    catch (IOException ex)
    {
      if (++attempt == 10)
        throw new IOException("No unique temporary file name is available.", ex);
    }
  }
}

Uwaga: działa to podobnie jak Path.GetTempFileName. Tworzony jest pusty plik, aby zarezerwować nazwę pliku. Wykonuje 10 prób w przypadku kolizji generowanych przez Path.GetRandomFileName ();

Maxence
źródło
Należy pamiętać, że Path.GetRandomFileName()faktycznie tworzy plik zero-bajtowy na dysku i zwraca pełną ścieżkę tego pliku. Nie używasz tego pliku, a jedynie jego nazwa do zmiany rozszerzenia. Jeśli więc nie upewnisz się, że usuwasz te pliki tymczasowe, po 65535 wywołaniach tej funkcji zacznie się to nie powieść.
Vladimir Baranov,
7
Mieszałeś GetTempFileName()i GetRandomFileName(). GetTempFileName()utwórz plik zerowy jak w mojej metodzie, ale GetRandomFileName()nie tworzy pliku. Z dokumentacji:> W przeciwieństwie do GetTempFileName, GetRandomFileName nie tworzy pliku. Twój link prowadzi do niewłaściwej strony.
Maxence
19

Możesz także alternatywnie użyć System.CodeDom.Compiler.TempFileCollection .

string tempDirectory = @"c:\\temp";
TempFileCollection coll = new TempFileCollection(tempDirectory, true);
string filename = coll.AddExtension("txt", true);
File.WriteAllText(Path.Combine(tempDirectory,filename),"Hello World");

Tutaj użyłem rozszerzenia txt, ale możesz określić, co chcesz. Ustawiłem również flagę keep na wartość true, aby plik tymczasowy był zachowywany po użyciu. Niestety TempFileCollection tworzy jeden losowy plik na rozszerzenie. Jeśli potrzebujesz więcej plików tymczasowych, możesz utworzyć wiele instancji TempFileCollection.

Mehmet Aras
źródło
10

Dlaczego nie sprawdzić, czy plik istnieje?

string fileName;
do
{
    fileName = System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".csv";
} while (System.IO.File.Exists(fileName));
Matías
źródło
9
File.Exists informuje o przeszłości i dlatego nie jest wiarygodny. Pomiędzy zwrotem File.Exist a wykonaniem kodu możliwe jest utworzenie pliku.
JaredPar
4
... to jesteś bezpieczny ze swoim własnym programem, ale może nie z innym procesem zapisującym plik w tym samym miejscu docelowym ...
Koen
@JaredPar i jaka jest możliwość tego zdarzenia?
Migol
2
@Migol Jest bardzo niski i z definicji wyjątkowy. Hmmm, dokładnie do jakich wyjątków zostały zaprojektowane.
Cody Gray
3
@CodyGray szansa na starcie Guid wynosi 1/2 ^ 128. Szansa, że ​​stanie się to 2 razy, to 1/2 ^ 256 itd. Nie przejmuj się!
Migol,
9

Dokumentacja MSDN dla GetTempFileName w C ++ omawia twoje obawy i odpowiada na nie:

GetTempFileName nie jest w stanie zagwarantować, że nazwa pliku jest unikalna .

Wykorzystywane są tylko dolne 16 bitów niepowtarzalnego parametru. Ogranicza to GetTempFileName do maksymalnie 65 535 unikalnych nazw plików, jeśli parametry lpPathName i lpPrefixString pozostają takie same.

Ze względu na algorytm używany do generowania nazw plików GetTempFileName może słabo działać podczas tworzenia dużej liczby plików z tym samym prefiksem. W takich przypadkach zaleca się tworzenie unikalnych nazw plików na podstawie identyfikatorów GUID .

Pułkownik Panika
źródło
2
Metoda GetTempFileName zgłosi wyjątek IOException, jeśli jest używany do tworzenia ponad 65535 plików bez usuwania poprzednich plików tymczasowych. Metoda GetTempFileName zgłosi wyjątek IOException, jeśli nie jest dostępna unikalna nazwa pliku tymczasowego. Aby rozwiązać ten błąd, usuń wszystkie niepotrzebne pliki tymczasowe.
Max Hodges,
To niepełny cytat. Odpowiedni cytat: „ Jeśli uUnique nie jest równy zero , musisz sam utworzyć plik. Tworzona jest tylko nazwa pliku, ponieważ GetTempFileName nie jest w stanie zagwarantować, że nazwa pliku jest unikalna”. Jeśli nazwiesz to tak, jak wszyscy tutaj dyskutują, uUnique wyniesie zero.
jnm2
6

Co powiesz na:

Path.Combine(Path.GetTempPath(), DateTime.Now.Ticks.ToString() + "_" + Guid.NewGuid().ToString() + ".csv")

Jest wysoce nieprawdopodobne, aby komputer wygenerował tego samego Guida w tym samym momencie. Jedyną słabością, którą tu widzę, jest wpływ DateTime.Now.Ticks na wydajność.

IRBaboon
źródło
5

Możesz także wykonać następujące czynności

string filename = Path.ChangeExtension(Path.GetTempFileName(), ".csv");

i to również działa zgodnie z oczekiwaniami

string filename = Path.ChangeExtension(Path.GetTempPath() + Guid.NewGuid().ToString(), ".csv");
Michał Prewecki
źródło
2
to się nie powiedzie, jeśli istnieje plik temp.csv i utworzysz temp.tmp, a następnie zmienisz rozszerzenie na csv
David
Nie, nie będzie ... GetTempFileName () tworzy unikalną nazwę pliku ... do pewnego limitu 32 KB, w którym to momencie musisz usunąć niektóre pliki, ale myślę, że moje rozwiązanie jest poprawne. To źle, gdybym przekazał ścieżkę pliku do ChangeExtension, co nie jest gwarantowane, że jest unikalne, ale nie takie jest moje rozwiązanie.
Michael Prewecki
11
GetTempFileName gwarantuje, że ścieżka, którą zwróci, będzie unikalna. Nie chodzi o to, że ścieżka, którą zwraca + „.csv”, będzie unikalna. Zmiana rozszerzenia w ten sposób może się nie powieść, jak powiedział David.
Marcus Griep
5
GetTempFileName tworzy plik, więc twoim pierwszym przykładem jest wyciek zasobów.
Gary McGill,
3

Moim zdaniem większość odpowiedzi zaproponowano tutaj jako nieoptymalne. Najbliżej jest oryginalny zaproponowany początkowo przez Branna.

Nazwa pliku tymczasowego musi być

  • Wyjątkowy
  • Bez konfliktów (jeszcze nie istnieje)
  • Atomic (tworzenie nazwy i pliku w tej samej operacji)
  • Trudno zgadnąć

Z powodu tych wymagań nie jest cholernym pomysłem samodzielne zaprogramowanie takiej bestii. Inteligentni ludzie piszący biblioteki IO martwią się takimi rzeczami jak blokowanie (w razie potrzeby) itp. Dlatego nie widzę potrzeby przepisywania System.IO.Path.GetTempFileName ().

To, nawet jeśli wygląda na niezdarne, powinno wykonać zadanie:

//Note that this already *creates* the file
string filename1 = System.IO.Path.GetTempFileName()
// Rename and move
filename = filename.Replace(".tmp", ".csv");
File.Move(filename1 , filename);
Daniel C. Oderbolz
źródło
2
Ale to nie jest wolne od konfliktów, „csv” może już istnieć i zostać zastąpione.
xvan
3
File.Movepodnosi, IOExceptionjeśli plik docelowy już istnieje. msdn.microsoft.com/en-us/library/…
joshuanapoli
2

Może ci się to przydać ... Aby stworzyć temp. folder i zwróć jako ciąg w VB.NET.

Łatwo konwertowalny do C #:

Public Function GetTempDirectory() As String
    Dim mpath As String
    Do
        mpath = System.IO.Path.Combine(System.IO.Path.GetTempPath, System.IO.Path.GetRandomFileName)
    Loop While System.IO.Directory.Exists(mpath) Or System.IO.File.Exists(mpath)
    System.IO.Directory.CreateDirectory(mpath)
    Return mpath
End Function
Szymon
źródło
2

Wydaje mi się, że to działa dobrze: sprawdza obecność pliku i tworzy plik, aby upewnić się, że jest to zapisywalna lokalizacja. Powinien działać poprawnie, możesz go zmienić, aby zwracał bezpośrednio FileStream (co zwykle jest potrzebne do pliku tymczasowego):

private string GetTempFile(string fileExtension)
{
  string temp = System.IO.Path.GetTempPath();
  string res = string.Empty;
  while (true) {
    res = string.Format("{0}.{1}", Guid.NewGuid().ToString(), fileExtension);
    res = System.IO.Path.Combine(temp, res);
    if (!System.IO.File.Exists(res)) {
      try {
        System.IO.FileStream s = System.IO.File.Create(res);
        s.Close();
        break;
      }
      catch (Exception) {

      }
    }
  }
  return res;
} // GetTempFile
Paolo Iommarini
źródło
2

Łatwa funkcja w C #:

public static string GetTempFileName(string extension = "csv")
{
    return Path.ChangeExtension(Path.GetTempFileName(), extension);
}
Ranch Camal
źródło
GetTempFileName () tworzy plik tymczasowy w folderze tymczasowym. W rezultacie zostaną utworzone dwa pliki tymczasowe, z których jeden wycieknie.
evgenybf
1

Mam mieszane @Maxence i @Mitch Pszenica odpowiedzi pamiętając chcę semantyczne metody GetTempFileName (nazwa pliku to nazwa nowego pliku utworzonego) dodanie rozszerzenia preferowany.

string GetNewTempFile(string extension)
{
    if (!extension.StartWith(".")) extension="." + extension;
    string fileName;
    bool bCollisions = false;
    do {
        fileName = Path.Combine(System.IO.Path.GetTempPath(), Guid.NewGuid().ToString() + extension);
        try
        {
            using (new FileStream(fileName, FileMode.CreateNew)) { }
            bCollisions = false;
        }
        catch (IOException)
        {
            bCollisions = true;
        }
    }
    while (bCollisions);
    return fileName;
}
Fil
źródło
0

Oto co robię:

string tStamp = String.Format ("{0: rrrrMMdd.HHmmss}", DateTime.Now);
ciąg ProcID = Process.GetCurrentProcess (). Id.ToString ();
ciąg tmpFolder = System.IO.Path.GetTempPath ();
string outFile = tmpFolder + ProcID + "_" + tStamp + ".txt";
Michael Fitzpatrick
źródło
Dobrze: zawiera identyfikator procesu Źle: nie zawiera identyfikatora wątku (chociaż można to uruchomić wewnątrz zamka) Źle: znacznik czasu ma rozdzielczość tylko 1 sekundy. W wielu aplikacjach często powstaje wiele plików na sekundę.
andrewf
0

Jest to prosty, ale skuteczny sposób generowania przyrostowych nazw plików. Będzie szukał bezpośrednio w bieżącym (możesz łatwo wskazać to gdzie indziej) i szukał plików za pomocą podstawowej YourApplicationName * .txt (ponownie możesz to łatwo zmienić). Rozpocznie się o 0000, tak aby pierwsza nazwa pliku to YourApplicationName0000.txt. jeśli z jakiegoś powodu istnieją nazwy plików ze śmieciami (czyli nie liczbami) między lewą a prawą częścią, pliki te zostaną zignorowane na podstawie wywołania tryparse.

    public static string CreateNewOutPutFile()
    {
        const string RemoveLeft = "YourApplicationName";
        const string RemoveRight = ".txt";
        const string searchString = RemoveLeft + "*" + RemoveRight;
        const string numberSpecifier = "0000";

        int maxTempNdx = -1;

        string fileName;
        string [] Files = Directory.GetFiles(Directory.GetCurrentDirectory(), searchString);
        foreach( string file in Files)
        {
            fileName = Path.GetFileName(file);
            string stripped = fileName.Remove(fileName.Length - RemoveRight.Length, RemoveRight.Length).Remove(0, RemoveLeft.Length);
            if( int.TryParse(stripped,out int current) )
            {
                if (current > maxTempNdx)
                    maxTempNdx = current;
            }
        }
        maxTempNdx++;
        fileName = RemoveLeft + maxTempNdx.ToString(numberSpecifier) + RemoveRight;
        File.CreateText(fileName); // optional
        return fileName;
    }
Wyświetlana nazwa
źródło
0

Na podstawie odpowiedzi, które znalazłem w Internecie, dochodzę do mojego kodu w następujący sposób:

public static string GetTemporaryFileName()
{       
    string tempFilePath = Path.Combine(Path.GetTempPath(), "SnapshotTemp");
    Directory.Delete(tempFilePath, true);
    Directory.CreateDirectory(tempFilePath);
    return Path.Combine(tempFilePath, DateTime.Now.ToString("MMddHHmm") + "-" + Guid.NewGuid().ToString() + ".png");
}

Jak wskazał Jay Hilyard w książce kucharskiej C #, Stephen Teilhet wskazał na użycie pliku tymczasowego w aplikacji :

  • powinieneś użyć pliku tymczasowego, ilekroć potrzebujesz tymczasowo przechowywać informacje do późniejszego pobrania.

  • Jedyną rzeczą, o której musisz pamiętać, jest usunięcie tego pliku tymczasowego przed zakończeniem aplikacji, która go utworzyła.

  • Jeśli nie zostanie usunięty, pozostanie w katalogu tymczasowym użytkownika, dopóki użytkownik nie usunie go ręcznie.

Speedoops
źródło
-2

Myślę, że powinieneś spróbować:

string path = Path.GetRandomFileName();
path = Path.Combine(@"c:\temp", path);
path = Path.ChangeExtension(path, ".tmp");
File.Create(path);

Generuje unikalną nazwę pliku i tworzy plik o tej nazwie w określonej lokalizacji.

Smita
źródło
3
To rozwiązanie ma wiele problemów. nie można łączyć C: \ temp ze ścieżką bezwzględną, c: \ temp może nie być zapisywalny i nie gwarantuje, że wersja pliku .tmp jest unikalna.
Mark Lakata