315

I use to create a tempfile, delete it and recreate it as a directory:

temp=`tempfile`
rm -f $temp
  # <breakpoint>
mkdir $temp

The problem is, when it runs to the <breakpoint>, there happens to be another program wants to do the same thing, which mkdir-ed a temp dir with the same name, will cause the failure of this program.

Lenik
  • 13,946
  • 17
  • 75
  • 103

6 Answers6

457

Use mktemp -d. It creates a temporary directory with a random name and makes sure that file doesn't already exist. You need to remember to delete the directory after using it though.

moinudin
  • 134,091
  • 45
  • 190
  • 216
  • 31
    I had to use `mktemp -d -t ` – Heath Borders Oct 03 '13 at 14:24
  • 20
    This is a OS X vs Linux thing. See this question for a version that works on both: http://unix.stackexchange.com/questions/30091/fix-or-alternative-for-mktemp-in-os-x – jwhitlock Jun 09 '14 at 19:25
  • 3
    Also, see the answer below by Ortwin, as that makes sure cleanup is done as well. – Mathiasdm Jan 21 '16 at 10:59
  • 9
    Why do you say "You need to remember to delete the directory after using it though."? Doesn't that kinda defeat the purpose of using a temp directory? – M.K. Safi Aug 31 '17 at 03:59
  • @M.K.Safi I think the main purpose is to create an empty directory where you can store temporary files without cluttering up other parts of the filesystem. You get to choose if/when you want to delete it. If it creates it in /tmp, then it should be cleared when the system restarts anyway. – mwfearnley Feb 19 '21 at 15:01
  • @mwfearnley "... it should be cleared when the system restarts anyway", true, though that could be weeks, months or even years away - best clean-up to avoid space/clutter issues – Straff Feb 23 '23 at 01:47
  • 1
    @Straff fair enough, and it's worth saying that some environments like WSL may never automatically clear out /tmp/, but my point was just that even if you need to delete it afterwards, it doesn't undermine the whole purpose of it. – mwfearnley Feb 23 '23 at 09:10
108

For a more robust solution i use something like the following. That way the temp dir will always be deleted after the script exits.

The cleanup function is executed on the EXIT signal. That guarantees that the cleanup function is always called, even if the script aborts somewhere.

#!/bin/bash    

# the directory of the script
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

# the temp directory used, within $DIR
# omit the -p parameter to create a temporal directory in the default location
WORK_DIR=`mktemp -d -p "$DIR"`

# check if tmp dir was created
if [[ ! "$WORK_DIR" || ! -d "$WORK_DIR" ]]; then
  echo "Could not create temp dir"
  exit 1
fi

# deletes the temp directory
function cleanup {      
  rm -rf "$WORK_DIR"
  echo "Deleted temp working directory $WORK_DIR"
}

# register the cleanup function to be called on the EXIT signal
trap cleanup EXIT

# implementation of script starts here
...

Directory of bash script from here.

Bash traps.

