Can we use temporary folders like temporary files
TMP=$(mktemp ... )
exec 3<>$TMP
rm $TMP
cat <&3
which will be destroyed automatically after this shell exit?
Can we use temporary folders like temporary files
TMP=$(mktemp ... )
exec 3<>$TMP
rm $TMP
cat <&3
which will be destroyed automatically after this shell exit?
In the case of a temporary file, your example in the question would create it, then unlink it from the directory (making it "disappear"), and when the script closes the filedescriptor (probably upon termination), the space taken by the file would be reclaimable by the system. This is a common way to deal with temporary files in languages like C.
It is, as far as I know, not possible to open a directory in the same way, at least not in any way that would make the directory usable.
A common way to delete temporary files and directories at the termination of a script is by installing a cleanup EXIT
trap. The code examples given below avoids having to juggle filedescriptors completely.
tmpdir=$(mktemp -d)
tmpfile=$(mktemp)
trap 'rm -f "$tmpfile"; rm -rf "$tmpdir"' EXIT
# The rest of the script goes here.
Or you may call a cleanup function:
cleanup () {
rm -f "$tmpfile"
rm -rf "$tmpdir"
}
tmpdir=$(mktemp -d)
tmpfile=$(mktemp)
trap cleanup EXIT
# The rest of the script goes here.
The EXIT
trap won't be executed upon receiving the KILL
signal (which can't be trapped), which means that there will be no cleanup performed then. It will however execute when terminating due to an INT
or TERM
signal (if running with bash
or ksh
, in other shells you may want to add these signals after EXIT
in the trap
command line), or when exiting normally due to arriving at the end of the script or executing an exit
call.
.
and ..
entries. (Tested on Linux, I don't know if that's consistent across platforms.)
– kasperd
Nov 07 '18 at 19:03
exec another-command
obviously.
– Stéphane Chazelas
Nov 08 '18 at 08:01
You can chdir into it and then remove it, provided that you don't try to use paths inside it afterwards:
#! /bin/sh
dir=`mktemp -d`
cd "$dir"
exec 4>file 3<file
rm -fr "$dir"
echo yes >&4 # OK
cat <&3 # OK
cat file # FAIL
echo yes > file # FAIL
I haven't checked, but it's most probably the same problem when using openat(2) in C with a directory that no longer exists in the file system.
If you're root and on Linux, you can play with a separate namespace, and mount -t tmpfs tmpfs /dir
inside it.
The canonical answers (set a trap on EXIT) don't work if your script is forced into an unclean exit (eg. with SIGKILL); that may leave sensitive data hanging around.
Update:
Here is a small utility which implements the namespace approach. It should be compiled with
cc -Wall -Os -s chtmp.c -o chtmp
and given CAP_SYS_ADMIN
file capabilities (as root) with
setcap CAP_SYS_ADMIN+ep chtmp
When run (as a normal) user as
./chtmp command args ...
it will unshare its filesystem namespace, mount a tmpfs filesystem on /proc/sysvipc
, chdir into it and run command
with the given arguments. command
will not inherit the CAP_SYS_ADMIN
capabilities.
That filesystem will not be accessible from another process not started from command
, and it will magically disappear (with all the files that were created inside it) when command
and its children die, no matter how that happens. Notice that this is just unsharing the mount namespace -- there are no hard barriers between command
and other processes run by the same user; they could still sneak inside its namespace either via ptrace(2)
, /proc/PID/cwd
or by other means.
The hijacking of the "useless" /proc/sysvipc
is, of course silly, but the alternative would've been to spam /tmp
with empty directories that would have to be removed or greatly complicate this small program with forks and waits. Alternatively, dir
can be changed to eg. /mnt/chtmp
and have it created by root at installation; do not make it user-configurable and do not set it to a user-owned path as that may expose you to symlink traps and other hairy stuff not worth spending time on.
chtmp.c
#define _GNU_SOURCE
#include <err.h>
#include <sched.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/mount.h>
int main(int argc, char **argv){
char *dir = "/proc/sysvipc"; /* LOL */
if(argc < 2 || !argv[1]) errx(1, "usage: %s prog args ...", *argv);
argv++;
if(unshare(CLONE_NEWNS)) err(1, "unshare(CLONE_NEWNS)");
/* "modern" systemd remounts all mount points MS_SHARED
see the NOTES in mount_namespaces(7); YUCK */
if(mount("none", "/", 0, MS_REC|MS_PRIVATE, 0))
err(1, "mount(/, MS_REC|MS_PRIVATE)");
if(mount("tmpfs", dir, "tmpfs", 0, 0)) err(1, "mount(tmpfs, %s)", dir);
if(chdir(dir)) err(1, "chdir %s", dir);
execvp(*argv, argv);
err(1, "execvp %s", *argv);
}
rm $PWD
work, shell is still in that dir. But no new files can be put into this "folder". Only you can do is read/write with file &3,&4. So this is still "temporary file", not "temporary folder".
– Bob Johnson
Nov 08 '18 at 01:12
Write a shell-function that will be executed when your script if finished. In the example below I call it 'cleanup' and set a trap to be executed on exit levels, like: 0 1 2 3 6
trap cleanup 0 1 2 3 6
cleanup()
{
[ -d $TMP ] && rm -rf $TMP
}
See this post for more info.
cleanup
before a clean exit (0) and on receiving SIGHUP(1), SIGINT(2), SIGQUIT(3) and SIGABRT(6). it will not run cleanup
when the script exits because of SIGTERM, SIGSEGV, SIGKILL, SIGPIPE, etc. This is clearly deficient.
–
Nov 08 '18 at 13:43
Do you require a specific shell?
If zsh is an option, please read zshexpn(1)
:
If =(...) is used instead of <(...), then the file passed as an argument will be the name of a temporary file containing the output of the list process. This may be used instead of the < form for a program that expects to
lseek
(seelseek(2)
) on the input file.[...]
Another problem arises any time a job with a substitution that requires a temporary file is disowned by the shell, including the case where
&!
or&|
appears at the end of a command containing a substitution. In that case the temporary file will not be cleaned up as the shell no longer has any memory of the job. A workaround is to use a subshell, for example,(mycmd =(myoutput)) &!
as the forked subshell will wait for the command to finish then remove the temporary file.
A general workaround to ensure a process substitution endures for an appropriate length of time is to pass it as a parameter to an anonymous shell function (a piece of shell code that is run immediately with function scope). For example, this code:
() { print File $1: cat $1 } =(print This be the verse)
outputs something resembling the following
File /tmp/zsh6nU0kS: This be the verse
For example I use this in rifle (part of the ranger file manager) to decrypt a file and then run rifle on the temporary file, which gets deleted when the subproces terminates. (don't forget to set $TERMCMD
)
# ~/.config/ranger/rifle.conf
...
!ext exe, mime octet-stream$, has gpg, flag t = () { rifle -f F "$1" } =(gpg -dq "$1")