Làm thế nào tôi có thể gán chính xác các giá trị khác nhau cho một biến, tùy thuộc vào một biến khác?


20

Làm thế nào tôi có thể rút ngắn kịch bản shell này?

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
Tôi cho rằng đây là bashmã? Hay bạn có bất kỳ vỏ khác trong tâm trí?
Freddy

3
FYI trong tương lai, tôi sẽ khuyên bạn nên thay thế thông tin cá nhân như URL và những thứ khác bằng một cái gì đó chung chung như "com.hello.wworld".
Trevor Boyd Smith

1
@IISomeOneII Bạn nên hỏi CodeGolf.SE thay vào đó: P
mackycheese21

3
@Trevor, tôi khuyên bạn nên example.org, example.netv.v., vì các tên miền này được dành riêng cho mục đích này trong RFC 2606 và sẽ không bao giờ được sử dụng cho các thực thể thực sự.
Toby Speight

2
@TrevorBoydSmith Đề xuất của Toby về com.example, v.v., vì "hello.com" thuộc sở hữu của Google.
David Conrad

Câu trả lời:


61

Sử dụng một casecâu lệnh (di động, hoạt động trong bất kỳ shshell nào):

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

Tôi cũng khuyên bạn nên thay đổi tên biến của bạn từ tất cả các chữ in hoa (như CODE) thành một chữ thường hoặc chữ thường (như codehoặc Code). Có rất nhiều tên mũ có ý nghĩa đặc biệt và việc sử dụng lại một trong số chúng một cách tình cờ có thể gây rắc rối.

Lưu ý khác: Quy ước tiêu chuẩn là gửi thông báo lỗi đến "lỗi tiêu chuẩn" thay vì "đầu ra tiêu chuẩn"; các >&2chuyển hướng thực hiện điều này. Ngoài ra, nếu một tập lệnh (hoặc chương trình) không thành công, tốt nhất bạn nên thoát với trạng thái khác ( exit 1), vì vậy bất kỳ bối cảnh gọi nào cũng có thể cho biết điều gì đã sai. Cũng có thể sử dụng trạng thái khác nhau để chỉ các vấn đề khác nhau (xem phần "CODES EXIT" của những curlngười đàn ông trang cho một ví dụ điển hình). (Tín dụng cho Stéphane Chazelas và Monty Harder cho các đề xuất ở đây.)

Tôi khuyên dùng printfthay vì echo -e(và echo -n), vì nó dễ di chuyển hơn giữa các hệ điều hành, phiên bản, cài đặt, v.v. Tôi đã từng có một loạt các tập lệnh của mình bị hỏng vì bản cập nhật hệ điều hành bao gồm một phiên bản bash được biên dịch với các tùy chọn khác nhau, thay đổi cách echoxử lý.

Các trích dẫn kép xung quanh $CODE không thực sự cần thiết ở đây. Chuỗi trong một caselà một trong số ít bối cảnh an toàn để loại bỏ chúng. Tuy nhiên, tôi thích trích dẫn hai tham chiếu biến trừ khi không có lý do cụ thể, bởi vì thật khó để theo dõi nơi nào an toàn và nơi không an toàn, vì vậy sẽ an toàn hơn khi chỉ trích dẫn chúng theo thói quen.


5
@IISomeOneII Điều đó sẽ được tính là *(và in lỗi) - mẫu [aA]phù hợp với "a" hoặc "A", nhưng không phải cả hai cùng một lúc.
Gordon Davisson

6
Đây chính xác là cách đúng đắn để làm điều đó, ngay đến ký tự đại diện ở cuối chuyển hướng đầu ra của nó sang thiết bị lỗi chuẩn và tạo ra giá trị thoát khác không. Điều duy nhất có thể cần thay đổi là giá trị thoát đó, vì có thể có nhiều hơn một lỗi để trả về. Trong một tập lệnh lớn hơn, có thể có một phần (có lẽ có nguồn gốc từ một tệp khác) xác định các giá trị thoát readonly Exit_BadCode=1để nó có thể nói exit $Exit_BadCodethay thế.
Monty Harder

2
Nếu đi với một bash gần đây, sau đó sử dụng case "${CODE,}" in, để mỗi điều kiện trở nên đơn giản a), b)v.v.
steve

2
@MontyHarder Nó phụ thuộc. Nếu có một vài trăm mã này, mỗi mã tương ứng với một chuỗi, thì cách tiếp cận khác có thể tốt hơn. Đối với các vấn đề chính xác trong tay, điều này là đủ.
Kusalananda

2
@MontyHarder Xin lỗi, tôi nên rõ ràng hơn. Ý tôi là "mã" $CODE. Tôi luôn gọi "thoát trạng thái" chính xác, không bao giờ chỉ là "mã". Nếu tập lệnh cần sử dụng nhiều hàng trăm khóa để tham chiếu chuỗi, sử dụng casecâu lệnh trở nên khó sử dụng.
Kusalananda

19

Giả sử bạn đang sử dụng bashphiên bản 4.0 hoặc mới hơn ...

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}

Trong mã, tôi xác định một mảng kết hợp có chứa tất cả các tên miền, mỗi tên được liên kết với một chữ cái viết thường.

Các $PNbiến được gán tên miền tương ứng với giá thấp hơn-cased $CODEgiá trị ( ${CODE,,}trả về giá trị của $CODEbiến thành chữ thường chỉ) từ mảng này, nhưng nếu $CODEkhông thực hiện tương ứng với một mục hợp lệ trong domaindanh sách, nó ra khỏi kịch bản với một lỗi.

