Reputation: 15821
In node.js, how do I generate a unique temporary file name, a la mkstemp(3)
? I want to atomically write a file using fs.rename
.
Upvotes: 80
Views: 118250
Reputation: 54812
I am using Node.js v20.17.0 and utilizing crypto.randomUUID to generate unique filenames and fs.mkdtempSync to create temporary directories within the OS's temp directory (typically /tmp
on Linux) with a unique prefix.
Additionally, I provide cleanup options to ensure that the temporary file and directory are deleted when the program exits successfully (exit
), when the user exits (SIGINT
via Ctrl+C
), or when the process manager requests a graceful shutdown (SIGTERM
).
Code:
import crypto from 'crypto';
import fs from 'fs';
import os from 'os';
import path from 'path';
async function createTempFile(content: string = '', cleanup: boolean = true) {
const uuid = crypto.randomUUID();
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), `${uuid}-`));
const tempFileName = path.join(tempDir, `${uuid}.txt`);
fs.writeFileSync(tempFileName, content);
console.debug(`Temporary file created at: ${tempFileName}`);
if (cleanup) {
const deleteFile = () => {
fs.rmSync(tempDir, { force: true, recursive: true });
};
// Delete temporary file when program exits
process.once('exit', deleteFile);
process.once('SIGINT', deleteFile);
process.once('SIGTERM', deleteFile);
}
return tempFileName;
}
createTempFile();
createTempFile('This file will be deleted.', true);
createTempFile('This file will remain.', false);
Upvotes: 2
Reputation: 2889
Rather than a temporary file, it may be feasible to create a temporary directory using the built in libraries.
https://nodejs.org/api/fs.html#fspromisesmkdtempprefix-options
import fs from 'node:fs/promises';
let folder = await fs.mkdtemp('foo-');
Knowing that the location you are working in, you can use constant names inside.
Upvotes: 12
Reputation: 6144
use npm package tempfile
import tempfile from 'tempfile';
Or
let tempfile = "";
import('tempfile').then(a => {
tempfile = a.default;
});
and then:
let filename = tempfile() //e,g: /tmp/5fc8a50f-e9cd-4420-b62a-e365b3ef2c2a
let imagefilename = tempfile(".jpeg") // e,g: /tmp/5a6dea08-a704-4fa8-9d0a-8ffa5e134543.jpeg
Upvotes: -1
Reputation: 12897
Temp file creation in node and python suffer from race conditions involving permission changes, and cross-platform issues, especially on Windows where it's challenging to test ACL's.
The result is that it can literally hang your machine asking for a temp file in many modern languages. (If node doesn't have access to the temp dir, depending on the ACLs, it can get EEXIST, even when the file doesn't exist - the result of which can be an infinite loop.).
The simplest solution is to use enough entropy that the likelihood of a collision is negligible (in the cryptographic sense).
This also has the side effect of making temp file creation secure across all platforms, without needing to heavily vet the source code. As long as your random numbers are secure, there is no way to predict the file that will be used to usurp permissions or access.
And it helps with programs that need to pass file names around, not file handles.
const crypto = require('crypto');
const os = require('os');
const path = require('path');
function tmpFile(prefix, suffix, tmpdir) {
prefix = (typeof prefix !== 'undefined') ? prefix : 'tmp.';
suffix = (typeof suffix !== 'undefined') ? suffix : '';
tmpdir = tmpdir ? tmpdir : os.tmpdir();
return path.join(tmpdir, prefix + crypto.randomBytes(16).toString('hex') + suffix);
}
The only disadvantage is that this will not work on FAT formatted partitions, which can still be common on USB sticks.
Upvotes: 14
Reputation: 282885
Similar to kinematic's answer, but with 2 bytes extra entropy and letters instead of numbers:
import Crypto from 'crypto';
import {tmpdir} from 'os';
import Path from 'path';
function tmpFile(ext) {
return Path.join(tmpdir(),`archive.${Crypto.randomBytes(6).readUIntLE(0,6).toString(36)}.${ext}`);
}
Usage:
const file = tmpFile('tar.gz'); // "/tmp/archive.1scpz5ew5d.tar.gz"
I'm creating archives, so I chose "archive" as the basename, but you can change it as you see fit.
Upvotes: 6
Reputation: 1945
Try this function, secure and without vulnerabilities. NODE 8.x LTS
function tempFile (name = 'temp_file', data = '', encoding = 'utf8') {
const fs = require('fs');
const os = require('os');
const path = require('path');
return new Promise((resolve, reject) => {
const tempPath = path.join(os.tmpdir(), 'foobar-');
fs.mkdtemp(tempPath, (err, folder) => {
if (err)
return reject(err)
const file_name = path.join(folder, name);
fs.writeFile(file_name, data, encoding, error_file => {
if (error_file)
return reject(error_file);
resolve(file_name)
})
})
})
}
It resolves the PATH of the temp file, rejects mkdtemp or writeFile errors
// C:\Users\MYPC\AppData\Local\Temp\foobar-3HmKod\temp_file
// /temp/Temp/foobar-3HmKod/temp_file
tempFile().then(path => console.log(path)).catch(e => console.log("error", e)) //or
// C:\Users\MYPC\AppData\Local\Temp\foobar-9KHuxg\hola.txt
// /temp/Temp/foobar-9KHuxg/hola.txt
tempFile('hola.txt', 'hello there').then(path => console.log(path)).catch(e => console.log("e", e))
Upvotes: 9
Reputation: 463
Without using any additional plugins:
var crypto = require('crypto');
var fs = require('fs');
var filename = 'foo'+crypto.randomBytes(4).readUInt32LE(0)+'bar';
fs.writeFileSync(filename, 'baz');
EDIT: read comments.
Upvotes: 25