Como impedir que opções 'shopt' não suportadas causem erros no meu .bashrc?

9

Trabalho em um ambiente relativamente heterogêneo, onde posso estar executando versões diferentes do Bash em diferentes nós HPC, VMs ou minha estação de trabalho pessoal. Como eu coloquei meus scripts de login em um repositório Git, eu gostaria de usar o mesmo (ish) .bashrcem geral, sem muitos "se este host, então ..." - confusão do tipo.

I como o comportamento padrão do Bash ≤ 4.1 que se expande cd $SOMEPATHem cd /the/actual/pathquando pressionando a Tabtecla. No Bash 4.2 e acima, você precisaria shopt -s direxpandreativar esse comportamento, que não estava disponível até o 4.2.29 . Este é apenas um exemplo; outra shoptopção possivelmente relacionada complete_fullquote(embora eu não saiba exatamente o que faz) também pode ter mudado o comportamento padrão na v4.2.

No entanto, direxpandnão é reconhecido pelas versões anteriores do Bash e, se eu tentar shopt -s direxpandno meu .bashrc, isso resulta em uma mensagem de erro sendo impressa no console toda vez que eu faço logon em um nó com um Bash mais antigo:

-bash: shopt: direxpand: invalid shell option name

O que eu gostaria de fazer é agrupar uma condição condicional shop -s direxpandpara ativar essa opção no Bash> 4.1 de maneira robusta, sem atrapalhar as versões mais antigas do Bash ( ou seja , não apenas redirecionar a saída de erro para /dev/null).

TheDudeAbides
fonte
Como minha resposta não ajudou?
Luciano Andress Martini
@LucianoAndressMartini Sim, e essa foi a solução que acabei adotando por conta própria .bashrc. Eu ainda queria um registro de como usar $BASH_VERSINFOpara interrogar a versão principal / secundária do shell em execução, para minha própria edificação, e é por isso que terminei de postar minha própria resposta. :)
TheDudeAbides
Olhe na minha resposta, eu tenho algo sobre como comparar a versão de programas com o shell script.
Luciano Andress Martini

Respostas:

14

Verifique se direxpandestá presente na saída de shopte ative-o se estiver:

shopt | grep -q '^direxpand\b' && shopt -s direxpand
Luciano Andress Martini
fonte
4
É melhor garantir que grep -q '^direxpand\b', caso alguma versão futura ou fork do bash tenha uma opção que contenha isso como substring e remova direxpand. Improvável neste caso específico, mas não custa muito para ser robusto.
Gilles 'SO- stop be evil'
Obrigado Luciano. Eu pretendia responder minha própria pergunta, mas aceitarei sua resposta depois que minhas edições passarem pela revisão por pares. Talvez você possa aprová-las você mesmo?
TheDudeAbides 5/05/19
4
O Bash permite consultar opções de shell específicas, para que você possa usar [ -z "$(shopt -po direxpand 2>&-)" ] || shopt -s direxpand. Não há mais problemas de regex! :-)
David Foerster
@ DavidFoerster Eu mudaria a lógica: do [ -n "blah" ] && shopt blahjeito que você diz, você está dizendo "se direxpand não é suportado, então não faça isso".
Rico
1
@ Rich: A maioria dos meus scripts de shell inclui set -eno topo, então eu costumo usar a lógica de atalho dessa maneira.
David Foerster
16

Não vejo o que há de errado em redirecionar erros para /dev/null. Se você deseja que seu código seja robusto set -e, use o idioma comum … || true:

shopt -s direxpand 2>/dev/null || true

Se você deseja executar algum código de fallback, se a opção não existir, use o status de retorno de shopt:

if shopt -s direxpand 2>/dev/null; then
   # the direxpand option exists
else
   # the direxpand option does not exist
fi

Mas se você realmente não gosta de redirecionar o erro, pode usar o mecanismo de conclusão para executar a introspecção. Isso pressupõe que você não possui máquinas antiquadas com bash ≤ 2,03 que não possuem conclusão programável.

shopt_exists () {
  compgen -A shopt -X \!"$1" "$1" >/dev/null
}
if shopt_exists direxpand; then
  shopt -s direxpand
fi

Esse método evita a bifurcação, o que é lento em alguns ambientes, como o Cygwin. O mesmo acontece com o direto 2>/dev/null, não acho que você possa superar isso no desempenho.

