¿Cómo puedo asignar de manera concisa diferentes valores a una variable, dependiendo de otra variable?

20

¿Cómo puedo acortar este 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
fuente
2
Supongo que este es el bashcódigo? ¿O tienes algún otro caparazón en mente?
Freddy
3
Para su información en el futuro, recomendaría reemplazar la información personal como las URL y otras cosas con algo genérico como "com.hello.world".
Trevor Boyd Smith
1
@IISomeOneII Deberías preguntarle a CodeGolf.SE en su lugar: P
mackycheese21
3
@Trevor, recomendaría example.org, example.netetc., ya que estos dominios están específicamente reservados para este propósito en RFC 2606 y nunca se utilizarán para entidades reales.
Toby Speight
2
@TrevorBoydSmith Secunda la recomendación de Toby de com.example, etc., ya que "hello.com" es propiedad de Google.
David Conrad

Respuestas:

61

Use una casedeclaración (portátil, funciona en cualquier shtipo de shell):

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

También recomendaría cambiar los nombres de las variables de todas las letras mayúsculas (like CODE) a algo en minúsculas o mixtas (like codeo Code). Hay muchos nombres en mayúsculas que tienen significados especiales, y reutilizar uno de ellos por accidente puede causar problemas.

Otras notas: La convención estándar es enviar mensajes de error a "error estándar" en lugar de "salida estándar"; la >&2redirección hace esto. Además, si falla un script (o programa), es mejor salir con un estado distinto de cero ( exit 1), de modo que cualquier contexto de llamada pueda decir qué salió mal. También es posible utilizar diferentes estados para indicar diferentes problemas (consulte la sección "CÓDIGOS DE SALIDA" de la curlpágina del manual para ver un buen ejemplo). (Gracias a Stéphane Chazelas y Monty Harder por las sugerencias aquí).

Recomiendo en printflugar de echo -e(y echo -n), porque es más portátil entre sistemas operativos, versiones, configuraciones, etc. Una vez tuve una interrupción de mis scripts porque una actualización del sistema operativo incluía una versión de bash compilada con diferentes opciones, lo que cambió la forma en que se echocomportó.

Las comillas dobles $CODEno son realmente necesarias aquí. La cadena en a casees uno de los pocos contextos donde es seguro dejarlos fuera. Sin embargo, prefiero citar las referencias de variables a menos que haya una razón específica para no hacerlo, porque es difícil hacer un seguimiento de dónde es seguro y dónde no, por lo que es más seguro citarlas habitualmente.

Gordon Davisson
fuente
55
@IISomeOneII Eso contará como *(e imprimirá el error): el patrón [aA]coincide con "a" o "A", pero no con ambos a la vez.
Gordon Davisson
66
Esta es exactamente la forma correcta de hacerlo, hasta el comodín al final redirigiendo su salida a stderr y generando un valor de salida distinto de cero. Lo único que puede necesitar cambiar es ese valor de salida, ya que puede haber más de un error para devolver. En una secuencia de comandos más grande, puede haber una sección (quizás proveniente de otro archivo) que defina los valores de salida readonly Exit_BadCode=1para que pueda decir en su exit $Exit_BadCodelugar.
Monty Harder
2
Si va con una fiesta reciente, úsela, de case "${CODE,}" inmodo que cada uno de los condicionales se vuelva simple a), b)etc.
Steve
2
@MontyHarder Depende. Si hay algunos cientos de estos códigos, cada uno correspondiente a una cadena, entonces otro enfoque puede ser mejor. Para el problema exacto en cuestión, esto es suficiente.
Kusalananda
2
@MontyHarder Lo siento, debería haber sido más claro. Por "código" quise decir $CODE. Siempre llamo "estado de salida" exactamente eso, nunca solo "código". Si el script necesita usar cientos de claves para referirse a las cadenas, el uso de una casedeclaración se vuelve difícil de manejar.
Kusalananda
19

Suponiendo que está utilizando la bashversión 4.0 o posterior ...

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}

En el código, defino una matriz asociativa que contiene todos los nombres de dominio, cada uno asociado con una sola letra minúscula.

A la $PNvariable se le asigna el nombre de dominio correspondiente al valor en minúsculas $CODE(solo ${CODE,,}devuelve el valor $CODEconvertido en letras minúsculas) de esta matriz, pero si $CODEno corresponde a una entrada válida en la domainlista, sale del script con un error.

