145

While running a script, I want to create a temporary file in /tmp directory.

After execution of that script, that will be cleaned by that script.

How to do that in shell script?

184
tmpfile=$(mktemp /tmp/abc-script.XXXXXX)
: ...
rm "$tmpfile"

You can make sure that a file is deleted when the scripts exits (including kills and crashes) by opening a file descriptor to the file and deleting it. The file keeps available (for the script; not really for other processes but /proc/$PID/fd/$FD is a work-around) as long as the file descriptor is open. When it gets closed (which the kernel does automatically when the process exits) the filesystem deletes the file.

tmpfile=$(mktemp /tmp/abc-script.XXXXXX)
exec 3>"$tmpfile"
rm "$tmpfile"
: ...
echo foo >&3
  • 4
    Good answer, elegant solution with the file descriptor in case of a crash +1 – chaos Jan 30 '15 at 7:29
  • 2
    /proc - except for systems that don't have it. – Dennis Williamson Jan 30 '15 at 20:33
  • 3
    what does the exec 3> "$tmpfile" do? Isn't that only useful if the tmpfile is a stand-alone script? – Alexej Magura Nov 29 '16 at 18:36
  • 5
    How do you read from the created FD? – eckes Apr 7 '17 at 9:37
  • 2
    @eckes You can use cat <3 or something similar. Remember stdin and stdout just happen to live on 1 and 2, you can move them around to other descriptors easily. – dragon788 Jul 21 '17 at 22:02
61

Use mktemp to create a temporary file or directory:

temp_file=$(mktemp)

Or for a direcotry:

temp_dir=$(mktemp -d)

At the end of the script you have to delete the temporary file/dir:

rm ${temp_file}
rm -R ${temp_dir}

mktemp creates file in the /tmp directory or in the drectory given with the --tmpdir argument.

  • 18
    You can use trap "rm -f $temp_file" 0 2 3 15 right after creating the file so that when the script exits or is stopped with ctrl-C the file is still removed. – wurtel Jan 30 '15 at 7:27
  • 1
    @wurtel What happens if EXIT is the only hook for trap? – Hauke Laging Jan 30 '15 at 7:41
  • 3
    @HaukeLaging Then the trap doesn't fire if the script is stopped with Ctrl+C. A thing to note is that TRAP doesn't help if you kill -9 $somepid. That particular kill signal is insta-death with nothing else happening. – dragon788 Jul 21 '17 at 22:08
  • 4
    @dragon788 Have you tried that? You should. bash -c 'echo $$; trap "echo foo" 0; sleep 5' – Hauke Laging Aug 5 '17 at 6:22
  • Trapping EXIT is enough. – Kusalananda Jun 21 '18 at 12:56
15

If you're on system which has mktemp, you should use it as other answers.

With POSIX toolchest:

umask 0177
tmpfile=/tmp/"$0"."$$"."$(awk 'BEGIN {srand();printf "%d\n", rand() * 10^10}')"
trap 'rm -f -- "$tmpfile"' INT TERM HUP EXIT
: > "$tmpfile"
  • What happens if EXIT is the only hook for trap? – Hauke Laging Jan 30 '15 at 7:40
  • @HaukeLaging: tmpfile still be removed before script exit, but not when script received other signals. – cuonglm Jan 30 '15 at 8:00
  • That's not what happens here (GNU bash, Version 4.2.53). – Hauke Laging Jan 30 '15 at 8:03
  • @HaukeLaging: What do you mean That's not what happens? – cuonglm Jan 30 '15 at 8:05
  • 2
    mktemp originated in HP/UX with a different syntax. Todd C. Miller created a different one for OpenBSD in the mid-90s (copied by FreeBSD and NetBSD) and later made it also available as a standalone utility (www.mktemp.org). That's the one that was typically used on Linux until a (mostly compatible) mktemp utility was added to the GNU coreutils in 2007. Just to say one cannot really say mktemp is a GNU utility. – Stéphane Chazelas Jan 30 '15 at 22:01
13

Some shells have the feature built-in.

zsh

zsh's =(...) form of process substitution uses a temporary file. For instance =(echo test) expands to the path of a temporary file that contains test\n.