Ortwin Angermeier
  • 5,957
  • 2
  • 34
  • 34
  • 40
    **FreeBSD Caution!** mktemp on FreeBSD doesn't have -p option, and `cleanup` will **rm -rf your current directory!** – madfriend Mar 07 '17 at 10:10
  • 4
    Good point, updated the script to check if the temp dir could be created. – Ortwin Angermeier Mar 07 '17 at 17:02
  • 3
    @madfriend really? if `mktemp` fails, `WORK_DIR` will be empty, meaning the command would just be `rm -rf` with no argument. I don't use FreeBSD but I'd be pretty surprised if `rm -rf` was equivalent to `rm -rf .` – jbg Jul 11 '17 at 08:37
  • 2
    @jbg yes, it seems odd to me now too - it shouldn't be a really big problem. I might have tweaked an old version of this script so that a path to temporary directory was calculated relatively to current directory, resulting in extinction of mankind current directory removal. – madfriend Jul 11 '17 at 10:35
  • I'll give you a -1 for safety reasons with that **rm -rf** thing and the trap, sorry. You are too worried in deleting a temporary directory. Not worth the risk you are causing. – DrBeco Jun 03 '18 at 01:39
  • @DrBeco it all depends on the use case. We use it a lot in build scripts and it works just fine there. And yes i am worried about the temp files on our build server. – Ortwin Angermeier Jun 03 '18 at 18:56
  • 1
    To make it better, you can avoid an empty directory or at least contain the problem within a directory using a solution where you do: 1. `TMPWORKDIR=$(basename 'mktemp -d -p /tmp/git/')` and then 2. `rmdir /tmp/git/"${TMPWORKDIR}"`. If the variable is empty now, you will still fall back to `/tmp/git/` not to the whole system. Consider something like this in the answer and I'll gladly agree. ;) – DrBeco Jun 03 '18 at 22:38
  • @DrBeco I was about to ask the same, why use current dir of script when there's the `/tmp` dir you could use. Also one level deeper, your '/tmp/git` will prevent falling back to `/tmp` accidentally and potentially force-removing files still in use by others. – Davos Jun 06 '18 at 09:13
  • @DrBeco the script will exit if the variable is empty before reaching the trap, or do i miss something? – Ortwin Angermeier Nov 30 '18 at 16:02
  • @Davos we use it to keep the logs/files/whatever close to the test case. It is/can be bothersome to find logs. (use `trap - EXIT´ to disable the trap if eg. a test failes) – Ortwin Angermeier Nov 30 '18 at 16:06
  • @OrtwinAngermeier I guess that makes sense, but wouldn't the logs be deleted when it exits? That would make them * really * hard to find. – Davos Dec 03 '18 at 03:34
83

My favorite one-liner for this is

cd $(mktemp -d)
Matt
  • 9,068
  • 12
  • 64
  • 84
Emmett Butler
  • 5,969
  • 2
  • 29
  • 47
  • 14
    and ``rm $(pwd)``? :P – Arran Cudbard-Bell Dec 16 '14 at 03:10
  • 21
    also useful: `pushd $(mktemp -d)` ... `popd` – Ponkadoodle Dec 24 '14 at 06:54
  • 4
    @ArranCudbard-Bell should be `rm -r $(pwd)` – piggybox Sep 24 '15 at 18:05
  • Depending on what you put in the temp directory, you might might need `rm -rf $(pwd)` to remove it. – Tad Feb 17 '17 at 06:38
  • 45
    @piggybox Frankly, I'd be *very* cautious of using `rm -r $(pwd)`. Consider the possibility that temporary directory creation fails for whatever reason (maybe the /tmp filesystem is full or has been remounted read only due to an error?); then `cd $(mktemp -d)` will evaluate to `cd` which changes to the user's home directory, which would subsequently be deleted. – Jules May 08 '17 at 07:11
  • 1
    It may be safe with `if pushd $(mktemp -d || echo BADMPDIR); then ........ ; rm -r $(pwd); popd; fi` – AndreyS Scherbakov Oct 21 '18 at 03:25
  • For _bash_, the current working dir (pwd) changed in the parentheses will be restored outside the parentheses. So a convenient way is: `tmp=$(mktemp -d); (cd $tmp; ...)` – Lenik Jul 22 '20 at 12:02
21

The following snippet will safely create and then clean up a temporary directory.

The first trap line executes exit 1 command when any of the specified signals is received. The second trap line removes the $TEMPD on program's exit (both normal and abnormal). We initialize these traps after we check that mkdir -d succeeded to avoid accidentally executing the exit trap with $TEMPD in an unknown state.

#!/bin/bash

# set -x # un-comment to see what's going on when you run the script

# Create a temporary directory and store its name in a variable.
TEMPD=$(mktemp -d)

# Exit if the temp directory wasn't created successfully.
if [ ! -e "$TEMPD" ]; then
    >&2 echo "Failed to create temp directory"
    exit 1
fi

