Pasta temporária que foi destruída automaticamente após a saída do processo

10

Podemos usar pastas temporárias como arquivos temporários

TMP=$(mktemp ... )
exec 3<>$TMP
rm $TMP

cat <&3

que será destruído automaticamente após a saída do shell?

Bob Johnson
fonte
Relacionado: armadilha de saída no traço vs ksh e bash
Stéphane Chazelas 8/08

Respostas:

12

No caso de um arquivo temporário, seu exemplo na pergunta o criaria, depois o desvincularia do diretório (fazendo com que "desapareça") e, quando o script fechar o filedescriptor (provavelmente após a finalização), o espaço ocupado pelo arquivo seria recuperável pelo sistema. Essa é uma maneira comum de lidar com arquivos temporários em idiomas como C.

Até onde eu sei, não é possível abrir um diretório da mesma maneira, pelo menos de nenhuma maneira que tornaria o diretório utilizável.

Uma maneira comum de excluir arquivos e diretórios temporários no final de um script é instalando uma EXITarmadilha de limpeza . Os exemplos de código fornecidos abaixo evitam ter que manipular completamente os filedescriptors.

tmpdir=$(mktemp -d)
tmpfile=$(mktemp)

trap 'rm -f "$tmpfile"; rm -rf "$tmpdir"' EXIT

# The rest of the script goes here.

Ou você pode chamar uma função de limpeza:

cleanup () {
    rm -f "$tmpfile"
    rm -rf "$tmpdir"
}

tmpdir=$(mktemp -d)
tmpfile=$(mktemp)

trap cleanup EXIT

# The rest of the script goes here.

A EXITinterceptação não será executada ao receber o KILLsinal (que não pode ser interceptado), o que significa que não haverá limpeza realizada. No entanto, ele será executado ao terminar devido a um sinal INTou TERM(se estiver sendo executado com bashou ksh, em outros shells, você poderá adicionar esses sinais depois EXITna traplinha de comando) ou quando sair normalmente devido à chegada ao final do script ou à execução de um exitligar.

Kusalananda
fonte
5
Não é apenas o shell que não pode usar diretórios temporários já desvinculados - nem os programas C. O problema é que diretórios desvinculados não podem ter arquivos neles. Você pode ter um diretório vazio não vinculado como seu diretório de trabalho, mas qualquer tentativa de criar um arquivo causará um erro.
Derobert
1
@derobert E esse diretório desvinculado nem tem as entradas .e ... (Testado em Linux, eu não sei se isso é consistente em todas as plataformas.)
kasperd
1
Observe que a armadilha EXIT também não será executada se o script chamar exec another-commandobviamente.
Stéphane Chazelas
1
Veja também: armadilha de saída no traço vs ksh e bash
Stéphane Chazelas 8/08/18
6

Escreva uma função shell que será executada quando o seu script se terminar. No exemplo abaixo, chamo de 'limpeza' e defino uma armadilha a ser executada nos níveis de saída, como: 0 1 2 3 6

trap cleanup 0 1 2 3 6

cleanup()
{
  [ -d $TMP ] && rm -rf $TMP
}

Veja este post para mais informações.

Dirk Krijgsman
fonte
Esses não são "níveis de saída", mas números de sinal, e a resposta à pergunta à qual você está vinculando explica exatamente isso. A armadilha será executada cleanupantes de uma saída limpa (0) e ao receber SIGHUP (1), SIGINT (2), SIGQUIT (3) e SIGABRT (6). ele irá não executar cleanupquando as saídas de script por causa de SIGTERM, SIGSEGV, SIGKILL, SIGPIPE, etc. Isto é claramente deficiente.
mosvy
6

Você pode chdir e removê-lo, desde que não tente usar os caminhos dentro dele posteriormente:

#! /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

Não verifiquei, mas provavelmente é o mesmo problema ao usar o openat (2) em C com um diretório que não existe mais no sistema de arquivos.

Se você é root e no Linux, pode jogar com um espaço para nome separado e mount -t tmpfs tmpfs /dirdentro dele.

As respostas canônicas (coloque uma armadilha em EXIT) não funcionam se seu script for forçado a uma saída impura (por exemplo, com SIGKILL); que podem deixar dados confidenciais por aí.

Atualizar:

Aqui está um pequeno utilitário que implementa a abordagem de namespace. Deve ser compilado com

cc -Wall -Os -s chtmp.c -o chtmp

e determinados CAP_SYS_ADMINrecursos de arquivo (como root) com

