別の変数に応じて、変数に異なる値を簡潔に割り当てるにはどうすればよいですか?


20

このシェルスクリプトを短縮するにはどうすればよいですか?

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
これはbashコードだと思う?または、他のシェルを考えていますか?
フレディ

3
将来的には、URLなどの個人情報を「com.hello.world」のような一般的なものに置き換えることをお勧めします。
トレバーボイドスミス

1
@IISomeOneIIあなたが代わりにCodeGolf.SEに尋ねるべきである:P
mackycheese21

3
@Trevorは、私がお勧めしたいexample.orgexample.netこれらのドメインは、具体的RFC 2606で、この目的のために予約されており、実際のエンティティのために使用することはありませんように、など。
トビースパイト

2
@TrevorBoydSmith Seconding Tobyによるcom.exampleなどの推奨。「hello.com」はGoogleが所有しているためです。
デビッドコンラッド

回答:


61

caseステートメントを使用します(移植可能、あらゆるshシェルで動作します):

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

私はまた、(のような、すべて大文字からあなたの変数名を変更することをお勧めしたいCODE何かの小文字または混在ケース(のように)codeまたはCode)。特別な意味を持つすべての大文字の名前があり、そのうちの1つを誤って再使用すると問題が発生する可能性があります。

その他の注意:標準の規則では、エラーメッセージを「標準出力」ではなく「標準エラー」に送信します。>&2リダイレクトはこれを行います。また、スクリプト(またはプログラム)が失敗した場合は、ゼロ以外のステータス(exit 1)で終了するのが最善です。これにより、呼び出し側のコンテキストは何が問題であったかを知ることができます。また、さまざまなステータスを使用してさまざまな問題を示すこともできます(良い例についてはcurlmanページの「EXIT CODES」セクションを参照してください)。(ここでの提案については、StéphaneChazelasとMonty Harderに感謝します。)

OS、バージョン、設定などの間でより移植性があるためprintfecho -e(およびecho -n)の代わりに推奨します.OSアップデートには異なるオプションでコンパイルされたbashのバージョンが含まれていたため、スクリプトの束が壊れていましたecho

ここでは二重引用符$CODEは必要ありません。aの文字列は、caseそれらをオフにしても安全な数少ないコンテキストの1つです。ただし、特定の理由がない限り、変数参照を二重引用符で囲むことを好みます。安全な場所とそうでない場所を追跡するのが難しいため、それらを習慣的に二重引用符で囲む方が安全です。


5
@IISomeOneIIこれはカウントされ*(エラーを出力します)、パターン[aA]は「a」または「A」のいずれかと一致しますが、両方が同時には一致しません。
ゴードンデイヴィソン

6
これはまさにそれを行う正しい方法です。最後のワイルドカードまで出力をstderrにリダイレクトし、ゼロ以外の終了値を生成します。変更する必要があるかもしれない唯一のものは、終了値です。複数のエラーが返される可能性があるためです。より大きなスクリプトでは、代わりにreadonly Exit_BadCode=1言うことができるように、出口値を定義するセクション(おそらく別のファイルからのソース)がありますexit $Exit_BadCode
モンティハーダー

2
最近のbashと一緒に行く場合は、使用case "${CODE,}" in条件文のそれぞれは、単純になりそうという、a)b)など
スティーブ・

2
@MontyHarderそれは依存します。これらのコードが数百個あり、それぞれが文字列に対応している場合、別のアプローチの方が適している場合があります。当面の問題については、これで十分です。
クサラナナンダ

2
@MontyHarder申し訳ありませんが、私はもっと明確にすべきでした。「コード」によって私は意味しました$CODE。私は常に「終了ステータス」と呼んでいますが、「コード」だけではありません。スクリプトが文字列を参照するために何百ものキーを使用する必要がある場合、caseステートメントの使用は扱いにくくなります。
クサラナナンダ

19

bashリリース4.0以降を使用していると仮定します...

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}

コードでは、すべてが単一文字の小文字キーに関連付けられているすべてのドメイン名を含む連想配列を定義します。

$PN変数は、下部外装に対応するドメイン名が割り当てられる$CODE値(${CODE,,}の戻り値は$CODE、この配列のみから小文字になっ)を、しかし場合$CODEに有効なエントリに対応しないdomainリストには、ANとスクリプトを出エラー。

