Como posso obter com segurança a versão do ksh?

12

Como posso obter com segurança a versão do ksh de um script ksh?

Eu vi as seguintes soluções :

  1. ksh --version
  2. echo ${.sh.version}
  3. echo $KSH_VERSION

E, dadas as circunstâncias corretas, cada uma delas funciona corretamente. No entanto, eu me preocupo com o caso não perfeito.

Especificamente, existem várias máquinas com as quais trabalho que possuem versões mais antigas do ksh que, para meus propósitos, estão com uma falta grave de funcionalidade. De qualquer forma, o motivo pelo qual desejo verificar a versão (programaticamente) é ver se a versão ksh é uma das versões menos capazes; e, se assim for, quero executar um ramo com código menos impressionante.

No entanto, nas máquinas problemáticas, a inaptidão do shell se estende à verificação da versão ...

  • Se eu tentar ksh --version, ele não imprime nada e abre uma nova instância do ksh!
  • Se eu tentar echo ${.sh.version}, kshtrata isso como um erro de sintaxe que não pode ser descartado 2> /dev/null.

    $ echo ${.sh.version} 2> /dev/null  
    ksh: ${.sh.version}: bad substitution
  • É claro que echo $KSH_VERSIONparece funcionar bem - quero dizer, não falhará - embora nessas máquinas esteja em branco. Além disso, eu vi em algum lugar que KSH_VERSIONé definido apenas por pdksh.

Questões:

  • Como posso verificar com segurança a versão do kshprogramaticamente? Para meus propósitos aqui, eu realmente não me importo com o número da versão real, apenas se é uma versão desatualizada do ksh.
  • É $KSH_VERSIONbom o suficiente? Quero dizer, se estiver em branco, então é kshnecessariamente uma versão desatualizada? Esse outro fórum estava correto e pode não ser definido mesmo para versões mais recentes ksh?
  • Não há como verificar isso?
Sildoreth
fonte
1
Algum motivo para você querer dois caminhos de código e não apenas um com menos código impressionante?
Thorbjørn Ravn Andersen
@ ThorbjørnRavnAndersen tem a ver com o prompt. No meu arquivo .kshrc, tenho uma função que simula a funcionalidade de abreviação de pwd dos prompts tcsh e zsh e configurei PS1para usar essa função. No entanto, ksh Velho não suporta $()no PS1. Portanto, se é uma versão moderna do ksh, quero PS1usar a função que criei; se é a versão antiga, eu uso apenas $PWD.
Sildoreth
Bem, você poderia ter duas versões do seu arquivo de configuração (talvez uma gerada a partir da outra) e depois distribuir a versão apropriada para a máquina em questão?
Thorbjørn Ravn Andersen
Outra abordagem poderia ser simplesmente dizer "É apenas esta máquina específica que tem o problema - vou encontrar um arquivo ou variável de ambiente ou outra coisa que só existe aqui (provavelmente AIX ou algo assim) e testar isso".
Thorbjørn Ravn Andersen

Respostas:

7

Eu acho que .sh.versionexiste desde a primeira versão do ATT ksh 93. Não está disponível em pdksh ou mksh. Desde a${.sh.version} existe um erro de sintaxe em shells diferentes de ksh93, envolva o teste em um subshell e proteja-o eval.

_sh_version=$(eval 'echo "${.sh.version}"' 2>/dev/null) 2>/dev/null
case $_sh_version in
  '') echo "This isn't ATT ksh93";;
  
esac

KSH_VERSION começou no clone ksh de domínio público (pdksh) e foi adicionado ao shell Korn real há relativamente pouco tempo, em 2008, com o ksh93t.

Em vez de testar o número de uma versão, você deve testar o recurso específico que está causando sofrimento. A maioria dos recursos pode ser testada, tentando alguma construção em um subshell e veja se ele desencadeia um erro.

