我们可以像临时文件一样使用临时文件夹吗
TMP=$(mktemp ... )
exec 3<>$TMP
rm $TMP
cat <&3
shell退出后哪个会自动销毁?
答案1
对于临时文件,问题中的示例将创建它,然后取消与目录的链接(使其“消失”),并且当脚本关闭文件描述符时(可能是在终止时),系统将回收该文件占用的空间。这是在 C 等语言中处理临时文件的常用方法。
据我所知,不可能以相同的方式打开目录,至少不可能以任何使目录可用的方式。
在脚本终止时删除临时文件和目录的常见方法是安装清理EXIT
陷阱。下面给出的代码示例避免了完全处理文件描述符。
tmpdir=$(mktemp -d)
tmpfile=$(mktemp)
trap 'rm -f "$tmpfile"; rm -rf "$tmpdir"' EXIT
# The rest of the script goes here.
或者您可以调用清理函数:
cleanup () {
rm -f "$tmpfile"
rm -rf "$tmpdir"
}
tmpdir=$(mktemp -d)
tmpfile=$(mktemp)
trap cleanup EXIT
# The rest of the script goes here.
EXIT
接收到信号后不会执行陷阱(KILL
无法捕获),这意味着此时不会执行任何清理。然而,当由于INT
或TERM
信号而终止时(如果使用bash
或运行,在其他 shell 中您可能需要在命令行ksh
后添加这些信号),或者由于到达脚本末尾或执行称呼。EXIT
trap
exit
答案2
您可以 chdir 进入它,然后将其删除,前提是您之后不要尝试使用其中的路径:
#! /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
我没有检查过,但在 C 中使用 openat(2) 和文件系统中不再存在的目录时,很可能是同样的问题。
如果您是 Linux 上的 root 用户,则可以使用单独的命名空间并mount -t tmpfs tmpfs /dir
在其中进行操作。
如果您的脚本被迫进入不干净的退出(例如使用 SIGKILL),那么规范的答案(在 EXIT 上设置陷阱)将不起作用;这可能会留下敏感数据。
更新:
这是一个实现命名空间方法的小实用程序。它应该编译为
cc -Wall -Os -s chtmp.c -o chtmp
并给定CAP_SYS_ADMIN
文件功能(作为 root)
setcap CAP_SYS_ADMIN+ep chtmp
当(作为普通)用户运行时
./chtmp command args ...
它将取消共享其文件系统命名空间,将 tmpfs 文件系统挂载到其中,chdir 并使用给定的参数/proc/sysvipc
运行。将要command
command
不是继承CAP_SYS_ADMIN
能力。
该文件系统将无法从不是从 启动的另一个进程访问command
,并且当它的子进程死亡时,它会神奇地消失(以及在其中创建的所有文件)command
,无论情况如何发生。请注意,这只是取消共享挂载命名空间——command
同一用户运行的其他进程之间没有硬障碍;他们仍然可以通过ptrace(2)
或/proc/PID/cwd
其他方式潜入其名称空间。
劫持“无用”/proc/sysvipc
当然是愚蠢的,但另一种选择是/tmp
使用空目录发送垃圾邮件,这些空目录必须被删除,或者通过分叉和等待使这个小程序变得非常复杂。或者,dir
可以更改为例如。/mnt/chtmp
并在安装时由 root 创建它;不要使其可由用户配置,也不要将其设置为用户拥有的路径,因为这可能会让您陷入符号链接陷阱和其他不值得花时间的麻烦的东西。
chmp.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);
}
答案3
编写一个 shell 函数,当脚本完成时将执行该函数。在下面的示例中,我将其称为“清理”,并设置要在退出级别执行的陷阱,例如: 0 1 2 3 6
trap cleanup 0 1 2 3 6
cleanup()
{
[ -d $TMP ] && rm -rf $TMP
}
看这发布以获取更多信息。
答案4
您需要特定的外壳吗?
如果可以选择 zsh,请阅读zshexpn(1)
:
如果使用 =(...) 而不是 <(...),则作为参数传递的文件将是包含列表进程输出的临时文件的名称。对于希望
lseek
(参见lseek(2)
)输入文件的程序,可以使用它来代替 < 形式。[...]
每当 shell 否认具有需要临时文件的替换的作业时,就会出现另一个问题,包括
&!
或&|
出现在包含替换的命令末尾的情况。在这种情况下,临时文件将不会被清除,因为 shell 不再拥有该作业的任何内存。解决方法是使用子 shell,例如,(mycmd =(myoutput)) &!
因为分叉的子 shell 将等待命令完成然后删除临时文件。
确保进程替换持续适当时间的一般解决方法是将其作为参数传递给匿名 shell 函数(在函数范围内立即运行的一段 shell 代码)。例如,以下代码:
() { print File $1: cat $1 } =(print This be the verse)
输出类似以下内容
File /tmp/zsh6nU0kS: This be the verse
例如,我在步枪(Ranger 文件管理器的一部分)中使用它来解密文件,然后在临时文件上运行步枪,当子进程终止时该临时文件将被删除。 (别忘了设置$TERMCMD
)
# ~/.config/ranger/rifle.conf
...
!ext exe, mime octet-stream$, has gpg, flag t = () { rifle -f F "$1" } =(gpg -dq "$1")