La ${variable:?error message}sustitución de parámetros se expandiría al valor de $variable(el dominio apropiado en el código) pero saldría del script con el mensaje de error si el valor está vacío no disponible. No obtiene exactamente el mismo formato del mensaje de error que en su código, pero esencialmente se comportaría igual si $CODEno es válido:

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

Si te importa el recuento de personajes, podemos acortar esto aún más:

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}

Además de eliminar nuevas líneas innecesarias, también he eliminado com.de cada dominio (esto se agrega en la asignación a PN).

Tenga en cuenta que todo el código anterior funcionaría incluso para un valor de varios caracteres en $CODE(si existieran claves en minúsculas para estos en la domainmatriz).


Si $CODEse tratara de un índice numérico (basado en cero), esto simplificaría un poco el 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}

Esto también facilitaría la lectura de la domainmatriz desde un archivo auxiliar que contiene una entrada por línea:

CODE=0

readarray -t domain <domains.txt
PN=${domain[CODE]:?ERROR! CODE KOSONG, MELAKUKAN EXIT OTOMATIS}
Kusalananda
fuente
1
@IISomeOneII declare -A domainsimplemente dice que domaindebería ser una variable de matriz asociativa ("hash").
Kusalananda
1
@Isaac Ahora más distinto del tuyo. Gracias por el aviso.
Kusalananda
1
Sería mejor usar zsh o ksh93. Para bash, necesitaría una versión reciente y fallaría para valores vacíos de $CODE.
Stéphane Chazelas
1
@ StéphaneChazelas Sí, recibirá un mensaje de error adicional sobre un subíndice de matriz incorrecto si no $CODEestá configurado o está vacío, pero aún así generaría el mensaje de error personalizado correcto después de eso.
Kusalananda
1
@Kusalananda Un nuevo script (POSIX válido) publicado. Sin el error, la comprobación es muy corta.
Isaac
11

Si su shell permite matrices, la respuesta más corta debería ser como este ejemplo en bash:

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

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

Eso es asumiendo que $codesolo podría ser a, b, c o d.
Si no, agregue una prueba 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
fuente
Si la entrada es A, ¿funcionará en ese script? Lo siento, mi inglés es malo
IISomeOneII
2
Sí, la expansión ${var,}convierte a minúscula el primer carácter de ${var}. @IISomeOneII
Isaac
1
${var,}Sin embargo, parece ser específico de Bash. Creo que la matriz asociativa también funcionaría en ksh y zsh
ilkkachu
@ilkkachu Sí, correcto en ambos aspectos.
Isaac
Gracias a todos, mucha gente buena aquí 👍
IISomeOneII
3

Voy a tomar esta respuesta en una dirección diferente. En lugar de codificar sus datos en el script, coloque esos datos en un archivo de datos separado, luego use el código para buscar el archivo:

$ 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

Separar estas preocupaciones tiene algunos beneficios:

  • Agregue y elimine datos de manera fácil y sencilla, sin tener que trabajar con la lógica del código.
  • Otros programas pueden reutilizar los datos, como contar cuántas coincidencias hay en un subdominio particular.
  • Si tiene una lista enorme de datos, puede ordenarlos en el disco y usarlos lookpara buscarlos binariamente de manera eficiente (en lugar de línea por línea grepo awk)
obispo
fuente
1
Si sigue este camino, aún necesita hacer arreglos para PNque se establezca en el valor correcto.
ilkkachu
1
@ilkkachu Punto justo. Me perdí eso en el OP. Corregido
obispo
2
+1 para separar datos del código.
arp
1

Está usando letras para indexar los valores, si usara números, se vuelve tan simple como:

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

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

Ese es el código de shell portátil, funcionará en la mayoría de los shells.
Para fiesta puede usar: pn=${!code}o para bash / ksh / zsh uso: pn=${@:code:1}.

letras

Si debe usar letras de usuario (de la A a la Z o de la A a la Z), deben convertirse en un í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  ))}\"

En un código más largo para aclarar la intención y el 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.

Si necesita convertir a valores en minúscula, use: $(( asciival & ~32 ))(asegúrese de que el bit 6 del valor ascii no esté establecido).

código de error

El resultado que su script imprime en un error es bastante largo (y particular).
La forma más versátil de manejarlo es definir una función:

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

Y luego llame a esa función con los mensajes específicos que necesita.

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

Tenga en cuenta que el valor de salida resultante viene dado por exitcode(el ejemplo aquí es 27).

Un script completo (con comprobación de errores) se convierte en:

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
fuente