AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / unix / Perguntas / 532206
Accepted
Evan Benn
Evan Benn
Asked: 2019-07-26 18:31:46 +0800 CST2019-07-26 18:31:46 +0800 CST 2019-07-26 18:31:46 +0800 CST

Como copiar um arquivo transacionalmente?

  • 772

Eu quero copiar um arquivo de A para B, que pode estar em sistemas de arquivos diferentes.

Existem alguns requisitos adicionais:

  1. A cópia é tudo ou nada, nenhum arquivo B parcial ou corrompido foi deixado no local na falha;
  2. Não sobrescreva um arquivo B existente;
  3. Não concorra com uma execução concorrente do mesmo comando, no máximo um pode ter sucesso.

Acho que isso se aproxima:

cp A B.part && \
ln B B.part && \
rm B.part

Mas 3. é violado pelo cp não falhando se B.part existir (mesmo com o sinalizador -n). Subsequentemente 1. pode falhar se o outro processo 'ganhar' o cp e o arquivo vinculado ao local estiver incompleto. B.part também pode ser um arquivo não relacionado, mas fico feliz em falhar sem tentar outros nomes ocultos nesse caso.

Acho que o bash noclobber ajuda, isso funciona totalmente? Existe uma maneira de obter sem o requisito de versão do bash?

#!/usr/bin/env bash
set -o noclobber
cat A > B.part && \
ln B.part B && \
rm B.part

Acompanhamento, eu sei que alguns sistemas de arquivos falharão de qualquer maneira (NFS). Existe uma maneira de detectar esses sistemas de arquivos?

Algumas outras perguntas relacionadas, mas não exatamente as mesmas:

Aproximando o movimento atômico entre sistemas de arquivos?

O mv é atômico no meu fs?

existe uma maneira de mover atomicamente o arquivo e o diretório da partição tempfs para ext4 no eMMC

https://rcrowley.org/2010/01/06/things-unix-can-do-atomically.html

linux bash
  • 7 7 respostas
  • 2485 Views

