323

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?

Hauke Laging
  • 90,279
Bhuvanesh
  • 3,365
  • 3
  • 13
  • 7

7 Answers7

417
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.

# create temporary file
tmpfile=$(mktemp /tmp/abc-script.XXXXXX)

create file descriptor 3 for writing to a temporary file so that

echo ... >&3 writes to that file

exec 3>"$tmpfile"

create file descriptor 4 for reading from the same file so that

the file seek positions for reading and writing can be different

exec 4<"$tmpfile"

delete temp file; the directory entry is deleted at once; the reference counter

of the inode is decremented only after the file descriptor has been closed.

The file content blocks are deallocated (this is the real deletion) when the

reference counter drops to zero.

rm "$tmpfile"

your script continues

: ...

example of writing to file descriptor

echo foo >&3

your script continues

: ...

reading from that file descriptor

head -n 1 <&4

close the file descriptor (done automatically when script exits)

see section 2.7.6 of the POSIX definition of the Shell Command Language

exec 3>&-

andyb
  • 3
Hauke Laging
  • 90,279
  • 9
    Good answer, elegant solution with the file descriptor in case of a crash +1 – chaos Jan 30 '15 at 07:29
  • 2
    /proc - except for systems that don't have it. – Dennis Williamson Jan 30 '15 at 20:33
  • 7
    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
  • 2
    @AlexejMagura That command creates the file descriptor which is to be used instead of a (regular) file path in order to solve the problem. – Hauke Laging Jan 02 '17 at 00:42
  • I want to put the second style into a shell function. Since the process isn't exiting, does that mean I need to close the file descriptor at the end of the function call? – CMCDragonkai Feb 05 '17 at 06:36
  • 8
    How do you read from the created FD? – eckes Apr 07 '17 at 09: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
  • Can this work against directories, like a directory descriptor? – CMCDragonkai Oct 11 '18 at 03:36
  • @CMCDragonkai You can do mkdir /tmp/del ; cd /tmp/del ; rmdir /tmp/del; ls -l /proc/self/cwd but it seems less useful than with files because you cannot store anything in such a directory (except for "deleted files"). – Hauke Laging Oct 16 '18 at 20:21
  • @HaukeLaging what do you mean by store anything except for deleted files. I was wondering why can't you store files in a directory descriptor. Is it the shell, the syscall, or the actual filesystem preventing this? – CMCDragonkai Oct 17 '18 at 01:33
  • @CMCDragonkai In contrast to regular files you cannot delete non-empty directories. – Hauke Laging Oct 17 '18 at 19:52
  • @HaukeLaging I'm primarily looking for a way to do an atomic directory commit. Being able to use a temporary directory for staging and then performing a rename op to atomically commit is cool. But if that temporary directory is guaranteed to be deleted when the process is, then I don't have to worry about external garbage collection. – CMCDragonkai Oct 18 '18 at 01:22
  • 7
    " You can use cat <3 or something similar." actually that reads from a file named 3 @dragon788. Also, cat <&3 will give Bad file descriptor. I'd appreciate it if you either fix it or remove it; misinformation doesn't much help. – erik258 Feb 21 '19 at 05:24
  • 4
    @HaukeLaging will you update this to show how to read from the file descriptor? Automatically deleting a file you write to and don't read from isn't much good. – erik258 Feb 21 '19 at 06:13
  • 4
    @DanielFarrell I asked your question separately and got an answer here. – Jonah Mar 31 '19 at 04:27
  • 1
    Note that the latter technique probably does not work on Cygwin or Mingw, because the Windows semantics for file deletions are different. – Robin Green Nov 09 '19 at 08:07
  • It would be great if the above code could be commented for those of us unfamiliar with file descriptors and this technique. – Matt Sephton May 23 '21 at 11:24
  • @MattSephton The text above the code explains what happens there. Do you want an explanation what file descriptors are? Seems a bit out of scope for this question to me. – Hauke Laging May 23 '21 at 15:06
  • The above text isn't a complete description of the script IMHO. I don't want an explanation of file descriptors, or I'd have asked for that. – Matt Sephton May 23 '21 at 15:17
  • Thanks for the edit && comments! – Matt Sephton May 23 '21 at 20:12
140

Use mktemp to create a temporary file. The utility returns the full path of the created file.

temp_file=$(mktemp)

Or, to create a temporary directory:

temp_dir=$(mktemp -d)

At the end of the script, you may want to delete the temporary file or directory:

rm "${temp_file}"
rm -r "${temp_dir}"

