Как я могу кратко назначить различные значения переменной, в зависимости от другой переменной?

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
IISomeOneII
источник
2
Я полагаю, это bashкод? Или ты имеешь в виду другую оболочку?
Фредди
3
К вашему сведению, в будущем я бы порекомендовал заменить личную информацию, такую ​​как URL-адреса и другие вещи, на что-то общее, например, «com.hello.world».
Тревор Бойд Смит
1
@IISomeOneII Вы должны спросить CodeGolf.SE вместо: P
mackycheese21
3
@Trevor, я бы рекомендовал example.orgи example.netт. Д., Поскольку эти домены специально зарезервированы для этой цели в RFC 2606 и никогда не будут использоваться для реальных объектов.
Тоби Спейт
2
@TrevorBoydSmith Передача рекомендации Тоби о 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). Есть много заглавных букв, которые имеют особое значение, и повторное использование одного из них случайно может вызвать проблемы.

Другие примечания: Стандартное соглашение - отправлять сообщения об ошибках в «стандартную ошибку», а не в «стандартный вывод»; >&2редирект это делает. Кроме того, если скрипт (или программа) дает сбой, лучше всего выйти с ненулевым состоянием ( exit 1), поэтому любой вызывающий контекст может сказать, что пошло не так. Также можно использовать разные статусы для обозначения разных проблем ( хороший пример см. В разделе «КОДЫ ВЫХОДА» на curlстранице руководства ). (Благодарим Стефана Шазеласа и Монти Хардера за предложения здесь.)

Я рекомендую printfвместо echo -eecho -n), потому что он более переносим между операционными системами, версиями, настройками и т. Д. У меня когда-то была куча моих скриптов, потому что обновление ОС включало версию bash, скомпилированную с различными опциями, которая изменила echoповедение.

Двойные кавычки $CODEздесь на самом деле не нужны. Строка в a caseявляется одним из немногих контекстов, где их можно оставить без присмотра. Тем не менее, я предпочитаю заключать в кавычки ссылки на переменные, если на то нет особой причины, потому что трудно отслеживать, где это безопасно, а где нет, поэтому безопаснее просто привычно заключать их в двойные кавычки.

Гордон Дэвиссон
источник
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списке, он выходит из сценария с ошибка.

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

Обратите внимание, что весь приведенный выше код будет работать даже для многосимвольного значения в $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должна быть переменная ассоциативного массива ("hash").
Кусалананда
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 Да, правильно в обоих случаях.
Исаак
Спасибо всем, много хороших людей здесь 👍
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для эффективного двоичного поиска (а не построчно grepили awk)
епископ
источник
1
Если вы идете по этому пути, вам все равно нужно принять меры, PNчтобы установить правильное значение.
Ильккачу
1
@ilkkachu Честная точка зрения. Я пропустил это в ОП. Исправлено.
епископ
2
+1 за отделение данных от кода.
ARP
1

Вы используете буквы для индексации значений, если вы должны использовать числа, это становится так просто, как:

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

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

Это переносимый код оболочки, который будет работать на большинстве оболочек.
Для Баша вы можете использовать: pn=${!code}или для Баша / КШ / ЗШ использования: 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 ))(убедитесь, что бит 6 значения ascii не установлен).

код ошибки

Вывод, который ваш скрипт печатает при ошибке, довольно длинный (и особенный).
Самый универсальный способ справиться с этим - определить функцию:

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"
Исаак
источник