Wie kann ich einer Variablen abhängig von einer anderen Variablen präzise unterschiedliche Werte zuweisen?

20

Wie kann ich dieses Shell-Skript kürzen?

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
quelle
2
Ich nehme an, das ist bashCode? Oder hast du eine andere Hülle im Sinn?
Freddy
3
Zukünftig würde ich empfehlen, persönliche Informationen wie URLs und andere Dinge durch etwas Generisches wie "com.hello.world" zu ersetzen.
Trevor Boyd Smith
1
@IISomeOneII Sie sollten stattdessen CodeGolf.SE fragen: P
mackycheese21
3
@ Trevor, würde ich empfehlen example.org, example.netusw., da diese Domänen in RFC 2606 speziell für diesen Zweck reserviert sind und niemals für echte Entitäten verwendet werden.
Toby Speight
2
@ TrevorBoydSmith Seconding Tobys Empfehlung von com.example etc., da "hello.com" im Besitz von Google ist.
David Conrad

Antworten:

61

Verwenden Sie eine caseAnweisung (portabel, funktioniert in einer beliebigen 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

Ich würde auch empfehlen, die Variablennamen von Großbuchstaben (wie CODE) in Klein- oder Mischbuchstaben (wie codeoder Code) zu ändern . Es gibt viele All-Caps-Namen, die eine besondere Bedeutung haben, und die versehentliche Wiederverwendung eines dieser Namen kann zu Problemen führen.

Sonstige Hinweise: Die Standardkonvention besteht darin, Fehlermeldungen an "Standardfehler" und nicht an "Standardausgabe" zu senden. Die >&2Umleitung tut dies. Wenn ein Skript (oder Programm) fehlschlägt, beenden Sie das Programm am besten mit einem Status ungleich Null ( exit 1), damit jeder aufrufende Kontext erkennen kann, was schief gelaufen ist. Es ist auch möglich, unterschiedliche Status zu verwenden, um auf unterschiedliche Probleme hinzuweisen ( ein gutes Beispiel finden Sie im Abschnitt "EXIT CODES" auf der curlManpage ). (Dank an Stéphane Chazelas und Monty Harder für Vorschläge hier.)

Ich empfehle printfanstelle von echo -e(und echo -n), weil es portabler zwischen Betriebssystemen, Versionen, Einstellungen usw. ist. Ich hatte einmal einige Probleme mit meinen Skripten, weil ein Betriebssystem-Update eine Version von bash enthielt, die mit verschiedenen Optionen kompiliert wurde, die das echoVerhalten änderten .

Die doppelten Anführungszeichen $CODEwerden hier nicht wirklich benötigt. Die Zeichenfolge in a caseist einer der wenigen Kontexte, in denen es sicher ist, sie wegzulassen. Ich ziehe es jedoch vor, Variablenreferenzen in doppelte Anführungszeichen zu setzen, es sei denn, es gibt einen bestimmten Grund, dies nicht zu tun, da es schwierig ist, den Überblick darüber zu behalten, wo sie sicher sind und wo nicht.

Gordon Davisson
quelle
5
@IISomeOneII Das zählt als *(und gibt den Fehler aus) - das Muster [aA]stimmt entweder mit "a" oder "A" überein, aber nicht mit beiden gleichzeitig.
Gordon Davisson
6
Dies ist genau der richtige Weg, bis hin zum Platzhalter am Ende, der seine Ausgabe nach stderr umleitet und einen Exit-Wert ungleich Null erzeugt. Das einzige, was möglicherweise geändert werden muss, ist der Exit-Wert, da möglicherweise mehr als ein Fehler zurückgegeben wird. In einem größeren Skript gibt es möglicherweise einen Abschnitt (der möglicherweise aus einer anderen Datei stammt), der Exit-Werte definiert, readonly Exit_BadCode=1damit er exit $Exit_BadCodestattdessen sagen kann .
Monty Harder
2
Wenn Sie mit einem kürzlichen Schlag gehen, dann verwenden Sie case "${CODE,}" in, so dass jede der Bedingungen einfach wird a), b)etc.
Steve
2
@MontyHarder Kommt drauf an. Wenn es einige Hundert dieser Codes gibt, die jeweils einer Zeichenfolge entsprechen, ist ein anderer Ansatz möglicherweise besser. Für die genaue Fragestellung reicht dies aus.
Kusalananda
2
@MontyHarder Sorry, ich hätte klarer sein sollen. Mit "Code" meinte ich $CODE. Genau das nenne ich immer "Exit Status", niemals nur "Code". Wenn das Skript Hunderte von Schlüsseln verwenden muss , um auf die Zeichenfolgen zu verweisen, wird die Verwendung einer caseAnweisung unhandlich.
Kusalananda
19

Angenommen, Sie verwenden bashRelease 4.0 oder neuer ...

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}

Im Code definiere ich ein assoziatives Array, das alle Domänennamen enthält, die jeweils einem einzelnen Kleinbuchstabenschlüssel zugeordnet sind.

Der $PNVariablen wird der Domänenname zugewiesen, der dem Wert in Kleinbuchstaben $CODEentspricht ( ${CODE,,}gibt nur den Wert $CODEin Kleinbuchstaben zurück). Wenn der Name $CODEjedoch keinem gültigen Eintrag in der domainListe entspricht, wird das Skript mit einem beendet Error.

Die ${variable:?error message}Parametersubstitution würde auf den Wert von $variable(die entsprechende Domäne im Code) erweitert, das Skript jedoch mit der Fehlermeldung verlassen, wenn der Wert leer ist, nicht verfügbar. Sie erhalten nicht genau die gleiche Formatierung der Fehlermeldung wie in Ihrem Code, sie verhält sich jedoch im Wesentlichen genauso, wenn sie $CODEungültig ist:

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

