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?
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. It only takes a minute to sign up.
Sign up to join this communitytmpfile=$(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>&-
exec 3> "$tmpfile"
do? Isn't that only useful if the tmpfile is a stand-alone script?
Nov 29, 2016 at 18:36
cat <&3
will give Bad file descriptor
. I'd appreciate it if you either fix it or remove it; misinformation doesn't much help.
Use mktemp
to create a temporary file
temp_file=$(mktemp)
or, to create a temporary directory:
temp_dir=$(mktemp -d)
At the end of the script you have to delete the temporary file or directory
rm ${temp_file}
rm -R ${temp_dir}
mktemp
creates file in the /tmp
directory or in the directory given with the --tmpdir
argument.
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.
kill -9 $somepid
. That particular kill signal is insta-death with nothing else happening.
Jul 21, 2017 at 22:08
bash -c 'echo $$; trap "echo foo" 0; sleep 5'
Aug 5, 2017 at 6:22
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.
Some shells have the feature built-in.
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.
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).
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).
=(...)
.
Jan 30, 2015 at 21:01
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"
tmpfile
still be removed before script exit, but not when script received other signals.
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.
Jan 30, 2015 at 22:01
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
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.
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).
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.