Como atribuir concisos diferentes valores a uma variável, dependendo de outra variável?

20

Como posso encurtar esse script de shell?

CODE="A"

if test "$CODE" = "A"
then
 PN="com.tencent.ig"
elif test "$CODE" = "a"
 then
 PN="com.tencent.ig"
elif test "$CODE" = "B"
 then
 PN="com.vng.pubgmobile"
elif test "$CODE" = "b"
 then
 PN="com.vng.pubgmobile"
elif test "$CODE" = "C"
 then
 PN="com.pubg.krmobile"
elif test "$CODE" = "c"
 then
 PN="com.pubg.krmobile"
elif test "$CODE" = "D"
 then
 PN="com.rekoo.pubgm"
elif test "$CODE" = "d"
 then
 PN="com.rekoo.pubgm"
else
 echo -e "\a\t ERROR!"
 echo -e "\a\t CODE KOSONG"
 echo -e "\a\t MELAKUKAN EXIT OTOMATIS"
 exit
fi
IISomeOneII
fonte
2
Suponho que isso seja bashcódigo? Ou você tem alguma outra concha em mente?
Freddy
3
Para sua informação, no futuro, eu recomendaria substituir informações pessoais como URLs e outras coisas por algo genérico como "com.hello.world".
Trevor Boyd Smith
11
@IISomeOneII Você deveria perguntar ao CodeGolf.SE: P
mackycheese21
3
@ Trevev, eu recomendaria example.org, example.netetc, pois esses domínios são reservados especificamente para esse fim na RFC 2606 e nunca serão usados ​​para entidades reais.
Toby Speight
2
@TrevorBoydSmith Seconding Toby A recomendação de com.example etc. de Toby, já que "hello.com" é de propriedade do Google.
David Conrad

Respostas:

61

Use uma casedeclaração (portátil, funciona em qualquer shshell semelhante):

case "$CODE" in
    [aA] ) PN="com.tencent.ig" ;;
    [bB] ) PN="com.vng.pubgmobile" ;;
    [cC] ) PN="com.pubg.krmobile" ;;
    [dD] ) PN="com.rekoo.pubgm" ;;
    * ) printf '\a\t%s\n' 'ERROR!' 'CODE KOSONG' 'MELAKUKAN EXIT OTOMATIS' >&2
        exit 1 ;;
esac

Também recomendo alterar os nomes das variáveis ​​de todas as letras maiúsculas (como CODE) para algo em letras minúsculas ou mistas (como codeou Code). Existem muitos nomes em maiúsculas com significados especiais e a reutilização de um deles por acidente pode causar problemas.

Outras notas: A convenção padrão é enviar mensagens de erro para "erro padrão" em vez de "saída padrão"; o >&2redirecionamento faz isso. Além disso, se um script (ou programa) falhar, é melhor sair com um status diferente de zero ( exit 1), para que qualquer contexto de chamada possa dizer o que deu errado. Também é possível usar diferentes status para indicar diferentes problemas (consulte a secção "códigos de saída" da curlpágina do homem para um bom exemplo). (Agradecemos a Stéphane Chazelas e Monty Harder pelas sugestões aqui.)

Eu recomendo, em printfvez de echo -e(e echo -n), porque é mais portátil entre sistemas operacionais, versões, configurações, etc. Certa vez, tive um monte de scripts quebrados porque uma atualização do sistema operacional incluía uma versão do bash compilada com opções diferentes, que mudavam o echocomportamento.

As aspas duplas $CODEnão são realmente necessárias aqui. A string em a caseé um dos poucos contextos em que é seguro deixá-los fora. No entanto, prefiro citar duas vezes as referências de variável, a menos que haja um motivo específico para não fazê-lo, porque é difícil acompanhar onde é seguro e onde não é, por isso é mais seguro citá-las habitualmente.

Gordon Davisson
fonte
5
@IISomeOneII Isso contará como *(e imprimirá o erro) - o padrão [aA]corresponde a "a" ou "A", mas não os dois ao mesmo tempo.
Gordon Davisson
6
Essa é exatamente a maneira correta de fazer isso, até o curinga no final, redirecionando sua saída para stderr e gerando um valor de saída diferente de zero. A única coisa que pode precisar mudar é esse valor de saída, pois pode haver mais de um erro para retornar. Em um script maior, pode haver uma seção (talvez originada de outro arquivo) que define os valores de saída readonly Exit_BadCode=1para que possa dizer em exit $Exit_BadCodevez disso.
Monty Harder
2
Se indo com uma festa recente, em seguida, usar case "${CODE,}" in, de modo que cada uma das condicionais torna-se simplesmente a), b)etc.
steve
2
@MontyHarder Depende. Se houver algumas centenas desses códigos, cada um correspondente a uma string, outra abordagem poderá ser melhor. Para o problema exato em questão, isso é suficiente.
Kusalananda
2
@ MontyHarder Desculpe, eu deveria ter sido mais claro. Por "código" eu quis dizer $CODE. Eu sempre chamo "status de saída" exatamente isso, nunca apenas "código". Se o script precisar usar muitas centenas de chaves para se referir às strings, o uso de uma caseinstrução se tornará difícil.
Kusalananda
19

