Come posso assegnare in modo conciso valori diversi a una variabile, a seconda di un'altra variabile?


20

Come posso abbreviare questo script di 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

2
Suppongo che questo sia bashcodice? O hai in mente qualche altra shell?
Freddy,

3
Cordiali saluti, in futuro, consiglierei di sostituire le informazioni personali come URL e altre cose con qualcosa di generico come "com.hello.world".
Trevor Boyd Smith,

1
@IISomeOneII Dovresti invece chiedere a CodeGolf.SE: P
mackycheese21

3
@Trevor, lo consiglierei example.org, example.netecc., Poiché questi domini sono specificamente riservati a questo scopo in RFC 2606 e non verranno mai utilizzati per entità reali.
Toby Speight,

2
@TrevorBoydSmith Secondo la raccomandazione di Toby di com.example ecc., Poiché "hello.com" è di proprietà di Google.
David Conrad,

Risposte:


61

Usa caseun'istruzione (portatile, funziona con qualsiasi shshell simile):

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

Consiglierei anche di cambiare i nomi delle variabili da tutte le lettere maiuscole (come CODE) in lettere minuscole o miste (come codeo Code). Esistono molti nomi di maiuscole che hanno significati speciali e il riutilizzo accidentale di uno di essi può causare problemi.

Altre note: la convenzione standard prevede l'invio di messaggi di errore a "errore standard" anziché "output standard"; il >&2reindirizzamento fa questo. Inoltre, se uno script (o un programma) fallisce, è meglio uscire con uno stato diverso da zero ( exit 1), quindi qualsiasi contesto chiamante può dire cosa è andato storto. E 'anche possibile utilizzare diversi status per indicare diversi problemi (vedere la sezione "codici di uscita" della della curlpagina man per un buon esempio). (Ringraziamo Stéphane Chazelas e Monty Harder per suggerimenti qui.)

Consiglio printfinvece di echo -e(e echo -n), perché è più portabile tra sistemi operativi, versioni, impostazioni, ecc. Una volta ho avuto una serie di interruzioni dei miei script perché un aggiornamento del sistema operativo includeva una versione di bash compilata con diverse opzioni, che ha cambiato il echocomportamento.

Le doppie virgolette $CODEnon sono davvero necessarie qui. La stringa in a caseè uno dei pochi contesti in cui è sicuro lasciarli fuori. Tuttavia, preferisco citare due volte i riferimenti alle variabili a meno che non ci sia un motivo specifico per non farlo, perché è difficile tenere traccia di dove è sicuro e dove non lo è, quindi è più sicuro fare solo una doppia citazione abitualmente.


5
@IISomeOneII Che conterà come *(e stampa l'errore) - il modello [aA]corrisponde a "a" o "A", ma non entrambi contemporaneamente.
Gordon Davisson,

6
Questo è esattamente il modo giusto per farlo, fino al carattere jolly alla fine reindirizzando il suo output su stderr e generando un valore di uscita diverso da zero. L'unica cosa che potrebbe essere necessario modificare è quel valore di uscita, poiché potrebbe esserci più di un errore da restituire. In uno script più grande, potrebbe esserci una sezione (forse proveniente da un altro file) che definisce i valori di uscita in readonly Exit_BadCode=1modo che possa dire exit $Exit_BadCodeinvece.
Monty Harder,

2
Se vai con un bash recente, usa case "${CODE,}" in, in modo che ognuno dei condizionali diventi semplicemente a), b)ecc.
steve

2
@MontyHarder Dipende. Se ci sono alcune centinaia di questi codici, ognuno corrispondente a una stringa, un altro approccio potrebbe essere migliore. Per l'esatto problema a portata di mano, questo è sufficiente.
Kusalananda

2
@MontyHarder Mi dispiace, avrei dovuto essere più chiaro. Con "codice" intendevo $CODE. Chiedo sempre esattamente "lo stato di uscita", mai "codice". Se lo script deve utilizzare molte centinaia di chiavi per fare riferimento alle stringhe, l'utilizzo di caseun'istruzione diventa ingombrante.
Kusalananda

19

Supponendo che tu stia utilizzando la bashversione 4.0 o successiva ...

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}

Nel codice, definisco un array associativo che contiene tutti i nomi di dominio, ciascuno associato a una singola lettera minuscola.

Alla $PNvariabile viene assegnato il nome di dominio corrispondente al $CODEvalore minuscolo ( ${CODE,,}restituisce il valore di $CODEsolo lettere minuscole trasformate) da questo array, ma se $CODEnon corrisponde a una voce valida domainnell'elenco, esce dallo script con un errore.

