Manter (ou restaurar) permissões de arquivo ao substituir o arquivo

11

Eu tenho um comando que aceita um arquivo como argumento, modifica o arquivo e o grava no nome do arquivo especificado no segundo argumento. Vou ligar para esse programa modifyfile.

Eu queria que ele funcionasse "no local", então escrevi um script de shell (bash) que o modifica para um arquivo temporário e depois o move de volta:

TMP=`mktemp`
modifyfile "$original" "$TMP"
mv -v "$TMP" "$original"

Isso tem o efeito colateral lamentável de destruir as permissões nesse arquivo. O arquivo é recriado com permissões padrão.

Existe uma maneira de dizer ao mvcomando para substituir o destino sem alterar suas permissões? Ou, alternativamente, existe uma maneira de salvar o usuário, grupo e permissões do original e restaurá-los?

Stephen Ostermiller
fonte

Respostas:

10

Em vez de usar mv, basta redirecionar cat. Por exemplo:

TMP=$(mktemp)
modifyfile "$original" "$TMP"
cat "$TMP" > "$original"

Isso substitui $originalo conteúdo de $TMP, sem tocar em nada no nível do arquivo.

strugee
fonte
Isso não seria problemático se algum programa tivesse um identificador de arquivo aberto?
Martin von Wittich
2
Eu também tenho que rm "$TMP"fazê-lo, mas parece fazer exatamente o que eu quero.
Stephen Ostermiller
@MartinvonWittich provavelmente seria um problema se você estivesse usando mv. Não vejo uma maneira de resolver esse problema.
strugee
2
@MartinvonWittich Yes. Create-new-then-move fornece uma alteração atômica e não afeta os programas que têm o arquivo aberto, mas como ele cria um novo arquivo, a propriedade e as permissões do arquivo são perdidas. Truncar existente e gravar preserva as permissões e a propriedade, mas perde dados em caso de falha e passa o tapete sob os pés dos programas que têm o arquivo aberto. Você não pode combinar as partes boas de ambos.
Gilles 'SO- stop be evil'
1
@MartinvonWittich chownfunciona apenas como raiz. chmode chgrppode ou não funcionar, dependendo das permissões do usuário. Nenhum deles copia outros atributos, como ACL ou atributos estendidos específicos do sistema de arquivos.
Gilles 'SO- stop be evil'
10

Existem duas estratégias para substituir um arquivo por uma nova versão:

  1. Crie um arquivo temporário com a nova versão e mova-o para o lugar.

    • Vantagem: se um programa abrir esse arquivo, ele lerá o conteúdo antigo ou o novo, dependendo de ter aberto o arquivo antes ou depois da mudança. Não há confusão.
    • Vantagem: em caso de falha, o conteúdo antigo é preservado.
    • Desvantagem: como um novo arquivo é criado, os atributos do arquivo (propriedade, permissão etc.) não são preservados.
  2. Substitua o arquivo antigo no local.

    • Vantagem: os atributos do arquivo são preservados.
    • Desvantagem: em caso de falha, o arquivo pode ser deixado pela metade.
    • Desvantagem: se um programa tem o arquivo aberto quando está sendo atualizado, esse programa pode ler dados inconsistentes.

Se você puder, use o método 1, mas primeiro replique os atributos do arquivo original com cp -p --attributes-only. Isso requer GNU coreutils (ou seja, Linux não incorporado ou ambientes suficientemente semelhantes ao Linux). Se o seu cpnão tiver --attributes-only, omita esta opção: funcionará, mas replicará os dados também.

tmp=$(mktemp)
cp -p --attributes-only "$original" "$tmp"
modifyfile "$original" "$tmp"
mv -f "$tmp" "$original"

Se você não puder replicar os atributos do arquivo existente, por exemplo, porque você possui permissões de gravação, mas não o possui e deseja preservar o proprietário, apenas o método 2 é possível. Para minimizar o risco de perda de dados:

  • Faça com que a janela durante a qual o arquivo fique incompleto seja o menor possível. Prepare os dados primeiro em um arquivo temporário e copie-os no lugar.
  • Faça um backup do arquivo antigo primeiro.

tmp=$(mktemp)
backup="${original}~"
modifyfile "$original" "$tmp"
cp -p "$original" "$backup"
cp -f "$tmp" "$original"
Gilles 'SO- parar de ser mau'
fonte
Boa resposta! Atualmente, sugiro usar o argumento --attributes-only com o comando cp no método 1 . Dessa forma, o cp -p --attributes-only "$original" "$tmp"não usará recursos para copiar o conteúdo do arquivo. Não consegui encontrar de qual versão esse argumento foi adicionado.
Marcelo Barros
@MarceloBarros Foi adicionado no GNU coreutils 8.6 lançado em 15/10/2010, então hoje em dia, se você possui o GNU coreutils, deve tê-lo. Ainda não existe tal coisa com outras cpimplementações.
Gilles 'SO- stop be evil'
5

Após nossa discussão sobre a primeira resposta, proponho uma resposta diferente:

TMP="$(mktemp "$original".XXXXXXXXXX)"
modifyfile "$original" "$TMP"
chmod --reference="$original" "$TMP"
chown --reference="$original" "$TMP"
mv -f "$TMP" "$original"

Observações:

  • Uso $originalno mktempmodelo para garantir que o arquivo temporário não seja colocado, /tmpmas na mesma pasta que $original. Acredito que, se /tmpmontado em um sistema de arquivos diferente, a operação não seria mais atômica.
  • O resultado de mktempagora é citado caso contenha espaço em branco.
  • Eu uso em $()vez de `` porque eu o considero mais limpo.
  • ch{mod,own} --referencesão usados ​​para transferir as permissões de $originalpara $TMP. Se alguém tiver ideias adicionais sobre quais metadados podem e devem ser transferidos, edite minha postagem e adicione-a.
  • Bem, isso requer permissões de root, como Gilles apontou. Bem, não vou descartar isso agora que escrevi: P
Martin von Wittich
fonte