Mover arquivo, mas apenas se estiver fechado

10

Quero mover um arquivo grande criado por um processo externo assim que for fechado.

Este comando de teste está correto?

if lsof "/file/name"
then
        # file is open, don't touch it!
else
        if [ 1 -eq $? ]
        then
                # file is closed
                mv /file/name /other/file/name
        else
                # lsof failed for some other reason
        fi
fi

EDIT: o arquivo representa um conjunto de dados e eu tenho que esperar até que esteja completo para movê-lo para que outro programa possa atuar nele. É por isso que preciso saber se o processo externo é feito com o arquivo.

Peter Kovac
fonte
3
Nota lateral: quando um arquivo é aberto, os processos usam descritores de arquivo e inode dados para manipulá-lo. Alterar o caminho (por exemplo, mover o arquivo) não causará muitos problemas ao processo.
John WH Smith
2
Você tem algum controle sobre o processo externo? Seria possível para o processo externo criar um arquivo temporário e renomear o arquivo assim que terminar de gravar nele?
Jenny D
@ JennyD Eu fiz algumas investigações e isso acabou sendo verdade. Eu não preciso de lsofnada, só preciso verificar se a extensão do arquivo não é .tmp. Isso o torna trivial. No entanto, eu estou feliz que eu pedi a minha pergunta desde que eu aprendi um pouco sobre lsofe inotifye outras coisas.
Peter Kovac
@ PeterKovac Também aprendi mais sobre eles, lendo as respostas, por isso estou muito feliz que você tenha perguntado.
Jenny D
@ JohnWHSmith - Isso normalmente acontece se mover o arquivo dentro do mesmo sistema de arquivos, se ele mover o arquivo para um novo sistema de arquivos antes que o escritor termine de escrevê-lo, ele perderá alguns dados.
Johnny

Respostas:

11

Na lsofpágina do manual

Lsof retorna 1 (um) se algum erro foi detectado, incluindo a falha em localizar nomes de comando, nomes de arquivos, endereços ou arquivos da Internet, nomes de login, arquivos NFS, PIDs, PGIDs ou UIDs que foram solicitados a listar. Se a opção -V for especificada, lsof indicará os itens de pesquisa que não foram listados.

Portanto, isso sugere que sua lsof failed for some other reasoncláusula nunca será executada.

Você já tentou mover o arquivo enquanto o processo externo ainda o abre? Se o diretório de destino estiver no mesmo sistema de arquivos, não haverá problemas em fazer isso, a menos que você precise acessá-lo no caminho original a partir de um terceiro processo, pois o inode subjacente permanecerá o mesmo. Caso contrário, acho mvque falhará de qualquer maneira.

Se você realmente precisar esperar até que seu processo externo seja concluído com o arquivo, é melhor usar um comando que bloqueie, em vez de pesquisar repetidamente. No Linux, você pode usar inotifywaitpara isso. Por exemplo:

 inotifywait -e close_write /path/to/file

Se você deve usar lsof(talvez para portabilidade), tente algo como:

until err_str=$(lsof /path/to/file 2>&1 >/dev/null); do
  if [ -n "$err_str" ]; then
    # lsof printed an error string, file may or may not be open
    echo "lsof: $err_str" >&2

    # tricky to decide what to do here, you may want to retry a number of times,
    # but for this example just break
    break
  fi

  # lsof returned 1 but didn't print an error string, assume the file is open
  sleep 1
done

if [ -z "$err_str" ]; then
  # file has been closed, move it
  mv /path/to/file /destination/path
fi

Atualizar

Conforme observado por @JohnWHSmith abaixo, o design mais seguro sempre usaria um lsofloop como acima, pois é possível que mais de um processo tenha o arquivo aberto para gravação (um exemplo de caso pode ser um daemon de indexação mal escrito que abre arquivos com a leitura / write flag quando realmente deve ser somente leitura). inotifywaitainda pode ser usado em vez de dormir, basta substituir a linha de sono por inotifywait -e close /path/to/file.

Graeme
fonte
Obrigado, eu não estava ciente inotify. Infelizmente, ele não está instalado na minha caixa, mas tenho certeza que vou encontrar um pacote em algum lugar. Veja minha edição por um motivo pelo qual eu preciso que o arquivo seja fechado: é um conjunto de dados e deve ser concluído antes de processá-lo.
Peter Kovac
1
Outra observação: apesar de inotifywaitimpedir que o script faça "polling" duas vezes, o OP ainda precisa fazer check- lsofin em um loop: se o arquivo for aberto duas vezes, o fechamento de uma vez poderá acionar o inotifyevento, mesmo que o arquivo não esteja pronto para ser manipulado (por exemplo, no seu último trecho de código, sua sleepchamada pode ser substituída por inotifywait).
John WH Smith
@ John a close_writedeve estar ok, pois apenas um processo pode ter o arquivo aberto para gravação por vez. Ele pressupõe que outro não o abra logo após ser fechado, mas o mesmo problema existe com a lsofpesquisa.
Graeme
1
@Graeme Embora isso possa ser verdade por design no caso do OP, o kernel permite que um arquivo seja aberto duas vezes para gravação (nesse caso, CLOSE_WRITEé acionado duas vezes).
John WH Smith
@ John, atualizado.
Graeme
4

Como uma abordagem alternativa, esse é o caso perfeito para um canal - o segundo processo processará a saída do primeiro processo assim que estiver disponível, em vez de aguardar a conclusão do processo completo:

process1 input_file.dat | process2 > output_file.dat

Vantagens:

  • Muito mais rápido em geral:
    • Não precisa gravar e ler a partir do disco (isso pode ser evitado se você usar um ramdisk).
    • Deve usar os recursos da máquina mais completamente.
  • Nenhum arquivo intermediário a ser removido após o término.
  • Não é necessário bloqueio complexo, como no OP.

Se você não tem como criar um canal diretamente, mas possui o GNU coreutils, pode usar isso:

tail -F -n +0 input_file.dat | process2 > output_file.dat

Isso começará a ler o arquivo de entrada desde o início, não importa a que distância o primeiro processo seja gravado (mesmo que ainda não tenha sido iniciado ou já esteja concluído).

l0b0
fonte
Sim, essa seria a solução "óbvia". Infelizmente, o processo de geração de dados está fora do meu controle (executado por outro usuário).
Peter Kovac
@PeterKovac Isso é irrelevante: cat input_file.dat | process2 output_file.dat
MariusMatutiae
@MariusMatutiae mas cate process2poderia terminar antes de process1terminar. Eles não bloqueariam.
cpugeniusmv