Supondo que você esteja usando a bashversão 4.0 ou mais recente ...

CODE=A

declare -A domain

domain=(
   [a]=com.tencent.ig
   [b]=com.vng.pubgmobile
   [c]=com.pubg.krmobile
   [d]=com.rekoo.pubgm
)

PN=${domain[${CODE,,}]:?ERROR! CODE KOSONG, MELAKUKAN EXIT OTOMATIS}

No código, defino uma matriz associativa contendo todos os nomes de domínio, cada um associado a uma única letra minúscula.

A $PNvariável é atribuído o nome de domínio correspondente ao encaixotado menor $CODEvalor ( ${CODE,,}retorna o valor de $CODEse transformou em letras minúsculas apenas) a partir desta matriz, mas se a $CODEnão corresponder a uma entrada válida na domainlista, ele sai do script com um erro.

A ${variable:?error message}substituição do parâmetro seria expandida para o valor de $variable(o domínio apropriado no código), mas sairia do script com a mensagem de erro se o valor estiver vazio não estiver disponível. Você não recebe exatamente a mesma formatação da mensagem de erro que no seu código, mas ela essencialmente se comportaria da mesma forma se $CODEfor inválida:

$ bash script.sh
script.sh: line 12: domain[${CODE,,}]: ERROR! CODE KOSONG, MELAKUKAN EXIT OTOMATIS

Se você se importa com a contagem de caracteres, podemos reduzir ainda mais isso:

CODE=A
declare -A domain=( [a]=tencent.ig [b]=vng.pubgmobile [c]=pubg.krmobile [d]=rekoo.pubgm )
PN=com.${domain[${CODE,,}]:?ERROR! CODE KOSONG, MELAKUKAN EXIT OTOMATIS}

Além de excluir novas linhas desnecessárias, também removi com.de cada domínio (isso foi adicionado na atribuição a PN).

Observe que todo o código acima funcionaria mesmo para um valor de vários caracteres $CODE(se existirem chaves de caixa baixa para elas na domainmatriz).


Se $CODEfosse um índice numérico (baseado em zero), isso simplificaria um pouco o código:

CODE=0

domain=( com.tencent.ig com.vng.pubgmobile com.pubg.krmobile com.rekoo.pubgm )
PN=${domain[CODE]:?ERROR! CODE KOSONG, MELAKUKAN EXIT OTOMATIS}

Além disso, seria muito fácil ler a domainmatriz de um arquivo auxiliar contendo uma entrada por linha:

CODE=0

readarray -t domain <domains.txt
PN=${domain[CODE]:?ERROR! CODE KOSONG, MELAKUKAN EXIT OTOMATIS}
Kusalananda
fonte
11
O @IISomeOneII declare -A domainapenas diz que domaindeve ser uma variável de matriz associativa ("hash").
Kusalananda
11
@ Isaac Agora, mais distinto do seu. Obrigado pela atenção.
Kusalananda
11
Seria melhor usar zsh ou ksh93. Para o bash, você precisaria de uma versão recente e falharia com valores vazios de $CODE.
Stéphane Chazelas
11
@ StéphaneChazelas Sim, você receberia uma mensagem de erro extra sobre um subscrito incorreto da matriz, se $CODEnão estivesse definido ou vazio, mas ainda assim geraria a mensagem de erro personalizada correta depois disso.
Kusalananda
11
@Kusalananda Um novo script (POSIX válido) publicado. Sem a verificação de erros, é muito curto.
Isaac
11

Se seu shell permitir matrizes, a resposta mais curta deve ser como este exemplo no bash:

declare -A site
site=( [a]=com.tencent.ig [b]=com.vng.pubgmobile [c]=com.pubg.krmobile [d]=com.rekoo.pubgm )

pn=${site[${code,}]}

Isso pressupõe que $codesó poderia ser a, b, c ou d.
Caso contrário, adicione um teste como:

case ${site,} in
    a|b|c|d)        pn=${site[${code,}]};;
    *)              pn="default site"
                    printf '\a\t %s\n' 'ERROR!' 'CODE KOSONG' 'MELAKUKAN EXIT OTOMATIS'
                    exit 1
                    ;;