Gilles 'SO- parar de ser mau'
fonte
Não vejo diferença ao usar um subshell. Ele ainda trata ${.sh.version}como um erro de sintaxe que não pode ser reconciliado. A mensagem que recebo é bad substitution.
Sildoreth
@sil O objetivo de usar um subshell é capturar o erro. Redirecione erros para /dev/nulle ignore o status de saída.
Gilles 'SO- stop be evil'
Eu entendo o que você está dizendo. O que estou dizendo é que o erro não é redirecionado. É sempre imprime para o console. Eu tentei isso no Solaris, AIX e HP-UX; e o ksh exibe esse comportamento em todos eles.
Sildoreth 5/05
@Sildoreth Ah. Eu havia testado apenas no Linux e não tenho nenhum desses sistemas operacionais para testar agora. Funciona eval '_sh_version=$(echo "${.sh.version}")' 2>/dev/nullmelhor?
Gilles 'SO- stop be evil'
Isso é um pouco melhor. Ele funciona corretamente no Solaris e no HP-UX. Para o AIX, ele funciona na linha de comando, mas curiosamente começa a falhar novamente se eu tentar colocá-lo em uma função shell.
Sildoreth 5/05
6

KSH_VERSIONnão foi implementado ksh93antes da versão 93t. Ele será definido em mksh, pdksh, lksh. Então, para verificar a versão doksh , podemos tentar estas etapas:

  • Verificando KSH_VERSIONpara detectar mksh,pdksh ,lksh
  • Se o primeiro passo falhar, tente um recurso diferente entre ksh93e ksh88/86( deixe David Korn nos mostrar ).

Com isso em mente, irei com:

case "$KSH_VERSION" in
  (*MIRBSD*|*PD*|*LEGACY*) printf '%s\n' "$KSH_VERSION" ;;
  (*) [ -z "$ERRNO" ] && printf '%s\n' "${.sh.version}" || echo ksh88/86 ;;
esac
cuonglm
fonte
Por que isso não verifica primeiro se $KSH_VERSIONnão está em branco? Na minha máquina Ubuntu, isso imprime "ksh93", ainda KSH_VERSIONestá definido.
Sildoreth
Isso falharia se algum código executado anteriormente (por exemplo: .kshrc) violasse a variável KSH_VERSION com algum valor aleatório.
Jlliagre
@ jlliagre: Não, como foi executado como um script, ele não lê .kshrc.
cuonglm
Se a ENVvariável estiver definida (e normalmente estiver definida como ~/.kshrc), o script definitivamente lerá o .kshrcarquivo. Obviamente, seria bastante estranho para um script definir um KSH_VERSION falso, mas isso é possível, assim como executar explicitamente um script com um intérprete diferente do especificado na primeira linha é uma situação possível.
Jlliagre #
@ jlliagre: Mesmo que você possa alterá-lo, você receberá segfault quando fizer referência a KSH_VERSION. E em mksh, pdksh, lksh, KSH_VERSIONé marcado como somente leitura.
Cuonglm
5

Para kshlançamentos "reais" (ou seja, baseados na AT&T), eu uso este comando:

strings /bin/ksh | grep Version | tail -2 

Aqui estão várias saídas que recebo:

Ksh original:

