Bagaimana saya dapat secara ringkas memberikan nilai yang berbeda untuk suatu variabel, tergantung pada variabel lain?

20

Bagaimana saya dapat mempersingkat skrip shell ini?

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
sumber
2
Saya kira ini bashkode? Atau apakah Anda memiliki shell lain dalam pikiran?
Freddy
3
FYI di masa depan, saya akan merekomendasikan mengganti informasi pribadi seperti URL dan hal-hal lain dengan sesuatu yang generik seperti "com.hello.world".
Trevor Boyd Smith
1
@IISomeOneII Anda seharusnya bertanya pada CodeGolf.SE: P
mackycheese21
3
@ Trevor, saya sarankan example.org, example.netdll, karena domain ini secara khusus dicadangkan untuk tujuan ini di RFC 2606 dan tidak akan pernah digunakan untuk entitas nyata.
Toby Speight
2
@TrevorBoydSmith Seconding Toby merekomendasikan com.example dll, karena "hello.com" dimiliki oleh Google.
David Conrad

Jawaban:

61

Gunakan casepernyataan (portabel, bekerja di shshell-like):

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

Saya juga merekomendasikan mengubah nama variabel Anda dari semua huruf besar (seperti CODE) ke huruf kecil atau campuran (suka codeatau Code). Ada banyak nama all-caps yang memiliki arti khusus, dan menggunakan kembali salah satunya secara tidak sengaja dapat menyebabkan masalah.

Catatan lain: Konvensi standar adalah untuk mengirim pesan kesalahan ke "kesalahan standar" daripada "output standar"; yang >&2redirect melakukan hal ini. Selain itu, jika skrip (atau program) gagal, sebaiknya keluar dengan status bukan nol ( exit 1), sehingga konteks panggilan apa pun dapat mengetahui apa yang salah. Ini juga mungkin untuk menggunakan status yang berbeda untuk menunjukkan masalah yang berbeda (lihat "KODE EXIT" bagian dari satu curlhalaman manual untuk contoh yang baik). (Penghargaan untuk Stéphane Chazelas dan Monty Harder untuk saran di sini.)

Saya sarankan printfdaripada echo -e(dan echo -n), karena ini lebih portabel antara OS, versi, pengaturan, dll. Saya pernah punya banyak skrip saya karena pembaruan OS termasuk versi bash yang dikompilasi dengan berbagai opsi, yang mengubah cara echoberperilaku.

Kutipan ganda di sekitar $CODEtidak benar-benar diperlukan di sini. String dalam a caseadalah salah satu dari beberapa konteks di mana aman untuk membiarkannya. Namun, saya lebih suka untuk mengutip variabel referensi kecuali ada alasan khusus untuk tidak melakukannya, karena sulit untuk melacak di mana itu aman dan di mana tidak, jadi lebih aman untuk hanya membiasakan mengutipnya secara umum.

Gordon Davisson
sumber
5
@IISomeOneII Itu akan dihitung sebagai *(dan mencetak kesalahan) - polanya [aA]cocok dengan "a" atau "A", tetapi tidak keduanya sekaligus.
Gordon Davisson
6
Ini persis cara yang tepat untuk melakukannya, sampai ke wildcard di akhir mengarahkan outputnya ke stderr dan menghasilkan nilai keluar yang tidak nol. Satu-satunya hal yang mungkin perlu diubah adalah nilai keluar itu, karena mungkin ada lebih dari satu kesalahan untuk kembali. Dalam skrip yang lebih besar, mungkin ada bagian (mungkin bersumber dari file lain) yang mendefinisikan katup keluar readonly Exit_BadCode=1sehingga bisa dikatakan exit $Exit_BadCodesebaliknya.
Monty Harder
2
Jika menggunakan bash baru-baru ini, gunakan case "${CODE,}" in, sehingga masing-masing kondisional menjadi sederhana a), b)dll.
steve
2
@MontyHarder Tergantung. Jika ada beberapa ratus kode ini, masing-masing sesuai dengan sebuah string, maka pendekatan lain mungkin lebih baik. Untuk masalah tepat yang dihadapi, ini sudah cukup.
Kusalananda
2
@MontyHarder Maaf, saya seharusnya lebih jelas. Yang saya maksud dengan "kode" $CODE. Saya selalu memanggil "status keluar" persis seperti itu, tidak pernah hanya "kode". Jika skrip perlu menggunakan ratusan kunci untuk merujuk ke string, menggunakan casepernyataan menjadi sulit.
Kusalananda
19

Dengan asumsi Anda menggunakan bashrilis 4.0 atau lebih baru ...

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}

Dalam kode tersebut, saya mendefinisikan array asosiatif yang berisi semua nama domain, masing-masing terkait dengan satu huruf kecil.

The $PNvariabel ditugaskan nama domain yang sesuai dengan rendah-cased $CODEnilai ( ${CODE,,}return nilai $CODEberubah menjadi huruf kecil saja) dari array ini, tetapi jika $CODEtidak sesuai dengan entri yang valid dalam domaindaftar, ia keluar script dengan kesalahan.