${variable:?error message}パラメータ置換は、の値に拡大する$variable(コード内の適切なドメイン)が、値が利用可能空でない場合、エラーメッセージを表示してスクリプトを出ます。エラーメッセージのフォーマットはコードとまったく同じではありませんが、無効な場合は基本的に同じように動作$CODEます。

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

文字数を気にする場合は、これをさらに短縮できます。

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}

不要な改行を削除するだけでなくcom.、各ドメインからも削除しました(これは代わりに割り当てに追加されPNます)。

上記のすべてのコードは、複数文字の値に対しても機能することに注意してください$CODEdomain配列内にこれらの小文字のキーが存在する場合)。


場合$CODE(ゼロベースの)インデックスの代わりに、数値であった、これはコードを少し単純化するであろう。

CODE=0

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

これにより、domain行ごとに1つのエントリを含む補助ファイルから配列を非常に簡単に読み取ることができます。

CODE=0

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

1
@IISomeOneII declare -A domainは、それdomainが連想配列(「ハッシュ」)変数であるべきだとだけ言っています。
クサラナンダ

1
@Isaacこれで、あなたのものとはさらに明確になりました。ヘッドアップをありがとう。
クサラナナンダ

1
zshまたはksh93を使用することをお勧めします。bashの場合、最新バージョンが必要になりますが、値が空の場合は失敗します$CODE
ステファンシャゼラス

1
@StéphaneChazelasはい、$CODE設定されていないか空の場合、不良な配列添え字に関する追加のエラーメッセージが表示されますが、その後も正しいカスタムエラーメッセージが生成されます。
クサラナンダ

1
@Kusalananda新しい(有効なPOSIX)スクリプトが投稿されました。エラーチェックなしでは非常に短いです。
アイザック

11

シェルが配列を許可している場合、最短の答えはbashの次の例のようになります。

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

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

それは$code、a、b、c、またはdのみであると仮定しています。
そうでない場合は、次のようなテストを追加します。

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

入力がAの場合、そのスクリプトで機能しますか?申し訳ありませんが私の英語の悪い
IISomeOneII

2
はい、展開${var,}はの最初の文字を小文字に変換し${var}ます。@IISomeOneII
アイザック

1
${var,}ただし、Bash固有のようです。連想配列はkshとzshでも機能すると思います
ilkkachu

@ilkkachuはい、両方のカウントで正しいです。
アイザック

THX皆、ここでは良い人がたくさん👍
IISomeOneII

3

この答えを別の方向に持っていきます。データをスクリプトにコーディングするのではなく、そのデータを別のデータファイルに入れてから、コードを使用してファイルを検索します。

$ 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

これらの懸念を分離することには、いくつかの利点があります。

  • コードロジックを回避することなく、データを簡単かつ簡単に追加および削除できます。
  • 他のプログラムは、特定のサブドメインにある一致の数をカウントするなど、データを再利用できます。
  • 膨大なデータのリストがある場合は、ディスク上で並べ替えて、それを使用lookして(1行ごとgrepまたはawk)ではなく、効率的にバイナリ検索できます。

1
この方法で行ってもPN、正しい値に設定されるように手配する必要があります。
イルカチュウ

1
@ilkkachuフェアポイント。私はOPでそれを見逃しました。修正しました。
ビショップ

2
データをコードから分離するための+1。
arp

1

文字を使用して値のインデックスを作成しています。数字を使用する場合は、次のように簡単になります。

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

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

それは移植性のあるシェルコードで、ほとんどのシェルで動作します。
bashの場合:pn=${!code}、またはbash / ksh / zshの場合:を使用できますpn=${@:code:1}

手紙

ユーザー文字(aからz、またはAからZ)を使用する必要がある場合は、インデックスに変換する必要があります。

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

各部分の意図と意味を明確にするためのより長いコードで:

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.

小文字の値に変換する必要がある場合は、次を使用します$(( asciival & ~32 ))(ascii値のビット6が設定されていないことを確認してください)。

エラーコード

スクリプトがエラーで出力する出力は非常に長くなります(特に)。
それに対処する最も用途の広い方法は、関数を定義することです:

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

そして、必要な特定のメッセージでその関数を呼び出します。

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

結果の終了値はexitcode(ここの例は27)で与えられることに注意してください。

完全なスクリプト(エラーチェック付き)は次のようになります。

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"
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.