esac
Isaac
fonte
Se a entrada for A, ela funcionará nesse script? Desculpe meu inglês ruim
IISomeOneII
2
Sim, a expansão é ${var,}convertida em minúscula o primeiro caractere de ${var}. @IISomeOneII
Isaac
11
${var,}parece ser específico do Bash. Eu acho que a matriz associativa também funcionaria no ksh e no zsh
ilkkachu
@ilkkachu Sim, correto em ambas as contagens.
Isaac
Thx pessoal, muitas pessoas boas aqui IIS
IISomeOneII
3

Vou levar esta resposta para uma direção diferente. Em vez de codificar seus dados no script, coloque esses dados em um arquivo de dados separado e use o código para pesquisar no arquivo:

$ cat names.cfg 
a com.tencent.ig
b com.vng.pubgmobile
c com.pubg.krmobile
d com.rekoo.pubgm

$ cat lookup.sh
PN=$(awk -v code="${1:-}" 'tolower($1) == tolower(code) { print $2; }' names.cfg)
if [ -z "${PN}" ]; then
  printf '\a\t%s\n' 'ERROR!' 'CODE KOSONG' 'MELAKUKAN EXIT OTOMATIS' >&2
  exit 1
fi
echo "${PN}"

$ bash lookup.sh A
com.tencent.ig
$ bash lookup.sh a
com.tencent.ig
$ bash lookup.sh x
    ERROR!
    CODE KOSONG
    MELAKUKAN EXIT OTOMATIS

A separação dessas preocupações traz alguns benefícios:

  • Adicione e remova dados de maneira fácil e simples, sem ter que contornar a lógica do código.
  • Outros programas podem reutilizar os dados, como contar quantas correspondências existem em um subdomínio específico.
  • Se você tiver uma lista enorme de dados, poderá classificá-los no disco e usá look-los para pesquisar com eficiência os binários (em vez de linha por linha grepou awk)
bispo
fonte
11
Se você seguir esse caminho, ainda precisará organizar PNo valor correto.
ilkkachu 26/07
11
@ilkkachu Fair point. Eu perdi isso no OP. Corrigido.
bispo
2
+1 para separar dados do código.
arp
1

Você está usando letras para indexar os valores; se você usar números, torna-se tão simples quanto:

code=1
set -- com.tencent.ig com.vng.pubgmobile com.pubg.krmobile com.rekoo.pubgm

eval pn\=\${"$code"}

Esse código shell portátil funciona na maioria dos shells.
Para a festança você pode usar: pn=${!code}ou para bash / ksh / uso zsh: pn=${@:code:1}.

cartas

Se você precisar usar letras (de a a z ou A a Z), elas deverão ser convertidas em um índice:

code=a                              # or A, B, C, ... etc.
set -- com.tencent.ig com.vng.pubgmobile com.pubg.krmobile com.rekoo.pubgm
eval pn\=\"\${$(( ($(printf '%d' "'$code")|32)-96  ))}\"

Em um código mais longo para esclarecer a intenção e o significado de cada parte:

code=A

set -- com.tencent.ig com.vng.pubgmobile com.pubg.krmobile com.rekoo.pubgm

asciival=$(( $(printf '%d' "'$code") ))      # byte value of the ASCII letter.
upperval=$(( asciival |  32 ))               # shift to uppercase.
indexval=$(( upperval -  96 ))               # convert to an index from a=1.
eval arg\=\"\$\{$indexval\}\"                # the argument at such index.

Se você precisar converter para valores em minúsculas, use: $(( asciival & ~32 ))(verifique se o bit 6 do valor ascii está desativado).

Erro de código

A saída que seu script imprime com um erro é bastante longa (e específica).
A maneira mais versátil de lidar com isso é definir uma função:

errorcode(){ exitcode=$1; shift; printf '\a\t %s\n' "$@"; exit "$exitcode"; }

E, em seguida, chame essa função com as mensagens específicas necessárias.

errorcode 27  "ERROR!" "CODE KOSONG" "MELAKUKAN EXIT OTOMATIS"

Observe que o valor de saída resultante é fornecido por exitcode(exemplo aqui é 27).

Um script completo (com verificação de erro) se torna:

errorcode(){ exitcode=$1; shift; printf '\a\t %s\n' "$@"; exit "$exitcode"; }

code=${1:-A}

case "$code" in 
    [a-d]|[A-D]) : ;;
    *)           errorcode 27  "ERROR!" "CODE KOSONG" "MELAKUKAN EXIT OTOMATIS" ;;
esac

set -- com.tencent.ig com.vng.pubgmobile com.pubg.krmobile com.rekoo.pubgm
eval pn\=\"\${$(( ($(printf '%d' "'$code") & ~32) - 64  ))}\"

printf 'Code=%s Argument=%s\n' "$code" "$pn"
Isaac
fonte