Gilles 'SO- parar de ser mau'
fonte
Isso é não onde meu cérebro teria ido, mas eu gosto da compgenproposta. Isso é coisa de nível do time do colégio ali mesmo! Evitar o redirecionamento para /dev/nullé apenas uma preferência pessoal. Eu gosto de pedir permissão em vez de perdão, se isso faz sentido? :)
TheDudeAbides
+1 para uma escolaridade totalmente imprevista na conclusão programável do Bash, o que me forçou a ir ao manual para decifrar o que compgen -A shopt -X ...significava.
TheDudeAbides
4
@TheDudeAbides Eu li sobre o uso compgendessa maneira no Unix e Linux , não sei quem o propôs pela primeira vez. (Parei de usar o bash como meu shell principal antes de ter a conclusão programável.) Na programação, geralmente é uma má idéia pedir permissão porque existe o risco de a verificação de permissão não corresponder ao que você está realmente fazendo, devido a uma codificação erro (onde você não está verificando o que pensa estar verificando) ou porque o que você verificou mudou antes de usá-lo .
Gilles 'SO- stop be evil'
5

Quando você tem certeza de que uma shoptopção específica está disponível em uma versão principal / secundária / de patch do Bash, você pode inspecionar a $BASH_VERSIONvariável ou os elementos da $BASH_VERSINFO[]matriz para habilitá-la condicionalmente.

Aqui está um teste para o Bash 4.2.29 ou superior, a versão em que direxpand foi introduzida pela primeira vez na série 4.2:

if [[ $BASH_VERSION == 4.2.* && ${BASH_VERSINFO[2]} -ge 29 ]] ||
   [[ ${BASH_VERSINFO[0]} -eq 4 && ${BASH_VERSINFO[1]} -ge 3 ]] ||
   [[ ${BASH_VERSINFO[0]} -ge 5 ]]; then
    shopt -s direxpand
fi

Edit: Para ser claro, esta é uma solução ridiculamente super-projetada para simplesmente ignorar uma mensagem de erro proveniente de seus scripts de login, mas eu queria documentá-la independentemente, para minha própria edição.

Observe as chaves ao redor , que são necessárias e o uso de e , que fazem comparações lógicas de números inteiros em vez de (dependentes do código do idioma). Se não for citado, o RHS do operador é tratado como padrões "extglob" dentro de Bash / condicionais, conforme observado aqui , o que torna uma comparação "estética com" mais estética do que uma regex seria, IMO.${BASH_VERSINFO[index]}-eq-gt==[[]]

A $BASH_VERSINFOmatriz contém todas as informações que você veria na saída de bash --version:

bash --version | head -1
# result:
# GNU bash, version 4.3.48(1)-release (x86_64-pc-linux-gnu)

declare -p BASH_VERSINFO
# result:
# declare -ar BASH_VERSINFO='([0]="4" [1]="3" [2]="48" [3]="1" [4]="release" [5]="x86_64-pc-linux-gnu")'

Quando não está claro na documentação para shopta qual as versões do Bash foram suportadas ou alteraram seu comportamento, o método proposto por Luciano é bom:

# note the '-q' so that the matched pattern isn't actually printed
shopt | grep -q direxpand && shopt -s direxpand

... como é a solução proposta por Gilles de apenas ignorar o erro ( shopt -s direxpand 2>/dev/null) e talvez verificar $?se é absolutamente necessário.

Referências: 1 , 2 , 3
Leitura relacionada: Set and Shopt - Why Two?

TheDudeAbides
fonte
Você também pode ser capaz de usar algo como if [[ $BASH_VERSION > 4.3 ]];(que corresponde 4.3.0, 5.0etc., mas também 4.3.0-alphanão sei se os assuntos de fatos posteriores..)
ilkkachu
Oi @ilkkachu. Obrigado pela sua edição para cobrir o Bash v5.x. A direxpandopção está realmente disponível para o Bash 4.2; Eu verifiquei isso com uma imagem do Docker na v4.2.53 executando docker run --rm bash:4.2 bash -c shopt | grep direxpand(e, para uma boa medida, que de fato não está disponível na v4.1.17 executando docker run --rm bash:4.1 bash -c shopt | grep direxpand).
TheDudeAbides 23/08/19
ah ok, eu testei 4.2.0e me deparei com o fato de que não funcionava lá. O changelog também menciona que foi adicionado bash-4.3-alpha. Suponho então que seria preciso verificar ${BASH_VERSINFO[2]}para ser exato sobre isso, mas eu não sei qual ponto de libertação acrescentou que ...
ilkkachu
Acho que basicamente provamos o argumento de Gilles acima; de fato, é melhor tentar ativar a opção de shell e lidar com o erro (ou suprimi-lo) se não for suportado.
TheDudeAbides