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 / 454694
Accepted
Raphael
Raphael
Asked: 2018-07-12 04:57:51 +0800 CST2018-07-12 04:57:51 +0800 CST 2018-07-12 04:57:51 +0800 CST

Como posso proteger scripts bash contra causar danos quando alterados no futuro?

  • 772

Então, apaguei minha pasta pessoal (ou, mais precisamente, todos os arquivos aos quais eu tinha acesso de gravação). O que aconteceu é que eu tinha

build="build"
...
rm -rf "${build}/"*
...
<do other things with $build>

em um script bash e, depois de não precisar mais $build, removendo a declaração e todos os seus usos - mas o rm. Bash felizmente se expande para rm -rf /*. Sim.

Eu me senti estúpido, instalei o backup, refiz o trabalho que perdi. Tentando superar a vergonha.

Agora, eu me pergunto: quais são as técnicas para escrever scripts bash para que tais erros não aconteçam, ou pelo menos sejam menos prováveis? Por exemplo, se eu tivesse escrito

FileUtils.rm_rf("#{build}/*")

em um script Ruby, o intérprete teria reclamado por buildnão ter sido declarado, então aí a linguagem me protege.

O que considerei no bash, além de encurralar rm(o que, como muitas respostas em perguntas relacionadas mencionam, não é isento de problemas):

  1. rm -rf "./${build}/"*
    Isso teria matado meu trabalho atual (um repositório Git), mas nada mais.
  2. Uma variante/parametrização rmdisso requer interação ao atuar fora do diretório atual. (Não foi possível encontrar nenhum.) Efeito semelhante.

É isso ou existem outras maneiras de escrever scripts bash que são "robustos" nesse sentido?

bash shell-script
  • 7 7 respostas
  • 4613 Views

7 respostas

  • Voted
  1. Best Answer
    Kusalananda
    2018-07-12T05:00:22+08:002018-07-12T05:00:22+08:00
    set -u
    

    ou

    set -o nounset
    

    Isso faria com que o shell atual tratasse as expansões de variáveis ​​não definidas como um erro:

    $ unset build
    $ set -u
    $ rm -rf "$build"/*
    bash: build: unbound variable
    

    set -ue set -o nounsetsão opções de shell POSIX .

    No entanto, um valor vazio não acionaria um erro.

    Para isso, utilize

    $ rm -rf "${build:?Error, variable is empty or unset}"/*
    bash: build: Error, variable is empty or unset
    

    A expansão de ${variable:?word}expandiria para o valor de variable, a menos que estivesse vazio ou não definido. Se estiver vazio ou não definido, o wordserá exibido em erro padrão e o shell tratará a expansão como um erro (o comando não será executado e, se estiver sendo executado em um shell não interativo, isso será encerrado). Deixar de :fora acionaria o erro apenas para um valor não definido, assim como em set -u.

    ${variable:?word}é uma expansão de parâmetro POSIX .

    Nenhum deles faria com que um shell interativo terminasse, a menos que set -e(ou set -o errexit) também estivesse em vigor. ${variable:?word}faz com que os scripts saiam se a variável estiver vazia ou não definida. set -ufaria com que um script fosse encerrado se usado junto com set -e.


    Quanto à sua segunda pergunta. Não há como limitar rmo trabalho fora do diretório atual.

    A implementação GNU de rmtem uma --one-file-systemopção que a impede de excluir recursivamente os sistemas de arquivos montados, mas isso é o mais próximo que acredito que podemos obter sem envolver a rmchamada em uma função que realmente verifica os argumentos.


    Como uma nota lateral: ${build}é exatamente equivalente a , a $buildmenos que a expansão ocorra como parte de uma string em que o caractere imediatamente seguinte seja um caractere válido em um nome de variável, como em "${build}x".

    • 78
  2. Centimane
    2018-07-12T10:36:04+08:002018-07-12T10:36:04+08:00

    Vou sugerir verificações de validação normais usando test/[ ]

    Você estaria seguro se tivesse escrito seu script como tal:

    build="build"
    ...
    [ -n "${build}" ] || exit 1
    rm -rf "${build}/"*
    ...
    

    As [ -n "${build}" ]verificações que "${build}"é uma string de comprimento diferente de zero .

    O ||é o operador OR lógico no bash. Faz com que outro comando seja executado se o primeiro falhar.

    Desta forma, ${build}estava vazio/indefinido/etc. o script teria saído (com um código de retorno de 1, que é um erro genérico).

    Isso também o protegeria caso você removesse todos os usos , ${build}porque [ -n "" ]sempre será falso.


    A vantagem de usar test/ [ ]é que existem muitas outras verificações mais significativas que ele também pode usar.

    Por exemplo:

    [ -f FILE ] True if FILE exists and is a regular file.
    [ -d FILE ] True if FILE exists and is a directory.
    [ -O FILE ] True if FILE exists and is owned by the effective user ID.
    
    • 12
  3. user117529
    2018-07-13T00:07:01+08:002018-07-13T00:07:01+08:00

    No seu caso específico, eu reformulei 'exclusão' no passado para mover arquivos/diretórios (assumindo que /tmp está na mesma partição que seu diretório):

    # mktemp -d is also a good, reliable choice
    trashdir=/tmp/.trash-$USER/trash-`date`
    mkdir -p "$trashdir"
    ...
    mv "${build}"/* "$trashdir"
    ...
    

    Nos bastidores, isso move as referências de arquivo/diretório de nível superior da origem para as $trashdirestruturas de diretório de destino, todas na mesma partição, e não gasta tempo percorrendo a estrutura de diretório e liberando os blocos de disco por arquivo ali mesmo. Isso produz uma limpeza muito mais rápida enquanto o sistema está em uso ativo, em troca de uma reinicialização um pouco mais lenta (/tmp é limpo nas reinicializações).

    Alternativamente, uma entrada cron para limpar periodicamente /tmp/.trash-$USER impedirá que /tmp seja preenchido, para processos (por exemplo, compilações) que consomem muito espaço em disco. Se seu diretório estiver em uma partição diferente como /tmp, você pode criar um diretório semelhante a /tmp em sua partição e fazer com que o cron limpe isso.

    Mais importante, porém, se você estragar as variáveis ​​de alguma forma, você pode recuperar o conteúdo antes que a limpeza aconteça.

    • 4
  4. rackandboneman
    2018-07-12T21:56:27+08:002018-07-12T21:56:27+08:00

    Use a substituição de parâmetro bash para atribuir padrões quando a variável não for inicializada, por exemplo:

    rm -rf ${variável:-"/inexistente"}

    • 2
  5. niya3
    2018-07-25T10:25:37+08:002018-07-25T10:25:37+08:00

    Eu sempre tento iniciar meus scripts Bash com uma linha #!/bin/bash -ue.

    -esignifica "falha no primeiro erro não detectado ";

    -usignifica "falha no primeiro uso da variável não declarada".

    Encontre mais detalhes no ótimo artigo Use o Unofficial Bash Strict Mode (a menos que você adore a depuração) . Também o autor recomenda o uso set -o pipefail; IFS=$'\n\t', mas para meus propósitos isso é um exagero.

    • 1
  6. eschwartz
    2018-07-25T11:57:55+08:002018-07-25T11:57:55+08:00

    O conselho geral para verificar se sua variável está definida é uma ferramenta útil para evitar esse tipo de problema. Mas neste caso havia uma solução mais simples.

    Provavelmente, não há necessidade de glob o conteúdo do $builddiretório para excluí-los, mas não o próprio $builddiretório real. Portanto, se você pulou o estranho *, um valor não definido se transformaria no rm -rf /qual, por padrão, a maioria das implementações de rm na última década se recusará a executar (a menos que você desative essa proteção com a --no-preserve-rootopção do GNU rm).

    Ignorar o trailing /também resultaria na rm ''mensagem de erro:

    rm: can't remove '': No such file or directory
    

    Isso funciona mesmo que seu comando rm não implemente proteção para arquivos /.

    • 1
  7. Rich
    2018-07-12T14:03:40+08:002018-07-12T14:03:40+08:00

    Você está pensando em termos de linguagem de programação, mas bash é uma linguagem de script :-) Então, use um paradigma de instrução totalmente diferente para um paradigma de linguagem totalmente diferente .

    Nesse caso:

    rmdir ${build}
    

    Como rmdirse recusará a remover um diretório não vazio, você precisa remover os membros primeiro. Você sabe quais são esses membros, certo? Eles provavelmente são parâmetros para seu script ou derivados de um parâmetro, então:

    rm -rf ${build}/${parameter}
    rmdir ${build}
    

    Agora, se você colocar alguns outros arquivos ou diretórios lá, como um arquivo temporário ou algo que você não deveria, rmdirele gerará um erro. Trate-o corretamente, então:

    rmdir ${build} || build_dir_not_empty "${build}"
    

    Essa maneira de pensar me serviu bem porque... sim, estive lá, fiz isso.

    • -3

relate perguntas

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

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

  • MySQL Select com função IN () com array bash

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

  • 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

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

    • 4 respostas
  • Marko Smith

    ssh Não é possível negociar: "nenhuma cifra correspondente encontrada", está rejeitando o cbc

    • 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

    Como descarregar o módulo do kernel 'nvidia-drm'?

    • 13 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
    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
    Wong Jia Hau ssh-add retorna com: "Erro ao conectar ao agente: nenhum arquivo ou diretório" 2018-08-24 23:28:13 +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
  • Martin Hope
    Bagas Sanjaya Por que o Linux usa LF como caractere de nova linha? 2017-12-20 05:48:21 +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