Comment puis-je attribuer de manière concise différentes valeurs à une variable, selon une autre variable?

20

Comment puis-je raccourcir ce script 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
la source
2
Je suppose que c'est du bashcode? Ou avez-vous un autre obus en tête?
Freddy
3
Pour info à l'avenir, je recommanderais de remplacer les informations personnelles comme les URL et d'autres choses par quelque chose de générique comme "com.hello.world".
Trevor Boyd Smith
1
@IISomeOneII Vous devriez plutôt demander CodeGolf.SE: P
mackycheese21
3
@Trevor, je recommanderais example.org, example.netetc., car ces domaines sont spécifiquement réservés à cet effet dans la RFC 2606 et ne seront jamais utilisés pour des entités réelles.
Toby Speight
2
@TrevorBoydSmith Seconding Toby recommande com.example etc., car "hello.com" appartient à Google.
David Conrad

Réponses:

61

Utilisez une caseinstruction (portable, fonctionne dans n'importe quel shshell):

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

Je recommanderais également de changer vos noms de variable de toutes les lettres majuscules (comme CODE) en quelque chose en minuscule ou en majuscules (comme codeou Code). Il existe de nombreux noms tout en majuscules qui ont des significations spéciales, et la réutilisation de l'un d'eux par accident peut causer des problèmes.

Autres remarques: La convention standard consiste à envoyer des messages d'erreur à "erreur standard" plutôt qu'à "sortie standard"; la >&2redirection fait cela. De plus, si un script (ou un programme) échoue, il est préférable de quitter avec un état différent de zéro (exit 1 ), afin que tout contexte d'appel puisse dire ce qui s'est mal passé. Il est également possible d'utiliser différents statuts pour indiquer différents problèmes (voir la section "CODES DE SORTIE" de la curlpage de manuel pour un bon exemple). (Nous remercions Stéphane Chazelas et Monty Harder pour leurs suggestions ici.)

Je recommande printfau lieu deecho -e (et echo -n), car il est plus portable entre les systèmes d'exploitation, les versions, les paramètres, etc. J'ai eu une fois un tas de mes scripts interrompus, car une mise à jour du système d'exploitation comprenait une version de bash compilée avec différentes options, ce qui a changé la façon dont echose comportait.

Les guillemets autour $CODEne sont pas vraiment nécessaires ici. La chaîne dans a caseest l'un des rares contextes où il est sûr de les laisser de côté. Cependant, je préfère citer les références de variable entre guillemets à moins qu'il n'y ait une raison spécifique de ne pas le faire, car il est difficile de savoir où il est sûr et où il ne l'est pas, il est donc plus sûr de simplement les citer entre deux.

Gordon Davisson
la source
5
@IISomeOneII Cela comptera comme *(et affichera l'erreur) - le motif [aA]correspond à "a" ou "A", mais pas aux deux à la fois.
Gordon Davisson
6
C'est exactement la bonne façon de le faire, jusqu'au caractère générique à la fin, redirigeant sa sortie vers stderr et générant une valeur de sortie non nulle. La seule chose qui pourrait devoir changer est cette valeur de sortie, car il peut y avoir plus d'une erreur à renvoyer. Dans un script plus grand, il pourrait y avoir une section (peut-être provenant d'un autre fichier) qui définit les valeurs de sortie readonly Exit_BadCode=1afin qu'il puisse dire à la exit $Exit_BadCodeplace.
Monty Harder
2
Si vous utilisez un bash récent, utilisez-le case "${CODE,}" in, de sorte que chacune des conditions devienne simplement a), b)etc.
Steve
2
@MontyHarder Cela dépend. S'il existe quelques centaines de ces codes, chacun correspondant à une chaîne, une autre approche peut être meilleure. Pour le problème exact en question, cela suffit.
Kusalananda
2
@MontyHarder Désolé, j'aurais dû être plus clair. Par "code", je voulais dire $CODE. J'appelle toujours "statut de sortie" exactement cela, jamais seulement "code". Si le script doit utiliser plusieurs centaines de clés pour faire référence aux chaînes, l'utilisation d'une caseinstruction devient compliquée.
Kusalananda
19

En supposant que vous utilisez la bashversion 4.0 ou plus récente ...

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}

Dans le code, je définis un tableau associatif contenant tous les noms de domaine, chacun associé à une clé minuscule à une seule lettre.

La $PNvariable se voit attribuer le nom de domaine correspondant à la $CODEvaleur en minuscules ( ${CODE,,}renvoie la valeur de $CODEtransformée en lettres minuscules uniquement) à partir de ce tableau, mais si le $CODEne correspond pas à une entrée valide dans la domainliste, il quitte le script avec un Erreur.

