Quão seguro é a saída para dir simultaneamente com rm dir / *

5

Às vezes, preciso remover todo o conteúdo de um diretório e criar novos arquivos nele. Posso fazer algo assim e esperar que todos os novos arquivos permaneçam intactos:

% rm -rf regression/* & ( sleep 10 ; run_regression )

em que run_regression marca seus arquivos de saída para que eles tenham nomes exclusivos e os coloque em regression ?

Meu pensamento é que o shell resolveria regression/* em uma lista explícita de nomes de arquivos pré-existentes e, em seguida, rm estaria removendo os arquivos nessa lista explícita, mas não os novos arquivos que run_regression estaria criando simultaneamente com rm . Como run_regression marca seus arquivos, não deve haver conflitos de nome.

No entanto, não tenho certeza de como saber quando o shell é feito, listando os arquivos e rm começa a funcionar. Os 10 seg acima são adequados? Posso fazer algo assim em bash :

% rm -rf regression/* & ( wait_unil_names_are_resolved ; run_regression )

Por comentário, estou esclarecendo que estou realmente perguntando se o shell garante que os curingas sejam expandidos em nomes de arquivos antes de invocar a ferramenta, mesmo que seja uma ferramenta intimamente conhecida do shell. Eu posso imaginar que o desenvolvedor do shell e da ferramenta pode ser tentado a expandir a expansão do curinga com a ferramenta; Espero que existam normas que impeçam isso.

    
por Michael 31.05.2016 / 22:48

5 respostas

4

Isso não é seguro.

Você não especificou qual é o problema que está tentando resolver. Se o seu problema é que você quer que seu diretório esteja sempre lá, mas seja limpo de vez em quando, sugiro remover explicitamente arquivos mais antigos que um arquivo de verificação (o sleep 1 é que eu sou paranóico):

touch regression.delete \
&& find regression \! -newer regression.delete -delete & \
&& sleep 1 \
&& run_regression

Isso terá problemas se você tiver subdiretórios, você pode escrever

touch regression.delete \
&& find regression -mindepth 1 -maxdepth 1 \! -newer regression.delete -exec rm -rf '{}' \; & \
&& sleep 1 \
&& run_regression

Se o seu problema é que você quer iniciar seu programa o mais rápido possível, se a ausência momentânea do diretório for possível e não for um ponto de montagem, eu geralmente executo algo como

mkdir regression.new \
&& chmod --reference regression regression.new \
&& mv regression regression.delete \
&& mv regression.new regression \
&& rm -rf regression.delete & \
run_regression

Isso deve permitir que você inicie a run_regression quase instantaneamente.

Respondendo a sua edição (e editando-me seguindo a pesquisa em outra resposta), curingas devem ser expandidos antes que o comando rm seja iniciado, mas o ponto crucial de seu problema é saber se a expansão é feita após os forks do shell. A especificação POSIX da execução assíncrona não especifica explicitamente de uma forma ou de outra, tanto quanto eu posso veja, e a seção 2.1 certamente implica que a expansão é uma operação distinta e anterior ao fork real / exec do comando, mas o teste (por @adonis, replicado por mim usando bash 4.3.42 (1)) mostra que o bash é o mais eficiente maneira: se a expansão curinga leva tempo, então as modificações executadas pelo comando a seguir podem influenciar essa expansão. Sua idéia original, portanto, corre o risco de excluir arquivos que você não deseja excluir.

Eu olhei para o bash source, e o execute_cmd.c explicitamente afirma que a bifurcação é feita antes da expansão de palavras:

3922 | /* If we're in a pipeline or run in the background, set DOFORK so we
3923 |  make the child early, before word expansion.  This keeps assignment
3924 |  statements from affecting the parent shell's environment when they
3925 |  should not. */
    
por 31.05.2016 / 23:32
5

Embora seu comando provavelmente funcione, aqui está um caso de teste:

$ ls
$ echo * $(sleep 1)&touch file1
[1] 12798
$ file1

[1]+  Done                    echo * $(sleep 1)

Note que o arquivo1 não foi digitado, foi a saída do comando echo.

Editar:

Outro teste:

$ ls
$ touch file1
$ for i in {1..5000}; do rm * & touch file$i; wait;done|grep file
rm: cannot remove '*': No such file or directory
***previous line repeated 14 times***
    