Note: mktemp creates a file in the /tmp directory or in the directory given with the --tmpdir argument. See the utility's manual for other options and how to modify its behaviour in other ways.

Kusalananda
  • 333,661
chaos
  • 48,171
  • 50
    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 07:27
  • 3
    @wurtel What happens if EXIT is the only hook for trap? – Hauke Laging Jan 30 '15 at 07:41
  • 4
    @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
  • 7
    @dragon788 Have you tried that? You should. bash -c 'echo $$; trap "echo foo" 0; sleep 5' – Hauke Laging Aug 05 '17 at 06:22
  • 1
    Trapping EXIT is enough. – Kusalananda Jun 21 '18 at 12:56
  • 28
    For those wondering, the trailing integers in trap "rm -f $temp_file" 0 2 3 15 are the signals upon which to run the first argument. 0: exit shell, 2: Interrupt, 3: Quit, 15: Terminate. – ijoseph May 23 '20 at 18:41
  • 1
    For those who would prefer to write the symbolic names of the signals, the equivalent is trap "rm -f $temp_file" EXIT INT QUIT TERM – sengi Oct 06 '22 at 09:39
  • On older systems, mktemp fails without argument and prints something like Usage: mktemp [-d] [-q] [-u] template instead – Tino May 07 '23 at 14:24
34

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-documents or here-strings in bash versions prior to 5.1 and zsh are implemented as deleted temporary files (as was the case in the Bourne shell which introduced here-documents in the late 70s).

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, though with bash version 5.0, you'd first to need to restore write permissions to it (which bash explicitly removes in that version):

$ exec 3<<< test
$ cat <&3
test
$ chmod u+w /dev/fd/3 # only needed in bash 5.0
$ 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).

20

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"
cuonglm
  • 153,898
  • What happens if EXIT is the only hook for trap? – Hauke Laging Jan 30 '15 at 07:40
  • @HaukeLaging: tmpfile still be removed before script exit, but not when script received other signals. – cuonglm Jan 30 '15 at 08:00
  • That's not what happens here (GNU bash, Version 4.2.53). – Hauke Laging Jan 30 '15 at 08:03
  • 1
    @HaukeLaging: What do you mean That's not what happens? – cuonglm Jan 30 '15 at 08:05
  • The EXIT trap is triggered in any case. May differe from shell to shell, though. – Hauke Laging Jan 30 '15 at 08:07
  • @HaukeLaging: Yes, of course. I mean when you specify other signal, i.e INT, tmpfile will be removed immediately when you press Ctrl+C, not when script exit. – cuonglm Jan 30 '15 at 08:10
  • 4
    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
  • @StéphaneChazelas: Thanks for this information, I updated my answer. – cuonglm Jan 31 '15 at 01:25
15

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
SwanS
  • 250
  • 4
    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 https://unix.stackexchange.com/questions/166482/cat-stdin-wont-work-twice-in-script/166485.

    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
  • 1
    @smihael You can simply create a second exec {FD_R2}<"$tmpfile" before the call to rm "$tmpfile" Then, to read it first time, use cat <&$FD_R. And, to read it the second time, use cat <&$FD_R2. To generalize, if you need to read it n times, you can create n read file descriptors. – Gino Apr 21 '21 at 04:13
1

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).

1

If you want your solution to work with a pure POSIX shell, it's a little tricky, because POSIX doesn't have a mktemp. See Why is there no mktemp command in POSIX? for more information.

However, here is a work-around solution for POSIX shell that doesn't require m4 and implements at least basic mktemp as well as the -d/--directory option to create temporary folders as well:

#!/bin/sh
mktemp() (
    create=f
    for option; do
        case $option in
            -d|--directory) create=d;;
            *) printf %s\\n "$0: unknown option '$option'" >&2; exit 1;;
        esac
    done
    set -o noclobber
    exec 2>/dev/null
    while :; do
        tmpname="$TMPDIR/tmp.$(</dev/urandom tr -dc "[:alnum:]" | dd bs=1 count=10 2>/dev/null)"
        case $create in
            f) umask 0177; printf "" >"$tmpname";;
            d) umask 0077; mkdir "$tmpname";;
        esac && break
    done
    printf %s\\n "$tmpname"
)

Of course, it would be possible to further extend this function with option parsing via getopts, for example, to add support for templates etc.

finefoot
  • 3,060
  • POSIX does not define $TMPDIR. – Dr. Koutheir Attouchi May 08 '23 at 22:17
  • @Dr.KoutheirAttouchi https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03 "TMPDIR This variable shall represent a pathname of a directory made available for programs that need a place to create temporary files." – finefoot May 09 '23 at 00:19