@(#)Version M-11/16/88i

dtksh;

@(#)Version 12/28/93
Version not defined

Ksh93 moderno:

@(#)$Id: Version AJM 93u+ 2012-08-01 $

Para pdksh/ msh kshclones e kshversões modernas da AT&T também, aqui está algo que funciona:

$ mksh -c 'echo $KSH_VERSION'
@(#)MIRBSD KSH R50 2015/04/19

Editar:

Eu esqueci que você estava perguntando sobre fazer isso de dentro de um script, não conhecendo o caminho para o binário ksh testado.

Supondo que você realmente queira a versão kshusada, e não os recursos suportados, aqui está uma maneira de fazê-lo usando apenas o stringscomando que deve funcionar pelo menos no Linux e Solaris:

echo $(for i in $(find /proc/$$ ! -type d ! -name "pagemap" | 
  grep -v "/path/" | grep -v "/fd/" ) ; do
  strings $i | egrep "([V]ersion|[K]SH_VERSION).*[0-9]" | sort -u
done 2>/dev/null)

Observe que esse método não é confiável, pois /procpode não ser montado, e certamente existem outros pontos fracos. Não foi testado em outros sistemas operacionais Unix.

jlliagre
fonte
Isso não fará distinção entre lkshe pdkshno Debian Jessie.
Cuonglm
@cuonglm Não tenho Jessie para testar. Você quer dizer lkshe pdkshnão pode ser separado deles KSH_VERSION?
Jlliagre
Não, quero dizer correndo stringsneles. KSH_VERSIONdefinitivamente pode.
Cuonglm
@cuonglm Desculpe se eu não estava claro. Quando escrevi «para kshlançamentos " reais " », excluí explicitamente clones que não são da AT&T como ksh pdksh, mkshe lksh.
Jlliagre
Rodar stringsem algum binário ksh é uma má idéia, porque você não sabe se é esse que está executando seu script. Talvez seu script esteja sendo executado por /usr/local/bin/kshou /home/bob/bin/kshou /bin/shou /usr/posix/bin/shou ...
Gilles 'SO- stop be evil'
2

Enquanto escrevia um script ksh, notei que a -aopção do whencecomando interno do ksh parece não ser suportada em versões mais antigas do ksh. E isso parece ser verdade em todos os sistemas que verifiquei, incluindo Solaris, AIX, HP-UX e Linux.

Então, aqui está a solução como uma função ksh:

is_modern_ksh() {
  if whence -a whence > /dev/null 2>&1 ; then
    return 0 #success -> true
  fi
  #Else the call to `whence` failed because `-a` is not supported
  return 1 #failure -> false
}

E aqui está como usá-lo:

if is_modern_ksh ; then
  echo "You're using a MODERN version of ksh. :)"
else
  echo "You're using an OLD version of ksh. :("
fi
Sildoreth
fonte
Por que você não usa ${.sh.version}?
Cuonglm 6/15/16
@cuonglm porque eu não posso. Veja os comentários na resposta de Gilles .
Sildoreth
infelizmente o whenceno Zsh tem-a
Greg A. Woods,
@ GregA.Woods, esta função é especificamente para ksh. A definição da função entraria em .kshrc e, portanto, nem existiria para outros shells, como zsh. O zsh possui seu próprio whencecomando interno, que não está de forma alguma vinculado ao ksh ou à sua versão. Nem sei por que você gostaria de verificar se o ksh é uma versão antiga de uma instância do zsh, que é um shell completamente diferente.
Sildoreth
Há um problema com suas suposições: o Zsh é frequentemente instalado com um link para /bin/ksh, por exemplo, no Debian Linux. Agora não o uso lá (e no momento não posso alterar meu shell de login para verificar), então não sei se ele lê .kshrcou não, mas eu suspeitaria que sim.
Greg A. Woods
1

CTRL+ ALT+V

ou

ESC, CTRL+V

Em geral, eles se mostraram muito confiáveis ​​na determinação interativa da versão do KSH que você está usando, no entanto, a criação de scripts para eles se mostrou mais difícil.

Tim Kennedy
fonte
1
esse foi o único que funcionou para uma versão do AIX ksh 88f.
Jeff Schaller
1
Eu tenho a opção <kbd> ESC </kbd>, <kbd> CTRL </kbd> + <kbd> V </kbd> para funcionar, depois que corri set -o vipara definir as combinações de teclas como vi. Antes disso, ou com + o vi ou -o emacs, simplesmente não me mostrava. PD KSH v5.2.14 99/07 / 13.2 no OpenBSD 6.1
bgStack15
0

Eu acho que o problema fundamental com o uso de $ {. Sh.version} é que o ksh88 para, com um código de saída diferente de zero.

Portanto, minha solução é colocar o código que faz referência a $ {. Sh.version} em um sub-shell e, em seguida, testar se o sub-shell sai diferente de zero e possui um código no sub-shell que funcionará nas versões do o ksh em que a referência a $ {. sh.version} funciona. Embrulhando-o em uma função que é chamada por outra função que reverte o código de retorno, para que a chamada final seja verificada como verdadeira.

function is_oldksh
{
    (test -n ${.sh.version}) 2>/dev/null
}

function oldkshtest
{

    is_oldksh || return 0 && return 1
}

oldkshtest && echo "old ksh" || echo "new ksh"

Eu executei isso no AIX e Oracle Enterprise Linux 5 e 6, com ksh88, ksh93 e pdksh.

Pete

Pete
fonte
1
A moderna AT&T Ksh ainda fornece .sh.version(na verdade, KSH_VERSIONé um apelido para ele). Além disso, alguns shells, como o NetBSD sh, simplesmente param de ler após o encontro ${.sh.version}e nenhuma quantidade de redirecionamento pode mantê-los executando o script.
Greg A. Woods
0

O seguinte parece funcionar razoavelmente bem para todos os shells que testei, incluindo o antigo ksh88e e uma gama quase completa de clones Ksh comuns (embora apenas uma versão de cada), embora ainda não testei um shell Bourne original real ( e isso pode exigir a adaptação da testexpressão para versões mais antigas ....

Termo aditivo:

Agora também testei com sucesso isso com o Heirloom Bourne Shell, embora com um programa externo (e mais moderno) test.

is_attksh()
{
    # ksh93
    _sh_version=$(eval 'echo "${.sh.version}"' 2>/dev/null)
    # pdksh only
    _opt_login=$(set -o | grep login)

    test -n "${_sh_version}" -o \( -z "${_opt_login}" -a -n "${_}" -a -n "${ERRNO}" -a -n "${FCEDIT}" -a -n "${PS3}" \)
}
is_attksh && echo "AT&T Ksh${_sh_version:+: }${_sh_version:- (probably ksh88 or ksh86)}" || echo "not real ksh"

is_zsh()
{
    test -n "${ZSH_VERSION}"
}
is_zsh && echo "Zsh: ${ZSH_VERSION}" || echo "not zsh"
Greg A. Woods
fonte
Por que você deseja executar esta função para shells que não são ksh? Se você estiver executando um script no bash ou no zsh, o ksh nunca entra em jogo. Além disso, já foi estabelecido por meio de respostas de outras pessoas que ${.sh.version}não podem fazer parte da solução, porque certas versões do ksh - as versões em que a postagem original se preocupava - erro fatal nessa sintaxe.
Sildoreth
Como eu disse, a função que mostro foi testada com versões do ksh que fornecem erros "fatais", bem como versões do Ash que fazem o mesmo.
Greg A. Woods,
Os scripts que escrevo são portáteis e executados por qualquer shell capaz. Além disso, como eu disse em outros lugares, algumas pessoas não necessariamente sabem que estão usando Zsh como Ksh porque, quando digitar 'ksh', o binário Zsh será chamado (com argv [0] como "ksh").
Greg A. Woods,
Isso esclarece de onde você vem. No entanto, isso soa como um requisito irrealista. Normalmente, quando um desenvolvedor do Unix diz "portátil", eles não significam "esse código será executado em qualquer shell ", eles significam "isso será executado em qualquer sistema ". E se você precisar executar um script que foi escrito para outro shell, isso é perfeitamente legal; basta iniciar uma instância não interativa do outro shell no seu script. Trago isso à tona porque quero promover boas práticas de codificação. Se esta solução funcionar para você, ótimo. Mas eu aconselharia outras pessoas a adotar uma abordagem mais simples.
Sildoreth
1
É certo que qualquer esforço para arrastar a compatibilidade com versões anteriores para o passado é muito tolo. Eu compilei apenas versões antigas da AT&T Ksh e Unix Sh para satisfazer meu desejo pessoal de entender melhor a história e a evolução de alguns recursos e para refrescar minha memória de como as coisas eram (o que geralmente me surpreende, pois as coisas eram muitas vezes " melhor "do que eu me lembro, embora algumas vezes eles também fossem muito piores).
Greg A. Woods,