Як я можу стисло призначити різні значення змінній, залежно від іншої змінної?


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.orgі example.netт. Д., Оскільки ці домени спеціально зарезервовані для цієї мети в RFC 2606 і ніколи не будуть використовуватися для реальних осіб.
Toby Speight

2
@TrevorBoydSmith Рекомендація 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). Існує безліч імен з усіма кришками, які мають особливі значення, і повторне використання одного з них випадково може спричинити неприємності.

Інші зауваження: Стандартна угода полягає в надсиланні повідомлень про помилки на "стандартну помилку", а не на "стандартний вихід"; >&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замість цього.
Monty Harder

2
Якщо йти з недавнім Баш, а потім використовувати case "${CODE,}" in, так що кожен з умовними стає просто a), і b)т.д.
Стів

2
@MontyHarder Це залежить. Якщо є кілька сотень цих кодів, кожен з яких відповідає рядку, то інший підхід може бути кращим. Для точного питання, цього немає.
Kusalananda

2
@MontyHarder Вибачте, я повинен був бути зрозумілішим. Під «кодом» я мав на увазі $CODE. Я завжди називаю "статус виходу" саме таким, ніколи не "кодом". Якщо сценарію потрібно використовувати багато сотень ключів для посилання на рядки, використання caseоператора стає непростим.
Kusalananda

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має бути змінною асоціативного масиву ("хеш").
Кусалаланда

1
@Isaac Тепер більш чіткий від твого. Дякую за голову вгору
Kusalananda

1
Краще використовувати zsh або ksh93. Для bash, вам знадобиться остання версія, і вона не зможе отримати порожні значення $CODE.
Стефан Шазелас

1
@ StéphaneChazelas Так, ви отримаєте одне додаткове повідомлення про помилку про неправильний підписник масиву, якщо він $CODEбув невідомим або порожнім, але воно все одно генерує правильне спеціальне повідомлення про помилку після цього.
Kusalananda

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,}схоже, Баш-специфічний, хоча. Я думаю, що асоціативний масив також працюватиме в 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встановлення правильного значення.
ilkkachu

1
@ilkkachu Справедливий пункт. Я пропустив це в ОП. Виправлено.
єпископ

2
+1 для відділення даних від коду.
арп

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"
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.