Jak mogę zwięźle przypisać różne wartości do zmiennej, w zależności od innej zmiennej?

20

Jak mogę skrócić ten skrypt powłoki?

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
źródło
2
Przypuszczam, że to jest bashkod? Czy masz na myśli jakąś inną powłokę?
Freddy
3
Do Twojej wiadomości w przyszłości zaleciłbym zastąpienie danych osobowych, takich jak adresy URL i inne rzeczy, czymś ogólnym, takim jak „com.hello.world”.
Trevor Boyd Smith
1
@IISomeOneII Zamiast tego powinieneś poprosić CodeGolf.SE: P
mackycheese21
3
@Trevor, polecam example.org, example.netetc, ponieważ te domeny są zarezerwowane na ten cel w RFC 2606 i nigdy nie zostaną wykorzystane do rzeczywistych podmiotów.
Toby Speight
2
@ TrevorBoydSmith Seconding zalecenie Toby dotyczące com.example itp., Ponieważ „hello.com” jest własnością Google.
David Conrad

Odpowiedzi:

61

Użyj caseinstrukcji (przenośna, działa w dowolnej shpowłoce):

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

Polecam również zmianę nazw zmiennych ze wszystkich wielkich liter (jak CODE) na małe lub małe litery (jak codelub Code). Istnieje wiele nazw wieloznacznych, które mają specjalne znaczenie, a ponowne użycie jednego z nich przez przypadek może spowodować problemy.

Inne uwagi: standardowa konwencja polega na wysyłaniu komunikatów o błędach do „standardowego błędu” zamiast do „standardowego wyjścia”; >&2przekierowanie to robi. Ponadto, jeśli skrypt (lub program) zawiedzie, najlepiej wyjść ze stanu niezerowego ( exit 1), aby każdy kontekst wywołujący mógł stwierdzić, co poszło nie tak. Możliwe jest także użycie różnych statusów w celu wskazania różnych problemów ( dobry przykład znajduje się w sekcji „KODY WYJŚCIA” na curlstronie podręcznika ). (Podziękowania dla Stéphane Chazelas i Monty Harder za sugestie tutaj.)

Polecam printfzamiast echo -e(i echo -n), ponieważ jest bardziej przenośny między systemami operacyjnymi, wersjami, ustawieniami itp. Kiedyś miałem pęk moich skryptów, ponieważ aktualizacja systemu operacyjnego zawierała wersję bash skompilowaną z różnymi opcjami, które zmieniły sposób echozachowania.

Podwójne cudzysłowy $CODEnie są tutaj tak naprawdę potrzebne. Ciąg w a casejest jednym z niewielu kontekstów, w których można je bezpiecznie pominąć. Wolę jednak podwójnie cytować odniesienia do zmiennych, chyba że istnieje konkretny powód, aby tego nie robić, ponieważ trudno jest śledzić, gdzie jest bezpiecznie, a gdzie nie, więc bezpieczniej jest zwyczajnie podwójnie cytować.

Gordon Davisson
źródło
5
@IISomeOneII To będzie się liczyć jako *(i wydrukuje błąd) - wzorzec [aA]pasuje do „a” lub „A”, ale nie do obu naraz.
Gordon Davisson
6
Jest to dokładnie właściwy sposób, aby to zrobić, aż do znaku wieloznacznego na końcu przekierowując jego wyjście do stderr i generując niezerową wartość wyjściową. Jedyne, co może wymagać zmiany, to wartość wyjściowa, ponieważ może wystąpić więcej niż jeden błąd do zwrócenia. W większym skrypcie może istnieć sekcja (być może pochodząca z innego pliku), która definiuje wartości wyjściowe, readonly Exit_BadCode=1aby exit $Exit_BadCodezamiast tego mogła powiedzieć .
Monty Harder
2
Jeśli idzie o niedawnym bash, a następnie użyć case "${CODE,}" in, tak że każdy z warunkowych staje się po prostu a), b)itd.
steve
2
@MontyHarder To zależy. Jeśli istnieje kilkaset tych kodów, z których każdy odpowiada ciągowi znaków, lepsze może być inne podejście. W przypadku konkretnego problemu wystarczy.
Kusalananda
2
@MontyHarder Przepraszam, powinienem był być jaśniejszy. Miałem na myśli „kod” $CODE. Zawsze nazywam „status wyjścia” dokładnie tak, nigdy nie „kod”. Jeśli skrypt wymaga użycia setek kluczy w celu odniesienia do ciągów, użycie caseinstrukcji staje się niewygodne.
Kusalananda
19

Zakładając, że używasz bashwersji 4.0 lub nowszej ...

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}

W kodzie definiuję tablicę asocjacyjną zawierającą wszystkie nazwy domen, z których każda jest powiązana z jednym klawiszem z małą literą.

$PNZmienna jest przypisana nazwę domeny odpowiadającą dolnym bocznym $CODEwartości ( ${CODE,,}zwraca wartość $CODEzmieniła się małymi literami tylko) z tej tablicy, ale jeśli $CODEnie odpowiada ważnego wpisu w domainwykazie, to wychodzi skrypt ze związkiem błąd.