The ${variable:?error message}substitusi parameter akan memperluas untuk nilai $variable(domain yang sesuai dalam kode) tapi akan keluar script dengan pesan kesalahan jika nilai kosong tidak tersedia. Anda tidak mendapatkan format pesan kesalahan yang sama persis seperti dalam kode Anda, tetapi pada dasarnya akan berperilaku sama jika $CODEtidak valid:

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

Jika Anda peduli dengan jumlah karakter, kami dapat mempersingkat ini lebih lanjut:

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}

Selain menghapus baris baru yang tidak perlu, saya juga menghapus com.dari setiap domain (ini malah ditambahkan dalam penugasan ke PN).

Perhatikan bahwa semua kode di atas akan berfungsi bahkan untuk nilai multi-karakter di $CODE(jika ada kunci yang lebih rendah untuk ini dalam domainarray).


Jika $CODEbukan indeks numerik (berbasis nol), ini akan menyederhanakan kode sedikit:

CODE=0

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

Ini juga akan membuatnya sangat mudah untuk membaca domainarray dari file tambahan yang mengandung satu entri per baris:

CODE=0

readarray -t domain <domains.txt
PN=${domain[CODE]:?ERROR! CODE KOSONG, MELAKUKAN EXIT OTOMATIS}
Kusalananda
sumber
1
@IISomeOneII declare -A domainhanya mengatakan bahwa itu domainharus menjadi variabel asosiatif array ("hash").
Kusalananda
1
@Isaac Sekarang lebih berbeda dari milikmu. Terimakasih atas peringatannya.
Kusalananda
1
Akan lebih baik menggunakan zsh atau ksh93. Untuk bash, Anda memerlukan versi terbaru dan gagal untuk nilai kosong $CODE.
Stéphane Chazelas
1
@ StéphaneChazelas Ya, Anda akan mendapatkan satu pesan kesalahan tambahan tentang larik subscript yang buruk jika $CODEtidak disetel atau kosong, tetapi itu akan tetap menghasilkan pesan kesalahan khusus yang benar setelah itu.
Kusalananda
1
@ Kusalananda Sebuah skrip baru (POSIX valid) diposting. Tanpa kesalahan memeriksa sangat singkat.
Isaac
11

Jika shell Anda mengizinkan array, jawaban terpendek harus seperti contoh ini di bash:

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

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

Itu dengan asumsi bahwa $codehanya bisa a, b, c atau d.
Jika tidak, tambahkan tes seperti:

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
Ishak
sumber
Jika inputnya A, apakah akan bekerja pada skrip itu? Maaf bahasa Inggris saya buruk
IISomeOneII
2
Ya, ekspansi akan ${var,}mengubah huruf kecil karakter pertama ${var}. @IISomeOneII
Isaac
1
${var,}tampaknya khusus untuk Bash. Saya pikir array asosiatif akan bekerja di ksh dan zsh juga
ilkkachu
@ilkkachu Ya, benar dalam kedua hal.
Isaac
Terima kasih semuanya, Banyak orang baik di sini 👍
IISomeOneII
3

Saya akan mengambil jawaban ini ke arah yang berbeda. Alih-alih mengode data Anda ke dalam skrip, masukkan data itu ke file data terpisah, lalu gunakan kode untuk mencari 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

Memisahkan masalah ini memiliki beberapa manfaat:

  • Tambah dan hapus data dengan mudah dan sederhana, tanpa harus bekerja di sekitar logika kode.
  • Program lain dapat menggunakan kembali data, seperti menghitung berapa banyak kecocokan dalam sub domain tertentu.
  • Jika Anda memiliki daftar data yang sangat besar , Anda dapat mengurutkannya pada disk dan menggunakannya lookuntuk mencarinya secara biner secara efisien (daripada baris demi baris grepatau awk)
uskup
sumber
1
Jika Anda menggunakan cara ini, Anda masih perlu mengatur PNagar diatur ke nilai yang benar.
ilkkachu
1
@ilkkachu Fair point. Saya melewatkan itu di OP. Dikoreksi.
Uskup
2
+1 untuk memisahkan data dari kode.
arp
1

Anda menggunakan huruf untuk mengindeks nilai, jika Anda menggunakan angka, itu menjadi sesederhana:

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

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

Itu kode shell portabel, akan bekerja pada sebagian besar shell.
Untuk bash Anda dapat menggunakan: pn=${!code}, atau untuk bash / ksh / zsh penggunaan: pn=${@:code:1}.

surat

Jika Anda harus pengguna huruf (dari a ke z, atau A ke Z) mereka harus dikonversi ke 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  ))}\"

Dalam kode yang lebih panjang untuk memperjelas maksud dan makna setiap bagian:

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.

Jika Anda perlu mengonversi ke nilai huruf kecil, gunakan: $(( asciival & ~32 ))(pastikan bahwa bit 6 dari nilai ascii tidak disetel).

kode kesalahan

Output yang dicetak skrip Anda pada kesalahan cukup panjang (dan khususnya).
Cara paling serbaguna untuk menghadapinya adalah dengan mendefinisikan suatu fungsi:

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

Dan kemudian panggil fungsi itu dengan pesan spesifik yang Anda butuhkan.

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

Perhatikan bahwa nilai keluar yang dihasilkan diberikan oleh exitcode(contoh di sini adalah 27).

Script lengkap (dengan pengecekan error) kemudian menjadi:

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"
Ishak
sumber