# Make sure the temp directory gets removed on script exit.
trap "exit 1"           HUP INT PIPE QUIT TERM
trap 'rm -rf "$TEMPD"'  EXIT
jreisinger
  • 1,493
  • 1
  • 10
  • 21
  • 2
    While this is an interesting solution for the error handling, a bit more explanation of the advantages and possible shortcomings would be nice. – Murphy Oct 30 '18 at 13:41
  • 1.) `-d` checks for directories. 2.) Termination is already the default for those signals. – ceving Mar 10 '20 at 14:57
  • 1
    Don't suggest TMPDIR as variable name here, as that is typically the system's temporary directory. https://en.wikipedia.org/wiki/TMPDIR – spawn Mar 10 '22 at 20:51
  • Thanks @spawn, I renamed the TMPDIR variable. – jreisinger Mar 25 '22 at 08:09
3

Here is a simple explanation about how to create a temp dir using templates.

  1. Creates a temporary file or directory, safely, and prints its name.
  2. TEMPLATE must contain at least 3 consecutive 'X's in last component.
  3. If TEMPLATE is not specified, it will use tmp.XXXXXXXXXX
  4. directories created are u+rwx, minus umask restrictions.

PARENT_DIR=./temp_dirs # (optional) specify a dir for your tempdirs
mkdir $PARENT_DIR

TEMPLATE_PREFIX='tmp' # prefix of your new tempdir template
TEMPLATE_RANDOM='XXXX' # Increase the Xs for more random characters
TEMPLATE=${PARENT_DIR}/${TEMPLATE_PREFIX}.${TEMPLATE_RANDOM}

# create the tempdir using your custom $TEMPLATE, which may include
# a path such as a parent dir, and assign the new path to a var
NEW_TEMP_DIR_PATH=$(mktemp -d $TEMPLATE)
echo $NEW_TEMP_DIR_PATH

# create the tempdir in parent dir, using default template
# 'tmp.XXXXXXXXXX' and assign the new path to a var
NEW_TEMP_DIR_PATH=$(mktemp -p $PARENT_DIR)
echo $NEW_TEMP_DIR_PATH

# create a tempdir in your systems default tmp path e.g. /tmp 
# using the default template 'tmp.XXXXXXXXXX' and assign path to var
NEW_TEMP_DIR_PATH=$(mktemp -d)
echo $NEW_TEMP_DIR_PATH

# Do whatever you want with your generated temp dir and var holding its path

yosefrow
  • 2,128
  • 20
  • 29
2

I need the following features:

  • Put all temp files into a single directory with a specific namespace for reusing.
  • Create temp files with filename prefix and suffix (extension).

With bash script on macOS:


$ namespace="com.namespace.mktemp"

# find directory for reusing
$ ls -d "${TMPDIR}${namespace}"*

# create directory if not exists
$ mktemp -d -t "$namespace"
/var/folders/s_/.../T/com.namespace.mktemp.HjqGT6w2

# create tempfile with directory name and file prefix
$ mktemp -t "com.namespace.mktemp.HjqGT6w2/file-prefix"
/var/folders/s_/.../T/com.namespace.mktemp.HjqGT6w2/file-prefix.sZDvjo14

# add suffix - `mktemp` on macOS does not support `--suffix`
mv "/var/folders/s_/.../file-prefix.sZDvjo14" "/var/folders/s_/.../file-prefix.sZDvjo14.txt"

The gmktemp (brew install coreutils) is a little different:

  • supports --suffix and --tmpdir
  • Xs are required in template and prefix
  • template should not contain directory, set TMPDIR instead

$ namespace="com.namespace.gmktemp"

# create directory if not exists
$ gmktemp -d -t "$namespace.XXXXXXXX"
/var/folders/s_/.../T/com.namespace.gmktemp.BjFtIAyZ

# set TMPDIR
TMPDIR="/var/folders/s_/.../T/com.namespace.gmktemp.BjFtIAyZ"

# create tempfile with directory name and file prefix
$ gmktemp --suffix=".txt" -t "prefix.XXXXXXXX"
/var/folders/s_/.../T/com.namespace.gmktemp.BjFtIAyZ/prefix.LWHj0G95.txt

Míng
  • 2,500
  • 6
  • 32
  • 48