Portanto, excluí minha pasta pessoal (ou, mais precisamente, todos os arquivos aos quais tive 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
, remover a declaração e todos os seus usos - mas o rm
. Bash se expande alegremente para rm -rf /*
. Sim.
Eu me senti idiota, 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 ocorram ou sejam pelo menos menos prováveis? Por exemplo, se eu tivesse escrito
FileUtils.rm_rf("#{build}/*")
em um script Ruby, o intérprete teria reclamado por build
não ter sido declarado, então a linguagem me protege.
O que eu considerei no bash, além de encurralar rm
(que, como muitas respostas em perguntas relacionadas mencionam, não é problemático):
rm -rf "./${build}/"*
Isso teria matado meu trabalho atual (um repositório Git), mas nada mais.- Uma variante / parametrização
rm
que 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?
fonte
rm -rf "${build}/*
não importa para onde vão as aspas.rm -rf "${build}
fará a mesma coisa por causa dof
.set -u
não é tão desaprovado quantoset -e
é, mas ainda tem suas desvantagens.#! /usr/bin/env ruby
no topo de todos os scripts de shell e esquecer o bash;) #Respostas:
ou
Isso faria o shell atual tratar expansões de variáveis não definidas como um erro:
set -u
eset -o nounset
são opções de shell POSIX .Um valor vazio não acionaria um erro.
Para isso, use
A expansão de
${variable:?word}
seria expandida para o valor de, avariable
menos que esteja vazio ou não esteja definido . Se estiver vazio ou não definido, oword
erro será exibido no erro padrão e o shell tratará a expansão como um erro (o comando não será executado e, se estiver executando em um shell não interativo, isso será encerrado). Deixar de:
fora acionaria o erro apenas para um valor não definido, assim como abaixoset -u
.${variable:?word}
é uma expansão de parâmetro POSIX .Nenhum deles causaria o término de um shell interativo, a menos que
set -e
(ouset -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 -u
causaria a saída de um script se usado junto comset -e
.Quanto à sua segunda pergunta. Não há como limitar
rm
a não trabalhar fora do diretório atual.A implementação do GNU
rm
tem uma--one-file-system
opção que impede a exclusão recursiva de sistemas de arquivos montados, mas é o mais próximo que acredito que podemos chegar sem envolver arm
chamada em uma função que realmente verifica os argumentos.Como uma nota lateral:
${build}
é exatamente equivalente a$build
menos que a expansão ocorre como parte de uma cadeia onde o caractere imediatamente seguinte é um caractere válido em um nome de variável, como em"${build}x"
.fonte
${build:?msg}
ou${build?msg}
? 2) No contexto de algo como scripts de construção, acho que seria bom usar uma ferramenta diferente da rm para excluir com segurança: sabemos que não queremos trabalhar fora do diretório atual, por isso, tornamos explícito usando um método restrito comando. Não há necessidade de tornar a empresa mais segura em geral.:
(isso acionaria o erro apenas para variáveis não definidas ). Não ouso tentar escrever uma ferramenta que lide com a exclusão de arquivos sob um caminho específico, exclusivamente, em todas as circunstâncias. Analisar caminhos e cuidar / não se importar com links simbólicos etc. é um pouco complicado para mim no momento.Vou sugerir verificações normais de validação usando
test
/[ ]
Você estaria seguro se tivesse escrito seu script como tal:
As
[ -n "${build}" ]
verificações que"${build}"
são uma sequência de comprimento diferente de zero .O
||
é o operador OR lógico no bash. Isso 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 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 também podem ser usadas.Por exemplo:
fonte
${variable:?word}
@Kusalananda propõe?test
(por exemplo[
) possui muitas outras verificações relevantes, como-d
(o argumento é um diretório),-f
(o argumento é um arquivo),-O
(o arquivo pertence ao usuário atual) e assim por diante.${build:?word}
? O${variable:?word}
formato não o protege se você remover todas as instâncias da variável.if
não é. 2) "você também não removeu $ {build:? Word} junto com ele" - o cenário é que perdi o uso da variável. Usar${v:?w}
teria me protegido de danos. Se eu tivesse removido todos os usos, mesmo o acesso simples não teria sido prejudicial, obviamente.No seu caso específico, reformulei a 'exclusão' no passado para mover arquivos / diretórios (supondo que / tmp esteja na mesma partição que o seu diretório):
Nos bastidores, isso move as referências de arquivo / diretório de nível superior da origem para as
$trashdir
estruturas de diretório de destino na mesma partição e não gasta tempo percorrendo a estrutura de diretórios e liberando os blocos de disco por arquivo naquele momento. 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).Como alternativa, uma entrada cron para limpar periodicamente /tmp/.trash-$USER manterá / tmp 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ê poderá criar um diretório semelhante a / tmp na partição e fazer com que o cron limpe isso.
Mais importante, porém, se você estragar as variáveis de alguma forma, poderá recuperar o conteúdo antes que a limpeza ocorra.
fonte
/tmp
estiver em outra partição (acho que sempre é para mim, pelo menos para scripts executados no espaço do usuário); então, a pasta da lixeira precisa ser alterada (e não se beneficiará da manipulação do SO/tmp
).mktemp -d
para obter esse diretório temporário sem pisar nos dedos de outro processo (e honrar corretamente$TMPDIR
).Use a substituição do bash paramater para atribuir padrões quando as variáveis não forem inicializadas, por exemplo:
rm -rf $ {variable: - "/ inexistente"}
fonte
Eu sempre tento iniciar meus scripts Bash com uma linha
#!/bin/bash -ue
.-e
significa "falhar em primeiro uncathed e rror";-u
significa "falhar em primeiro uso de u ndeclared variável".Encontre mais detalhes no excelente artigo Use o modo restrito não oficial do Bash (a menos que você perca a depuração) . O autor também recomenda o uso,
set -o pipefail; IFS=$'\n\t'
mas para meus propósitos isso é um exagero.fonte
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 exibir o conteúdo do
$build
diretório para excluí-lo, mas não o próprio$build
diretório. Portanto, se você pular o estranho*
, um valor não definido se tornará orm -rf /
que, por padrão, a maioria das implementações rm na última década se recusará a executar (a menos que você desative essa proteção com a--no-preserve-root
opção do GNU rm ).Ignorar a trilha
/
também resultaria narm ''
mensagem de erro:Isso funciona mesmo que seu comando rm não implemente proteção para
/
.fonte
Você está pensando em termos de linguagem de programação, mas bash é uma linguagem de script :-) Portanto, use um paradigma de instrução totalmente diferente para um paradigma de linguagem totalmente diferente .
Nesse caso:
Como
rmdir
se recusará a remover um diretório não vazio, você deverá remover os membros primeiro. Você sabe o que são esses membros, certo? Provavelmente são parâmetros para o seu script ou derivados de um parâmetro, portanto:Agora, se você colocar alguns outros arquivos ou diretórios, como um arquivo temporário ou algo que não deveria, o
rmdir
erro será gerado . Manuseie-o corretamente e:Esse modo de pensar me serviu bem porque ... sim, esteve lá, fez isso.
fonte
make clean
usará alguns curingas (a menos que um compilador muito diligente crie uma lista de todos os arquivos que cria). Além disso, parece-me que terrm -rf ${build}/${parameter}
apenas movido a questão um pouco para baixo.make
questão. Escrever uma resposta aplicável apenasmake
seria uma suposição muito específica e surpreendente. Minha resposta funciona para outros casos além domake
desenvolvimento de software, além do problema específico para iniciantes que você estava tendo, excluindo suas coisas.