La ${variable:?error message}substitution de paramètres s'étendrait à la valeur de $variable(le domaine approprié dans le code) mais quitterait le script avec le message d'erreur si la valeur est vide non disponible. Vous n'obtenez pas exactement le même formatage du message d'erreur que dans votre code, mais il se comporterait essentiellement de la même manière s'il $CODEn'est pas valide:

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

Si vous vous souciez du nombre de caractères, nous pouvons le raccourcir davantage:

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}

Outre la suppression des sauts de ligne inutiles, j'ai également supprimé com.de chaque domaine (cela est ajouté à la place dans l'affectation à PN).

Notez que tout le code ci-dessus fonctionnerait même pour une valeur à plusieurs caractères dans $CODE(si des clés en bas de casse existaient pour celles-ci dans le domaintableau).


Si $CODEc'était un index numérique (basé sur zéro) à la place, cela simplifierait un peu le code:

CODE=0

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

De plus, cela faciliterait la lecture du domaintableau à partir d'un fichier auxiliaire contenant une entrée par ligne:

CODE=0

readarray -t domain <domains.txt
PN=${domain[CODE]:?ERROR! CODE KOSONG, MELAKUKAN EXIT OTOMATIS}
Kusalananda
la source
1
@IISomeOneII declare -A domaindit simplement que cela domaindevrait être une variable de tableau associatif ("hachage").
Kusalananda
1
@Isaac Désormais plus distinct du vôtre. Merci pour l'information.
Kusalananda
1
Il serait préférable d'utiliser zsh ou ksh93. Pour bash, vous auriez besoin d'une version récente et elle échouerait pour les valeurs vides de $CODE.
Stéphane Chazelas
1
@ StéphaneChazelas Oui, vous obtiendrez un message d'erreur supplémentaire à propos d'un mauvais indice de tableau s'il $CODEn'était pas défini ou vide, mais il générerait toujours le message d'erreur personnalisé correct après cela.
Kusalananda
1
@Kusalananda Un nouveau script (POSIX valide) publié. Sans la vérification des erreurs, c'est très court.
Isaac
11

Si votre shell autorise les tableaux, la réponse la plus courte devrait être comme cet exemple dans bash:

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

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

Cela suppose que cela $codene peut être que a, b, c ou d.
Sinon, ajoutez un test comme:

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
la source
Si l'entrée est A, cela fonctionnera-t-il sur ce script? Désolé mon mauvais anglais
IISomeOneII
2
Oui, l'extension ${var,}convertit en minuscules le premier caractère de ${var}. @IISomeOneII
Isaac
1
${var,}semble être spécifique à Bash. Je pense que le tableau associatif fonctionnerait aussi dans ksh et zsh
ilkkachu
@ilkkachu Oui, correct dans les deux cas.
Isaac
Thx tout le monde, beaucoup de bonnes personnes ici 👍
IISomeOneII
3

Je vais prendre cette réponse dans une direction différente. Plutôt que de coder vos données dans le script, placez ces données dans un fichier de données séparé, puis utilisez le code pour rechercher le fichier:

$ 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

La séparation de ces préoccupations présente quelques avantages:

  • Ajoutez et supprimez des données facilement et simplement, sans avoir à contourner la logique du code.
  • D'autres programmes peuvent réutiliser les données, comme compter le nombre de correspondances dans un sous-domaine particulier.
  • Si vous avez une énorme liste de données, vous pouvez les trier sur le disque et les utiliser lookpour effectuer des recherches binaires efficaces (plutôt que ligne par ligne grepou awk)
évêque
la source
1
Si vous procédez de cette façon, vous devez tout de même faire en sorte que PNla valeur correcte soit définie.
ilkkachu le
1
@ilkkachu Fair point. J'ai manqué ça au PO. Corrigée.
évêque
2
+1 pour séparer les données du code.
arp
1

Vous utilisez des lettres pour indexer les valeurs, si vous utilisiez des nombres, cela devient aussi simple que:

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

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

C'est un code shell portable qui fonctionnera sur la plupart des shells.
Pour bash vous pouvez utiliser: pn=${!code}ou pour une utilisation bash / ksh / zsh: pn=${@:code:1}.

des lettres

Si vous devez utiliser des lettres (de a à z ou de A à Z), elles doivent être converties en index:

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  ))}\"

Dans un code plus long pour clarifier l'intention et la signification de chaque partie:

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 vous devez convertir des valeurs en minuscules, utilisez: $(( asciival & ~32 ))(assurez-vous que le bit 6 de la valeur ascii n'est pas défini).

code d'erreur

La sortie que votre script imprime sur une erreur est assez longue (et particulière).
La façon la plus polyvalente de la gérer est de définir une fonction:

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

Ensuite, appelez cette fonction avec le ou les messages spécifiques dont vous avez besoin.

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

Notez que la valeur de sortie résultante est donnée par exitcode(l'exemple ici est 27).

Un script complet (avec vérification d'erreur) devient alors:

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
la source