Wenn Sie sich für die Anzahl der Zeichen interessieren, können wir dies weiter verkürzen:

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}

Abgesehen vom Löschen unnötiger Zeilenumbrüche habe ich auch com.jede Domain entfernt (dies wird stattdessen in der Zuordnung zu hinzugefügt PN).

Beachten Sie, dass der gesamte obige Code auch für einen aus mehreren Zeichen bestehenden Wert in $CODE(sofern für diese im domainArray Schlüssel mit geringerem Gehäuse vorhanden sind) funktionieren würde .


Wenn $CODEes sich stattdessen um einen numerischen (nullbasierten) Index handeln würde, würde dies den Code etwas vereinfachen:

CODE=0

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

Dies würde es zusätzlich sehr einfach machen, das domainArray aus einer Hilfsdatei zu lesen, die einen Eintrag pro Zeile enthält:

CODE=0

readarray -t domain <domains.txt
PN=${domain[CODE]:?ERROR! CODE KOSONG, MELAKUKAN EXIT OTOMATIS}
Kusalananda
quelle
1
@IISomeOneII declare -A domainsagt nur, dass domaindies eine assoziative Arrayvariable ("Hash") sein soll.
Kusalananda
1
@Isaac Jetzt mehr von dir zu unterscheiden. Vielen Dank für die Köpfe hoch.
Kusalananda
1
Wäre besser, zsh oder ksh93 zu verwenden. Für bash bräuchten Sie eine aktuelle Version, und bei leeren Werten von würde dies fehlschlagen $CODE.
Stéphane Chazelas
1
@ StéphaneChazelas Ja, Sie würden eine zusätzliche Fehlermeldung über einen ungültigen Array-Index erhalten, wenn $CODEdieser nicht gesetzt oder leer wäre. Danach würde jedoch immer noch die richtige benutzerdefinierte Fehlermeldung generiert.
Kusalananda
1
@Kusalananda Ein neues (gültiges POSIX) Skript wurde veröffentlicht. Ohne den Fehler ist die Überprüfung sehr kurz.
Isaac
11

Wenn Ihre Shell Arrays zulässt, sollte die kürzeste Antwort wie folgt lauten:

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

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

Das setzt voraus, dass es $codesich nur um a, b, c oder d handeln kann.
Wenn nicht, fügen Sie einen Test wie folgt hinzu:

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
quelle
Wenn die Eingabe A ist, funktioniert sie in diesem Skript? Sorry mein Englisch schlecht
IISomeOneII
2
Ja, die Erweiterung ${var,}konvertiert das erste Zeichen von in Kleinbuchstaben ${var}. @ IISomeOneII
Isaac
1
${var,}scheint jedoch Bash-spezifisch zu sein. Ich denke, das assoziative Array würde auch in ksh und zsh
funktionieren
@ilkkachu Ja, in beiden Punkten richtig.
Isaac
Vielen Dank an alle, viele gute Leute hier 👍
IISomeOneII
3

Ich werde diese Antwort in eine andere Richtung nehmen. Anstatt Ihre Daten in das Skript zu codieren, speichern Sie diese Daten in einer separaten Datendatei und durchsuchen Sie die Datei anschließend mit Code:

$ 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

Das Trennen dieser Bedenken hat einige Vorteile:

  • Fügen Sie Daten einfach und problemlos hinzu und entfernen Sie sie, ohne die Codelogik umgehen zu müssen.
  • Andere Programme können die Daten wiederverwenden, z. B. die Anzahl der Übereinstimmungen in einer bestimmten Unterdomäne.
  • Wenn Sie eine große Liste von Daten haben, können Sie diese auf der Festplatte sortieren und lookzur effizienten binären Suche verwenden (anstatt zeilenweise grepoder awk).
Bischof
quelle
1
Wenn Sie auf diese Weise vorgehen, müssen Sie immer noch dafür sorgen PN, dass der richtige Wert eingestellt wird.
Ilkkachu
1
@ilkkachu Fairer Punkt. Das habe ich im OP verpasst. Korrigiert
Bischof
2
+1 zum Trennen von Daten vom Code.
27.
1

Sie verwenden Buchstaben, um die Werte zu indizieren. Wenn Sie Zahlen verwenden, wird dies so einfach wie:

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

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

Das ist portabler Shell-Code, der auf den meisten Shells funktioniert.
Für bash können Sie verwenden: pn=${!code}oder für bash / KSH / zsh Verwendung: pn=${@:code:1}.

Briefe

Wenn Sie Benutzerbriefe (von A bis Z oder von A bis Z) benötigen, müssen diese in einen Index konvertiert werden:

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 einem längeren Code, um die Absicht und Bedeutung jedes Teils zu verdeutlichen:

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.

Wenn Sie in Kleinbuchstaben konvertieren müssen, verwenden Sie: $(( asciival & ~32 ))(Stellen Sie sicher, dass Bit 6 des ASCII-Werts nicht gesetzt ist).

Fehlercode

Die Ausgabe, die Ihr Skript bei einem Fehler ausgibt, ist ziemlich lang (und speziell).
Der vielseitigste Weg, damit umzugehen, besteht darin, eine Funktion zu definieren:

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

Rufen Sie dann diese Funktion mit den spezifischen Nachrichten auf, die Sie benötigen.

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

Beachten Sie, dass der resultierende Exit-Wert durch gegeben ist exitcode(Beispiel hier ist 27).

Ein vollständiges Skript (mit Fehlerprüfung) wird dann:

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
quelle