${variable:?error message}Podstawienie parametr byłoby rozszerzyć do wartości $variable(odpowiedniej domeny w kodzie), ale byłoby wyjść skryptu z komunikatem o błędzie, jeśli wartość nie jest pusta dostępny. Nie dostaniesz dokładnie to samo formatowanie komunikatu o błędzie, jak w kodzie, ale to w zasadzie zachowują się tak samo, jeśli $CODEjest nieprawidłowy:

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

Jeśli zależy Ci na liczbie postaci, możemy to jeszcze skrócić:

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}

Oprócz usuwania niepotrzebnych nowych linii, usunąłem również com.z każdej domeny (jest to zamiast tego dodane w przypisaniu do PN).

Zauważ, że cały powyższy kod działałby nawet dla wartości wieloznakowej w $CODE(jeśli istniałyby dla nich małe litery w domaintablicy).


Jeśli $CODEzamiast tego byłby indeks numeryczny (zero), to nieco uprościłoby kod:

CODE=0

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

Ułatwi to dodatkowo odczyt domaintablicy z pliku pomocniczego zawierającego jeden wpis w wierszu:

CODE=0

readarray -t domain <domains.txt
PN=${domain[CODE]:?ERROR! CODE KOSONG, MELAKUKAN EXIT OTOMATIS}
Kusalananda
źródło
1
@IISomeOneII declare -A domainmówi tylko, że domainpowinna to być zmienna tablicy asocjacyjnej („hash”).
Kusalananda
1
@Isaac Teraz bardziej różni się od twojego. Dzięki za heads-upy.
Kusalananda
1
Lepiej byłoby użyć zsh lub ksh93. Do bash potrzebujesz najnowszej wersji i nie powiodłaby się dla pustych wartości $CODE.
Stéphane Chazelas
1
@ StéphaneChazelas Tak, pojawiłby się jeden dodatkowy komunikat o błędzie dotyczący złego indeksu tablicy, jeśli $CODEbyłby rozbrojony lub pusty, ale nadal generowałby poprawny niestandardowy komunikat o błędzie.
Kusalananda
1
@Kusalananda Opublikowano nowy (prawidłowy POSIX) skrypt. Bez błędu sprawdzanie jest bardzo krótkie.
Izaak
11

Jeśli twoja powłoka zezwala na tablice, najkrótsza odpowiedź powinna wyglądać następująco:

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

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

Zakłada się, że $codemoże to być tylko a, b, c lub d.
Jeśli nie, dodaj test:

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
Izaak
źródło
Jeśli dane wejściowe to A, czy będzie działać na tym skrypcie? Przepraszam, mój angielski źle
IISomeOneII
2
Tak, rozszerzenie ${var,}zamienia na małe litery pierwszego znaku ${var}. @IISomeOneII
Isaac
1
${var,}wydaje się jednak być specyficzny dla Bash. Myślę, że tablica asocjacyjna również działałaby w ksh i zsh
ilkkachu
@ilkkachu Tak, popraw w obu liczbach.
Izaak
Dzięki wszystkim, mnóstwo dobrych ludzi tutaj 👍
IISomeOneII
3

Przyjmę tę odpowiedź w innym kierunku. Zamiast kodować dane w skrypcie, umieść te dane w osobnym pliku danych, a następnie użyj kodu, aby wyszukać plik:

$ 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

Rozdzielenie tych obaw ma kilka zalet:

  • Dodawaj i usuwaj dane łatwo i łatwo, bez konieczności obejścia logiki kodu.
  • Inne programy mogą ponownie wykorzystywać dane, na przykład licząc, ile dopasowań znajduje się w określonej subdomenie.
  • Jeśli masz ogromną listę danych, możesz posortować je na dysku i użyć lookdo wydajnego wyszukiwania binarnego (zamiast linii po linii greplub awk)
biskup
źródło
1
Jeśli pójdziesz tą drogą, nadal musisz PNustawić poprawną wartość.
ilkkachu
1
@ilkkachu Fair point. Brakowało mi tego w OP. Poprawione
biskup
2
+1 za oddzielenie danych od kodu.
arp
1

Używasz liter do indeksowania wartości, jeśli użyjesz liczb, stanie się to tak proste, jak:

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

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

To przenośny kod powłoki, będzie działał na większości powłok.
Dla bash można użyć: pn=${!code}albo za bash / ksh / zsh użycia: pn=${@:code:1}.

listy

Jeśli musisz użyć liter użytkownika (od a do z lub od A do Z), muszą one zostać przekonwertowane na indeks:

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

W dłuższym kodzie, aby wyjaśnić intencję i znaczenie każdej części:

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.

Jeśli chcesz przekonwertować na małe litery, użyj: $(( asciival & ~32 ))(upewnij się, że bit 6 wartości ascii jest wyłączony).

Kod błędu

Dane wyjściowe, które skrypt wyświetla na błędzie, są dość długie (i szczególne).
Najbardziej uniwersalnym sposobem radzenia sobie z tym jest zdefiniowanie funkcji:

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

A następnie wywołaj tę funkcję z konkretnymi potrzebnymi komunikatami.

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

Zauważ, że wynikowa wartość wyjścia jest podawana przez exitcode(przykład tutaj to 27).

Pełny skrypt (z kontrolą błędów) staje się:

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"
Izaak
źródło