por 01.06.2016 / 00:03
2

rm -rf regression/* é executado em paralelo com ( sleep 10 ; run_regression ) . Isso significa que você não tem garantia quanto à ordem das coisas. rm -rf regression/* primeiro coleta a lista de arquivos no diretório regression e invoca rm para excluí-los. Isso não acontece por mágica, é o shell fazendo o trabalho como parte da avaliação do comando rm -rf regression/* , e isso acontece depois do fork causado pelo operador & . Se a etapa de coleta levar menos de 10 segundos, os arquivos criados por run_regression serão seguros. Se demorar mais de 10 segundos para que a etapa de coleta atinja um arquivo criado por run_regression , esse arquivo será excluído.

A exclusão do arquivo não afetará run_regression , a menos que ele feche o arquivo e reabra-o. A exclusão de um arquivo não afeta os processos que têm o arquivo aberto: o arquivo continua existindo, sem uma entrada de diretório (ou seja, uma contagem de hard link de 0), até que todos os processos que a abrem fechem a mesma. Mas você não poderá acessar a saída do programa, pois ele será excluído.

Então não faça isso. Não confie no tempo: com um atraso tão alto quanto 10 segundos, ele funcionará durante o teste (especialmente porque provavelmente haverá alguns arquivos, um cache quente, nenhum pico de I / O, nenhuma suspensão do sistema, etc. durante seu teste), mas mais cedo ou mais tarde ele falhará na produção.

Se você realmente quiser manter o diretório e excluir os arquivos, faça a coleta do nome do arquivo primeiro.

files_to_delete=(regression/*)
rm -rf "${files_to_delete[@]}" & run_regression

(Isto assume um shell com arrays. Em sh simples, use set regression/*; rm -rf "$@" & run_regression .) Claro que isso assume que os arquivos run_regression apenas cria arquivos que não existem, se sobrescrever arquivos existentes, então esses arquivos excluído.

Você provavelmente não precisa de toda essa complexidade: basta executar

rm -rf regression/*
run_regression

A menos que a lista de arquivos seja tão grande que não caiba no cache, ou a menos que o sistema de arquivos tenha operações de gravação anormalmente lentas, coletar a lista de nomes é maior do que excluí-los, por isso não fará um desempenho diferença.

Se o desempenho da operação de remoção for muito ruim (o que, de novo, seria incomum), crie um novo diretório.

mv regression regression.old
mkdir regression
rm -rf regression.old &
run_regression
    
por 01.06.2016 / 01:26
1
mv regression regression.old
rm -rf regression.old &
mkdir regression
run_regression

Renomeie o antigo diretório de regressão, exclua-o em segundo plano, crie um novo diretório de regressão e, em seguida, execute seu programa.

se run_regression criar o próprio diretório, se ele não existir, o terceiro passo não será necessário.

Uma versão mais segura, no caso de regression.old já existir, seria usar mktemp para criar e usar um diretório temporário na pasta atual:

td=$(mktemp -d -p .)
mv regression "$td/"
rm -rf "$td" &
unset td
mkdir regression
run_regression
    
por 01.06.2016 / 06:41
0

Só é seguro se você usar novos nomes de arquivos. O shell conhece nomes de arquivos, não seus inodes, etc., e faz o globbing (expansão de curingas) antes de executar um comando. De acordo com POSIX :

2.6.6 Pathname Expansion

After field splitting, if set -f is not in effect, each field in the resulting command line shall be expanded using the algorithm described in Pattern Matching Notation, qualified by the rules in Patterns Used for Filename Expansion.

Isto é, é um passo bem definido na análise que ocorre antes de executar o comando. A maioria dos casos complicados no POSIX lidam com redirecionamento e atribuições . Não há nenhum neste exemplo, então é isso que se aplica:

2.9.1 Simple Commands

  1. The words that are not variable assignments or redirections shall be expanded. If any fields remain following their expansion, the first field shall be considered the command name and remaining fields are the arguments for the command.

O exemplo mostrado na pergunta faz parecer que nenhum diretório foi removido. Se acontecer de você confiar na existência de um subdiretório que possa ter sido removido, a mesma limitação se aplica.

Presumivelmente, seu timestamp (dez segundos é diferente para segundos em um timestamp) seria parte dos nomes de arquivos resultantes.

    
por 31.05.2016 / 22:52