Việc ${variable:?error message}thay thế tham số sẽ mở rộng thành giá trị của $variable(tên miền thích hợp trong mã) nhưng sẽ thoát khỏi tập lệnh với thông báo lỗi nếu giá trị trống không có sẵn. Bạn không nhận được chính xác định dạng của thông báo lỗi như trong mã của mình, nhưng về cơ bản nó sẽ hoạt động giống như vậy nếu$CODE không hợp lệ:

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

Nếu bạn quan tâm đến số lượng nhân vật, chúng ta có thể rút ngắn điều này hơn nữa:

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}

Ngoài việc xóa các dòng mới không cần thiết, tôi cũng đã xóa com. khỏi mỗi tên miền (thay vào đó, nó được thêm vào trong bài tập PN).

Lưu ý rằng tất cả các mã ở trên sẽ hoạt động ngay cả đối với một giá trị nhiều ký tự trong $CODE(nếu các khóa có ký tự thấp hơn tồn tại cho các mã này trongdomain mảng).


$CODEThay vào đó, nếu là một chỉ số bằng số (dựa trên số không), điều này sẽ đơn giản hóa mã một chút:

CODE=0

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

Điều này cũng sẽ giúp bạn thực sự dễ dàng đọc domainmảng từ một tệp phụ trợ chứa một mục nhập trên mỗi dòng:

CODE=0

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

1
@IISomeOneII declare -A domainchỉ nói rằng đó domainphải là một biến kết hợp ("băm").
Kusalananda

1
@Isaac Bây giờ khác biệt hơn với bạn. Cảm ơn cho những người đứng đầu lên.
Kusalananda

1
Sẽ tốt hơn để sử dụng zsh hoặc ksh93. Đối với bash, bạn cần một phiên bản gần đây và nó sẽ thất bại đối với các giá trị trống $CODE.
Stéphane Chazelas

1
@ StéphaneChazelas Có, bạn sẽ nhận được thêm một thông báo lỗi về một mục con mảng xấu nếu $CODEkhông được đặt hoặc trống, nhưng nó vẫn sẽ tạo ra thông báo lỗi tùy chỉnh chính xác sau đó.
Kusalananda

1
@Kusalananda Một tập lệnh (POSIX) mới được đăng. Nếu không có kiểm tra lỗi là rất ngắn.
Isaac

11

Nếu shell của bạn cho phép mảng, câu trả lời ngắn nhất sẽ giống như ví dụ này trong bash:

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

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

Đó là giả sử rằng $codechỉ có thể là a, b, c hoặc d.
Nếu không, hãy thêm một bài kiểm tra như:

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

Nếu đầu vào là A thì nó có hoạt động trên tập lệnh đó không? Xin lỗi tiếng Anh của tôi không tốt
IISomeOneII

2
Có, phần mở rộng ${var,}chuyển đổi thành chữ thường đầu tiên của ${var}. @IISomeOneII
Isaac

1
${var,}dường như là đặc thù của Bash. Tôi nghĩ rằng mảng kết hợp cũng sẽ hoạt động trong ksh và zsh
ilkkachu

@ilkkachu Vâng, đúng trong cả hai cách tính.
Isaac

Thx mọi người, Rất nhiều người tốt ở đây
IISomeOneII

3

Tôi sẽ đưa câu trả lời này theo một hướng khác. Thay vì mã hóa dữ liệu của bạn vào tập lệnh, hãy đặt dữ liệu đó vào một tệp dữ liệu riêng biệt, sau đó sử dụng mã để tìm kiếm tệp:

$ 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

Tách những mối quan tâm này có một vài lợi ích:

  • Thêm và xóa dữ liệu dễ dàng và đơn giản, không phải làm việc xung quanh logic mã.
  • Các chương trình khác có thể sử dụng lại dữ liệu, như đếm xem có bao nhiêu kết quả trùng khớp trong một tên miền phụ cụ thể.
  • Nếu bạn có một danh sách dữ liệu khổng lồ , bạn có thể sắp xếp nó trên đĩa và sử dụng lookđể tìm kiếm nhị phân một cách hiệu quả (thay vì từng dòng grephoặc awk)

1
Nếu bạn đi theo cách này, bạn vẫn cần sắp xếp PNđể được đặt thành giá trị chính xác.
ilkkachu

1
@ilkkachu Điểm công bằng. Tôi đã bỏ lỡ điều đó trong OP. Đã sửa.
giám mục

2
+1 để tách dữ liệu khỏi mã.
arp

1

Bạn đang sử dụng các chữ cái để lập chỉ mục các giá trị, nếu bạn sử dụng các số, nó trở nên đơn giản như:

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

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

Đó là mã shell di động, sẽ hoạt động trên hầu hết các shell.
Đối với bash, bạn có thể sử dụng : pn=${!code}, hoặc cho bash / ksh / zsh sử dụng:pn=${@:code:1} .

bức thư

Nếu bạn phải viết thư người dùng (từ a đến z hoặc A đến Z), chúng phải được chuyển đổi thành một chỉ mục:

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

Trong một mã dài hơn để làm rõ ý định và ý nghĩa của từng phần:

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.

Nếu bạn cần chuyển đổi thành giá trị chữ thường, hãy sử dụng: $(( asciival & ~32 ))(đảm bảo rằng bit 6 của giá trị ascii không được đặt).

mã lỗi

Đầu ra mà tập lệnh của bạn in trên một lỗi khá dài (và cụ thể).
Cách linh hoạt nhất để đối phó với nó là xác định hàm:

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

Và sau đó gọi hàm đó với (các) thông điệp cụ thể mà bạn cần.

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

Lưu ý rằng giá trị thoát kết quả được đưa ra bởi exitcode(ví dụ ở đây là 27).

Một tập lệnh đầy đủ (có kiểm tra lỗi) sau đó trở thành:

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"
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.