setcap CAP_SYS_ADMIN+ep chtmp

Quando executar um usuário (normalmente) como

./chtmp command args ...

ele irá compartilhar seu espaço de nome do sistema de arquivos, montar um sistema de arquivos tmpfs /proc/sysvipc, chdir nele e executar commandcom os argumentos fornecidos. commandvai não herdar as CAP_SYS_ADMINcapacidades.

Esse sistema de arquivos não estará acessível a partir de outro processo não iniciado command, e desaparecerá magicamente (com todos os arquivos criados dentro dele) quando commande seus filhos morrerem, não importa como isso aconteça. Observe que isso está apenas compartilhando o espaço para nome da montagem - não há barreiras rígidas entre commande outros processos executados pelo mesmo usuário; eles ainda poderiam deslocar dentro de seu namespace quer através ptrace(2), /proc/PID/cwdou por outros meios.

O seqüestro do "inútil" /proc/sysvipcé, é claro, bobo, mas a alternativa seria enviar spam /tmpcom diretórios vazios que precisariam ser removidos ou complicar bastante esse pequeno programa com garfos e esperas. Como alternativa, dirpode ser alterado para, por exemplo. /mnt/chtmpe crie-o pela raiz na instalação; não faça com que seja configurável pelo usuário e não o configure como um caminho de propriedade do usuário, pois isso pode expô-lo a desvios de links e outras coisas peludas que não valem a pena gastar tempo.

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);
}
Qubert
fonte
1
Mesmo que você não seja root, você pode fazer isso com espaços para nome, criando um novo espaço para nome de usuário e montando o tmpfs dentro dele. Contrabandear o acesso ao novo diretório para o mundo exterior é um pouco complicado, mas deve ser possível.
R .. GitHub Pare de ajudar o gelo
Isso ainda requer CAP_SYS_ADMIN. Eu tenho a idéia de um pequeno utilitário habilitado para setcap que fará isso, atualizarei a resposta com ele.
qubert
1
A menos que o kernel tenha sido bloqueado para desabilitá-lo, a criação de espaços para nome de usuário não é uma operação privilegiada. O design subjacente é tal que é suposto ser seguro permitir que usuários comuns passem sem nenhum recurso especial. No entanto, existe uma superfície / risco de ataque suficiente para que muitas distros o desativem, eu acho.
R .. GitHub Pare de ajudar o gelo
Eu tentei no terminal. Em algum diretório temporário, rm $PWDtrabalho, shell ainda está nesse diretório. Mas nenhum novo arquivo pode ser colocado nesta "pasta". Somente você pode fazer é ler / gravar com o arquivo & 3, & 4. Portanto, este ainda é "arquivo temporário", não "pasta temporária".
Bob Johnson
@BobJohnson Isso não é diferente do que eu já estava dizendo na minha resposta ;-)
qubert
0

Você precisa de um shell específico?

Se zsh for uma opção, leia zshexpn(1):

Se = (...) for usado em vez de <(...), o arquivo passado como argumento será o nome de um arquivo temporário que contém a saída do processo de lista. Isso pode ser usado em vez do formulário <para um programa que espera lseek(consulte lseek(2)) no arquivo de entrada.

[...]

Outro problema surge sempre que um trabalho com uma substituição que requer um arquivo temporário é rejeitado pelo shell, incluindo o caso em que &!ou &|aparece no final de um comando que contém uma substituição. Nesse caso, o arquivo temporário não será limpo, pois o shell não terá mais memória do trabalho. Uma solução alternativa é usar um subshell, por exemplo,

(mycmd =(myoutput)) &!

pois o subshell bifurcado aguardará o término do comando e removerá o arquivo temporário.

Uma solução geral para garantir que uma substituição de processo perdure por um período de tempo apropriado é transmiti-la como um parâmetro para uma função de shell anônima (uma parte do código de shell que é executada imediatamente com o escopo da função). Por exemplo, este código:

() {
   print File $1:
   cat $1
} =(print This be the verse)

produz algo semelhante ao seguinte

File /tmp/zsh6nU0kS:
This be the verse

Por exemplo, eu uso isso no rifle (parte do gerenciador de arquivos do ranger) para descriptografar um arquivo e depois executar o rifle no arquivo temporário, que é excluído quando o subprocesso termina. (não se esqueça de definir $TERMCMD)

# ~/.config/ranger/rifle.conf
...
!ext exe, mime octet-stream$, has gpg, flag t = () { rifle -f F "$1" } =(gpg -dq "$1")
Bart
fonte