$ {cat $file; ls -l /dev/fd/3; echo test2 >&3; cat $file} 3<> ${file::==(echo test)}
test
lrwx------ 1 stephane stephane 64 Jan 30 11:19 /dev/fd/3 -> /tmp/zshMLbER0
test2

That file is automatically removed, once the command has finished.

bash/zsh on Linux.

Here-files or here-strings in bash and zsh are implemented as deleted temporary files.

So if you do:

exec 3<<< test

The file descriptor 3 is connected to a deleted temporary file that contains test\n.

You can get its content with:

cat <&3

If on Linux, you can also read or write to that file via /dev/fd/3

$ exec 3<<< test
$ cat <&3
test
$ echo foo > /dev/fd/3
$ cat /dev/fd/3
foo

(some other shells use pipes, or may use /dev/null if the here doc is empty).

POSIX

There is no mktemp POSIX utility. POSIX however specifies a mkstemp(template) C API, and the m4 standard utility exposes that API with the mkstemp() m4 function by the same name.

mkstemp() gives you a file name with a random part that was guaranteed not to exist at the time the function was called. It does create the file with permissions 0600 in a race-free way.

So, you could do:

tmpfile=$(
  echo 'mkstemp(template)' |
    m4 -D template="${TMPDIR:-/tmp}/baseXXXXXX"
) || exit

Note however that you need to handle the clean-up upon exit, though if you only need to write and read the file a fixed number of times, you could open it and delete it just after creating like for the here-doc/here-string approach above:

tmpfile=$(
  echo 'mkstemp(template)' |
    m4 -D template="${TMPDIR:-/tmp}/baseXXXXXX"
) || exit

# open once for writing, twice for reading:
exec 3> "$tempfile" 4< "$tempfile" 5< "$tempfile"

rm -f -- "$tmpfile"

cmd >&3   # store something in the temp file
exec 3>&- # fd no longer needed

# read the content twice:
cat <&4
cat <&5

You could open the file for reading once, and rewind in between two reads, however there's no POSIX utility that can do that rewinding (lseek()), so you can't do it portably in a POSIX script (zsh (sysseek builtin) and ksh93 (<#((...)) operator) can do it though).

  • 1
    Bash also has process substitution using <() – WinnieNicklaus Jan 30 '15 at 20:45
  • 3
    @WinnieNicklaus, yes, but that doesn't use temporary files so is irrelevant here. Process substitution was introduced by ksh, copied by bash and zsh, and zsh extended it with a 3rd form: =(...). – Stéphane Chazelas Jan 30 '15 at 21:01
6

Here is a little bit improved answer in the line of Hauke Laging's:

#!/bin/bash

tmpfile=$(mktemp)  # Create a temporal file in the default temporal folder of the system

# Lets do some magic for the tmpfile to be removed when this script ends, even if it crashes
exec {FD_W}>"$tmpfile"  # Create file descriptor for writing, using first number available
exec {FD_R}<"$tmpfile"  # Create file descriptor for reading, using first number available
rm "$tmpfile"  # Delete the file, but file descriptors keep available for this script

# Now it is possible to work with the temporal file
echo foo >&$FD_W
echo bar >&$FD_W  # Note that file descriptor always concatenates, not overwrites

cat <&$FD_R
  • It should be noted that the content is available only once. I.e. if I do cat <&$FD_R for the second time, no output is produced. See unix.stackexchange.com/questions/166482/…. Is there any way to have the file automatically deleted if the program crashes, but making it accessible multiple times? – smihael Aug 24 '17 at 22:09
0

My workflow typically with temp files is because of some bash script I'm testing. I want to tee it up so I can see that it's working and save the output for the next iteration of my process. I've created a file called tmp

#!/bin/bash
echo $(mktemp /tmp/$(date +"%Y-%m-%d_%T_XXXXXX"))

so that I can use it like

$ some_command --with --lots --of --stuff | tee $(tmp)

The reason I like the datetime formatted before the random values is it allows me to find the tmp file that I just made easily, and I don't have to think about what to name it next time (and focus on just getting my dang script to work).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.