如何根据另一个变量简洁地为变量分配不同的值?


20

我如何缩短这个shell脚本?

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
仅供参考,在未来,我建议用“ com.hello.world”之类的通用名称替换诸如URL之类的个人信息和其他内容。
Trevor Boyd Smith

1
@IISomeOneII您应该问CodeGolf.SE而不是:P
mackycheese21

3
@Trevor,我建议example.orgexample.net等等,因为这些领域是专门为此目的保留在RFC 2606和永远不会被用于真正的实体。
Toby Speight

2
@TrevorBoydSmith Seconding Toby对com.example等的推荐,因为“ hello.com”由Google拥有。
戴维·康拉德

Answers:


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)更改为小写或大小写(如codeCode)。有许多全大写字母名称具有特殊含义,并且意外重用其中之一可能会造成麻烦。

其他说明:标准约定是将错误消息发送到“标准错误”而不是“标准输出”。该>&2重定向做到这一点。另外,如果脚本(或程序)失败,则最好以非零状态(exit 1)退出,因此任何调用上下文都可以告诉您出了什么问题。它也可以使用不同的状态来表示不同的问题(见的“退出代码”一节curl手册页的一个很好的例子)。(此处提供StéphaneChazelas和Monty Harder的建议。)

我建议printf不要使用echo -e(和echo -n),因为它在操作系统,版本,设置等之间更易于移植。我曾经一堆脚本中断了,因为操作系统更新包括了一个bash版本,该版本使用不同的选项编译,从而改变了echo行为方式。

$CODE此处实际上不需要双引号。a中的字符串case是少数可以安全删除的上下文之一。但是,除非有特殊原因,否则我倾向于对变量引用加双引号,因为很难跟踪它在什么地方安全以及在何处不安全,因此习惯性地对它们进行双引用会更安全。


5
@IISomeOneII将被视为*(并显示错误)-模式[aA]匹配“ a”或“ A”,但不能一次匹配。
戈登·戴维森

6
这是正确的方法,最后到通配符,将其输出重定向到stderr并生成非零退出值。唯一需要更改的是该退出值,因为可能会返回多个错误。在较大的脚本中,可能会有一个部分(可能来自另一个文件)定义了退出值,readonly Exit_BadCode=1以便可以exit $Exit_BadCode代替。
Monty Harder

2
如果与最近的bash去,然后用case "${CODE,}" in,使每个条件句变得简单a)b)等等
史蒂夫

2
@MontyHarder这取决于。如果这些代码中有几百个,每个代码对应一个字符串,那么另一种方法可能更好。对于眼前的确切问题,这已足够。
库萨兰达

2
@MontyHarder对不起,我应该更清楚了。我所说的“代码” $CODE。我总是准确地称呼“退出状态”,而不仅仅是“代码”。如果脚本需要使用数百个来引用字符串,则使用case语句变得笨拙。
库萨兰达

19

假设您使用的是bash4.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,则它将以错误。

${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)。

请注意,以上所有代码即使对于in中的多字符值也适用$CODE(如果domain数组中存在小写键)。


如果$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从每行包含一个条目的辅助文件中读取数组变得非常容易:

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
斯特凡Chazelas

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对文件进行有效的二进制搜索(而不是逐行grepawk

1
如果采用这种方式,您仍然需要安排PN将其设置为正确的值。
ilkkachu

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

那是可移植的shell代码,适用于大多数shell。
对于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"
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.