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


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 -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
Якщо йти з недавнім Баш, а потім використовувати 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.