La ${variable:?error message}sostituzione dei parametri si espanderebbe al valore di $variable(il dominio appropriato nel codice) ma uscirebbe dallo script con il messaggio di errore se il valore è vuoto non disponibile. Non ottieni esattamente la stessa formattazione del messaggio di errore del codice, ma sostanzialmente si comporterebbe lo stesso se $CODEnon è valido:

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

Se ti interessa il conteggio dei personaggi, possiamo accorciare ulteriormente questo:

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}

Oltre all'eliminazione di newline non necessarie, ho anche rimosso com.da ciascun dominio (questo viene invece aggiunto nell'assegnazione a PN).

Si noti che tutto il codice sopra funzionerebbe anche per un valore composto da più caratteri $CODE(se esistevano chiavi con lettere minuscole per questi domainnell'array).


Se invece $CODEfosse un indice numerico (a base zero), questo semplificherebbe un po 'il codice:

CODE=0

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

Ciò renderebbe inoltre più semplice la lettura domaindell'array da un file ausiliario contenente una voce per riga:

CODE=0

readarray -t domain <domains.txt
PN=${domain[CODE]:?ERROR! CODE KOSONG, MELAKUKAN EXIT OTOMATIS}

1
@IISomeOneII declare -A domaindice solo che domaindovrebbe essere una variabile associativa ("hash").
Kusalananda

1
@Isaac Ora più distinto dal tuo. Grazie per il testa a testa.
Kusalananda

1
Sarebbe meglio usare zsh o ksh93. Per bash, avresti bisogno di una versione recente e fallirebbe per valori vuoti di $CODE.
Stéphane Chazelas,

1
@ StéphaneChazelas Sì, otterresti un messaggio di errore aggiuntivo su un indice di array errato se $CODEnon impostato o vuoto, ma dopo genererebbe comunque il messaggio di errore personalizzato corretto.
Kusalananda

1
@Kusalananda È stato pubblicato un nuovo script (POSIX valido). Senza l'errore il controllo è molto breve.
Isaac, il

11

Se la tua shell consente array, la risposta più breve dovrebbe essere come questo esempio in bash:

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

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

Ciò presuppone che $codepotrebbe essere solo a, b, c o d.
In caso contrario, aggiungi un test come:

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

Se l'input è A, funzionerà su quello script? Scusate il mio inglese cattivo
IISomeOneII

2
Sì, l'espansione ${var,}converte in minuscolo il primo carattere di ${var}. @IISomeOneII
Isaac

1
${var,}sembra essere specifico di Bash però. Penso che l'array associativo funzionerebbe anche in ksh e zsh
ilkkachu il

@ilkkachu Sì, corretto in entrambi i casi.
Isaac,

Grazie a tutti, un sacco di brave persone qui 👍
IISomeOneII

3

Prenderò questa risposta in un'altra direzione. Invece di codificare i tuoi dati nello script, inseriscili in un file di dati separato, quindi usa il codice per cercare il file:

$ 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

Separare queste preoccupazioni ha alcuni vantaggi:

  • Aggiungi e rimuovi i dati facilmente e semplicemente, senza dover aggirare la logica del codice.
  • Altri programmi possono riutilizzare i dati, come contare quante corrispondenze si trovano in un determinato sottodominio.
  • Se disponi di un vasto elenco di dati, puoi ordinarli su disco e utilizzarli lookper effettuare ricerche binarie in modo efficiente (anziché riga per riga grepo awk)

1
Se si va in questo modo, è comunque necessario disporre PNdi essere impostato sul valore corretto.
ilkkachu,

1
@ilkkachu Fair point. L'ho perso nel PO. Corretto.
vescovo

2
+1 per separare i dati dal codice.
Arp,

1

Stai usando le lettere per indicizzare i valori, se dovessi usare i numeri, diventa semplice come:

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

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

Questo è un codice shell portatile, funzionerà sulla maggior parte delle shell.
Per bash è possibile utilizzare: pn=${!code}o per bash / ksh / zsh uso: pn=${@:code:1}.

lettere

Se è necessario che le lettere dell'utente (dalla a alla z o dalla A alla Z) siano convertite in un indice:

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

In un codice più lungo per chiarire l'intento e il significato di ogni 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 è necessario convertire in valori minuscoli, utilizzare: $(( asciival & ~32 ))(accertarsi che il bit 6 del valore ascii non sia impostato).

codice di errore

L'output che lo script stampa su un errore è piuttosto lungo (e particolare).
Il modo più versatile per gestirlo è definire una funzione:

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

Quindi chiama quella funzione con i messaggi specifici di cui hai bisogno.

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

Si noti che il valore di uscita risultante è dato da exitcode(l'esempio qui è 27).

Uno script completo (con controllo degli errori) diventa quindi:

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"
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.