7 respostas

  • Voted
  1. Best Answer
    Hermann
    2019-07-27T01:24:05+08:002019-07-27T01:24:05+08:00

    rsyncfaz esse trabalho. Um arquivo temporário é O_EXCLcriado por padrão (desativado apenas se você usar --inplace) e, em seguida, renamedsobre o arquivo de destino. Use --ignore-existingpara não substituir B se existir.

    Na prática, nunca tive problemas com isso em montagens ext4, zfs ou mesmo NFS.

    • 12
  2. ilkkachu
    2019-07-27T01:18:40+08:002019-07-27T01:18:40+08:00

    Não se preocupe, noclobberé um recurso padrão .

    • 4
  3. filbranden
    2019-07-27T02:10:48+08:002019-07-27T02:10:48+08:00

    Você perguntou sobre NFS. É provável que esse tipo de código seja interrompido no NFS, pois a verificação noclobberenvolve duas operações NFS separadas (verificar se o arquivo existe, criar um novo arquivo) e dois processos de dois clientes NFS separados podem entrar em uma condição de corrida em que ambos são bem-sucedidos ( ambos verificam que B.partainda não existe, então ambos procedem para criá-lo com sucesso, como resultado, eles estão substituindo um ao outro.)

    Não há realmente para fazer uma verificação genérica para saber se o sistema de arquivos no qual você está escrevendo suportará algo como noclobberatomicamente ou não. Você pode verificar o tipo de sistema de arquivos, se é NFS, mas isso seria uma heurística e não necessariamente uma garantia. Sistemas de arquivos como SMB/CIFS (Samba) provavelmente sofrerão dos mesmos problemas. Os sistemas de arquivos expostos através do FUSE podem ou não se comportar corretamente, mas isso depende principalmente da implementação.


    Uma abordagem possivelmente melhor é evitar a colisão na B.partetapa, usando um nome de arquivo exclusivo (através da cooperação com outros agentes) para que você não precise depender de noclobber. Por exemplo, você pode incluir, como parte do nome do arquivo, seu nome de host, PID e um carimbo de data/hora (+possivelmente um número aleatório). garantem a exclusividade.

    Então, qualquer um:

    test -f B && continue  # skip already existing
    unique=$(hostname).$$.$(date +%s).$RANDOM
    cp A B.part."$unique"
    # Maybe check for existance of B again, remove
    # the temporary file and bail out in that case.
    mv B.part."$unique" B
    # mv (rename) should always succeed, overwrite a
    # previously copied B if one exists.
    

    Ou:

    test -f B && continue  # skip already existing
    unique=$(hostname).$$.$(date +%s).$RANDOM
    cp A B.part."$unique"
    if ln B.part."$unique" B ; then
        echo "Success creating B"
    else
        echo "Failed creating B, already existed"
    fi
    # Both cases require cleanup.
    rm B.part."$unique"
    

    Portanto, se você tiver uma condição de corrida entre dois agentes, ambos prosseguirão com a operação, mas a última operação será atômica, portanto, B existe com uma cópia completa de A ou B não existe.

    Você pode reduzir o tamanho da corrida verificando novamente após a cópia e antes da operação mvou ln, mas ainda há uma pequena condição de corrida. Mas, independentemente da condição de corrida, o conteúdo de B deve ser consistente, assumindo que ambos os processos estão tentando criá-lo de A (ou uma cópia de um arquivo válido como origem).

    Observe que na primeira situação com mv, quando existe uma corrida, o último processo é quem vence, pois rename(2) substituirá atomicamente um arquivo existente:

    Se newpath já existir, ele será substituído atomicamente, para que não haja nenhum ponto em que outro processo tentando acessar newpath o encontre ausente. [...]

    Se newpath existir, mas a operação falhar por algum motivo, rename()garante deixar uma instância de newpath no lugar.

    Portanto, é bem possível que os processos que consomem B no momento possam ver diferentes versões dele (diferentes inodes) durante esse processo. Se os escritores estão apenas tentando copiar o mesmo conteúdo, e os leitores estão simplesmente consumindo o conteúdo do arquivo, tudo bem, se eles obtiverem inodes diferentes para arquivos com o mesmo conteúdo, eles ficarão felizes da mesma forma.

    A segunda abordagem usando um link físico parece melhor, mas me lembro de fazer experimentos com links físicos em um loop apertado no NFS de muitos clientes simultâneos e contando o sucesso e ainda parecia haver algumas condições de corrida lá, onde parecia que dois clientes emitiram um link físico operação ao mesmo tempo, com o mesmo destino, ambos pareciam ter sucesso. (É possível que esse comportamento esteja relacionado à implementação específica do servidor NFS, YMMV.) Em qualquer caso, esse é provavelmente o mesmo tipo de condição de corrida, onde você pode acabar obtendo dois inodes separados para o mesmo arquivo nos casos em que há simultaneidade entre escritores para acionar essas condições de corrida. Se seus escritores são consistentes (ambos copiando de A para B) e seus leitores estão consumindo apenas o conteúdo, isso pode ser suficiente.

    Finalmente, você mencionou o bloqueio. Infelizmente, o bloqueio está em falta, pelo menos no NFSv3 (não tenho certeza sobre o NFSv4, mas aposto que também não é bom). cópias de arquivos reais, mas isso é perturbador, complexo e propenso a problemas como deadlocks, então eu diria que é melhor evitar.


    Para obter mais informações sobre o assunto de atomicidade em NFS, você pode querer ler sobre o formato de caixa de correio Maildir , que foi criado para evitar bloqueios e trabalhar de forma confiável mesmo em NFS. Ele faz isso mantendo nomes de arquivos exclusivos em todos os lugares (para que você nem obtenha um B final no final).

    Talvez um pouco mais interessante para o seu caso particular, o formato Maildir++ estende Maildir para adicionar suporte para cota de caixa de correio e faz isso atualizando atomicamente um arquivo com um nome fixo dentro da caixa de correio (para que possa estar mais próximo do seu B.) Acho que o Maildir++ tenta para anexar, o que não é realmente seguro no NFS, mas há uma abordagem de recálculo que usa um procedimento semelhante a este e é válido como uma substituição atômica.

    Espero que todas essas dicas sejam úteis!

    • 4
  4. 炸鱼薯条德里克
    2019-07-26T21:11:49+08:002019-07-26T21:11:49+08:00

    Você pode escrever um programa para isso.

    Use open(O_CREAT|O_RDWD)para abrir o arquivo de destino, leia todos os bytes e metadados para verificar se o arquivo de destino é completo, caso contrário, existem duas possibilidades,

    1. Gravação incompleta

    2. Outro processo está executando o mesmo programa.

    Tente obter um bloqueio de descrição de arquivo aberto no arquivo de destino.

    Falha significa que há um processo simultâneo, o processo atual deve existir.

    Sucesso significa que a última gravação falhou, você deve começar de novo ou tentar corrigi-lo gravando no arquivo.

    Observe também que é melhor fsync()gravar no arquivo de destino antes de fechar o arquivo e liberar o bloqueio, ou outro processo pode ler dados que ainda não estão no disco.

    https://www.gnu.org/software/libc/manual/html_node/Open-File-Description-Locks.html

    Isso é importante para ajudá-lo a distinguir entre um programa em execução concorrente e uma operação travada por último.

    • 2
  5. Kaan
    2019-07-27T11:54:21+08:002019-07-27T11:54:21+08:00

    Você obterá o resultado correto fazendo um cpjunto com mv. Isso substituirá "B" por uma nova cópia de "A" ou deixará "B" como estava antes.

    cp A B.tmp && mv B.tmp B
    

    atualização para acomodar existente B:

    cp A B.tmp && if [ ! -e B ]; then mv B.tmp B; else rm B.tmp; fi
    

    Isso não é 100% atômico, mas chega perto. Existe uma condição de corrida onde duas dessas coisas estão rodando, ambas entram no ifteste ao mesmo tempo, ambas veem que Bnão existe, então ambas executam o mv.

    • 0
  6. Dev
    2021-04-09T10:01:01+08:002021-04-09T10:01:01+08:00

    Você pode fazer isso criando um arquivo temporário adequado no diretório de destino, copiando esse arquivo temporário e vinculando o arquivo temporário ao destino, como estava fazendo na pergunta.

    Isso depende apenas de linkat(2)ser atômico para o sistema de arquivos de destino

    #!/bin/sh
    
    ddir=$(dirname "$2")
    tmpfile=$(mktemp --tmpdir="$ddir")
    cp "$1" "$tmpfile" && ln "$tmpfile" "$2"
    ret=$?
    rm "$tmpfile"
    exit $ret
    
    • 0
  7. Mory-Marcel Sangare
    2021-04-09T10:43:34+08:002021-04-09T10:43:34+08:00

    Rsync é a ferramenta apropriada para usar, eu acho.

    Você deve usar rsync -Pahn --checksum /path/from/source /destination/path

    No entanto, tenha cuidado, os arquivos que você possui são muito grandes ...

    • -1

relate perguntas

  • Problema estranho ao passar variáveis ​​do arquivo de texto

  • Enquanto a linha lê mantendo os espaços de escape?

  • ordem de substituição de processos `te` e `bash`

  • Passe o sistema de arquivos raiz por rótulo para o kernel do Linux

  • Execute um script muito lento até que seja bem-sucedido

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Possível firmware ausente /lib/firmware/i915/* para o módulo i915

    • 3 respostas
  • Marko Smith

    Falha ao buscar o repositório de backports jessie

    • 4 respostas
  • Marko Smith

    Como exportar uma chave privada GPG e uma chave pública para um arquivo

    • 4 respostas
  • Marko Smith

    Como podemos executar um comando armazenado em uma variável?

    • 5 respostas
  • Marko Smith

    Como configurar o systemd-resolved e o systemd-networkd para usar o servidor DNS local para resolver domínios locais e o servidor DNS remoto para domínios remotos?

    • 3 respostas
  • Marko Smith

    apt-get update error no Kali Linux após a atualização do dist [duplicado]

    • 2 respostas
  • Marko Smith

    Como ver as últimas linhas x do log de serviço systemctl

    • 5 respostas
  • Marko Smith

    Nano - pule para o final do arquivo

    • 8 respostas
  • Marko Smith

    erro grub: você precisa carregar o kernel primeiro

    • 4 respostas
  • Marko Smith

    Como baixar o pacote não instalá-lo com o comando apt-get?

    • 7 respostas
  • Martin Hope
    user12345 Falha ao buscar o repositório de backports jessie 2019-03-27 04:39:28 +0800 CST
  • Martin Hope
    Carl Por que a maioria dos exemplos do systemd contém WantedBy=multi-user.target? 2019-03-15 11:49:25 +0800 CST
  • Martin Hope
    rocky Como exportar uma chave privada GPG e uma chave pública para um arquivo 2018-11-16 05:36:15 +0800 CST
  • Martin Hope
    Evan Carroll status systemctl mostra: "Estado: degradado" 2018-06-03 18:48:17 +0800 CST
  • Martin Hope
    Tim Como podemos executar um comando armazenado em uma variável? 2018-05-21 04:46:29 +0800 CST
  • Martin Hope
    Ankur S Por que /dev/null é um arquivo? Por que sua função não é implementada como um programa simples? 2018-04-17 07:28:04 +0800 CST
  • Martin Hope
    user3191334 Como ver as últimas linhas x do log de serviço systemctl 2018-02-07 00:14:16 +0800 CST
  • Martin Hope
    Marko Pacak Nano - pule para o final do arquivo 2018-02-01 01:53:03 +0800 CST
  • Martin Hope
    Kidburla Por que verdadeiro e falso são tão grandes? 2018-01-26 12:14:47 +0800 CST
  • Martin Hope
    Christos Baziotis Substitua a string em um arquivo de texto enorme (70 GB), uma linha 2017-12-30 06:58:33 +0800 CST

Hot tag

linux bash debian shell-script text-processing ubuntu centos shell awk ssh

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve