У Bash, коли до псевдоніму, коли до сценарію та коли писати функцію?


360

Щоб задати це питання мені знадобилося майже 10 років використання Linux. Все це було пробними помилками та випадковим веб-серфінгом пізньої ночі.

Але людям не потрібно для цього 10 років. Якби я тільки починав роботу з Linux, я хотів би знати: Коли робити псевдонім, коли сценарій і коли написати функцію?

Що стосується псевдонімів, я використовую псевдоніми для дуже простих операцій, які не беруть аргументів.

alias houston='cd /home/username/.scripts/'

Це здається очевидним. Але деякі люди роблять це:

alias command="bash bashscriptname"

(і додати його у .bashrcфайл)

Чи є вагомі причини для цього? Я дуже стараюся, але справді не можу придумати жодних обставин, за яких я хотів би це зробити. Отже, якщо є крайній випадок, коли це може змінити ситуацію, будь ласка, дайте відповідь нижче.

Тому що саме там я просто поклав би щось у свій PATH, і chmod +xце ще одна річ, що з’явилася після років проб і помилок Linux.

Що підводить мене до наступної теми. Наприклад, я додав приховану папку ( .scripts/) в домашній каталог до свого PATH, просто додавши рядок до мого .bashrc( PATH=$PATH:/home/username/.scripts/), тому все, що виконується там, автоматично автоматично завершується.

Якщо мені потрібно.

Мені це насправді не потрібно, чи не так? Я використовував би це лише для мов, які не є оболонкою, як Python.

Якщо це оболонка, я можу просто написати функцію все тієї ж .bashrc:

funcname () {
  somecommand -someARGS "$@"
}

Як я вже зазначив, я дізнався багато цього через спроби та помилки. І я справді бачив красу функцій, коли мій комп’ютер помер, і я був змушений користуватися комп'ютерами оточуючих людей, коли вони не використовували їх.

Замість того, щоб перенести цілий каталог сценаріїв з комп'ютера на комп'ютер, я в кінцевому підсумку просто замінив усі.

Але я щось пропустив?

Отже, що б ви сказали початковому користувачеві Linux про те, коли він повинен мати псевдонім, коли сценарій та коли писати функцію?

Якщо це не очевидно, я припускаю, що люди, які відповідають на це, використають усі три варіанти. Якщо ви використовуєте лише псевдоніми або використовуєте лише скрипти, або використовуєте лише функції - або якщо ви використовуєте лише псевдоніми та сценарії або псевдоніми та функції або сценарії та функції - це питання насправді не спрямоване на вас.


5
можливий дублікат функцій bash vs скриптів
Gilles

10
+1 для чіткого зазначення всіх підмножин {псевдонім, скрипт, функція}, на які це питання не спрямоване. +1 для дитячої віри, що вам було нормально пропустити нульовий підмножина.
Thomas L Holaday

1
Хоча питання спеціально ставилося про баш, зауважте, що старші снаряди Борна мали "псевдонім", але не функціонували. Це може змінити значення, якщо ви переживаєте за сумісність.
AndyB

Якщо .bashrc дійсно найкраще місце чи хоча б солідне місце для цього? У Linux так багато способів зробити те саме, що я ціную, проте, при рівності, я вважаю за краще робити речі найпоширенішим способом.
Kit10

Відповіді:


242

Псевдонім фактично не повинен (загалом) робити більше, ніж змінювати параметри команди за замовчуванням. Це не що інше, як проста заміна тексту на ім'я команди. Він не може нічого робити з аргументами, але передає їх команді, якою вона насправді працює. Тож якщо вам просто потрібно додати аргумент на передній частині однієї команди, псевдонім спрацює. Поширені приклади

# Make ls output in color by default.
alias ls="ls --color=auto"
# make mv ask before overwriting a file by default
alias mv="mv -i"

Функцію слід використовувати, коли потрібно робити щось складніше, ніж псевдонім, але це не буде корисним самостійно. Наприклад, візьміть цю відповідь на запитання, яке я задав про зміну grepповедінки за замовчуванням залежно від того, чи є вона в конвеєрі:

grep() { 
    if [[ -t 1 ]]; then 
        command grep -n "$@"
    else 
        command grep "$@"
    fi
}

Це ідеальний приклад функції, оскільки він занадто складний для псевдоніму (вимагає різних за замовчуванням залежно від умови), але це не те, що вам знадобиться в неінтерактивному сценарії.

Якщо у вас занадто велика кількість функцій або функцій занадто велика, помістіть їх у окремі файли у прихованій папці та заведіть їх у свій ~/.bashrc:

if [ -d ~/.bash_functions ]; then
    for file in ~/.bash_functions/*; do
        . "$file"
    done
fi

Сценарій повинен стояти самостійно. Він повинен мати значення як те, що можна повторно використовувати або використовувати для більш ніж однієї мети.


14
Також важливо пам’ятати, що - якщо сценарій не використовується .або source-, сценарій виконується окремим процесом bash і має власне середовище. З цієї причини все, що змінює середовище оболонки (наприклад, функції, змінні тощо), не буде зберігатися в середовищі оболонки, з якої ви запускаєте сценарій.
Буде Вусден

260

Інші відповіді містять деякі чіткі загальні рекомендації, засновані на особистому смаку, але ігнорують багато відповідних фактів, які слід враховувати, приймаючи рішення між сценаріями, функціями чи псевдонімами.

Псевдоніми та функції ¹

  • Весь вміст псевдонімів та функцій зберігається в пам'яті оболонки.
  • Природним наслідком цього є те, псевдоніми і функції можуть тільки бути використані поточної оболонки, а не якихось - або інших програм , які ви можете викликати з командного рядка , як текстові редактори, скрипти, або навіть дітей примірників однієї й тієї ж оболонки.
  • Псевдоніми та функції виконуються поточною оболонкою, тобто вони працюють в межах і впливають на поточне середовище оболонки.

Сценарії

  • Оболонки не зберігають сценарії в пам’яті. Натомість сценарії зчитуються з файлів, де вони зберігаються щоразу, коли вони знадобляться. Якщо сценарій знайдений за допомогою $PATHпошуку, багато оболонок зберігають в пам'яті хеш імені свого шляху, щоб заощадити час на майбутні $PATHпошуки, але це ступінь сліду пам'яті сценарію, коли він не використовується.
  • Сценарії можна викликати більше способів, ніж функції та псевдоніми. Вони можуть бути передані як аргумент інтерпретатору, наприклад sh script, або викликані безпосередньо як виконуваний файл; у цьому випадку інтерпретатор у рядку shebang (наприклад #!/bin/sh) викликається для його запуску. В обох випадках сценарій керується окремим процесом інтерпретації з власним оточенням, окремим від оточуючої оболонки, на середовище якого сценарій не може впливати жодним чином. Дійсно, оболонка інтерпретатора навіть не повинна відповідати оболонці, що викликає. Оскільки сценарії, на які посилаються таким чином, поводяться як звичайні виконувані файли, їх може використовувати будь-яка програма.

    Нарешті, сценарій може бути прочитаний і працювати в поточній оболонці з ., або в деяких оболонках, source. У цьому випадку сценарій веде себе як функція, яка читається на вимогу, а не постійно зберігається в пам'яті.

Застосування

З огляду на вищесказане, ми можемо розробити деякі загальні вказівки щодо того, чи потрібно щось робити скриптом чи функцією / псевдонімом.

  • Чи повинні інші програми, крім вашої оболонки, вміти ним користуватися? Якщо так, це повинен бути сценарій.

  • Ви хочете, щоб він був доступний лише з інтерактивної оболонки? Зазвичай потрібно змінити поведінку багатьох команд за замовчуванням під час інтерактивного виконання, не впливаючи на зовнішні команди / сценарії. У цьому випадку використовуйте псевдонім / функцію, встановлену у файлі rc "тільки для інтерактивного режиму" оболонки (для bashцього є .bashrc).

  • Чи потрібно змінювати середовище оболонки? І функція / псевдонім, або скрипт з джерелами вибору є можливим вибором.

  • Це те, що ти часто використовуєш? Напевно, ефективніше зберігати його в пам’яті, тому зробіть це функцією / псевдонімом, якщо це можливо.

  • І навпаки, це щось, що ви використовуєте лише рідко? У такому випадку немає сенсу, щоб він не зависав пам’яті, коли вона вам не потрібна, тому зробіть це сценарієм.


¹ Хоча функції та псевдоніми мають деякі важливі відмінності, вони групуються, оскільки функції можуть робити все, що псевдоніми можуть. Псевдоніми не можуть мати локальні змінні, а також не можуть обробляти аргументи, і вони незручні для чогось довшого, ніж один рядок.

² Кожен запущений процес в системі Unix має середовище, що складається з купки variable=valueпар, які часто містять параметри глобальної конфігурації, як-от LANGдля локалі за замовчуванням та PATHдля визначення виконавчого шляху пошуку.


26
ІМХО Це найкраща відповідь.
Люк М

4
Формат запитання / відповіді - чудова ідея. Я можу це вкрасти. ;-)
Мікель

4
Варто зауважити: якщо двом (або більше) скриптам потрібно поділити деякий код, можливо, найкраще ввести цей код у функцію, яка сама знаходиться в третьому файлі, який обидва ці сценарії імпортують / джерела.
kbolino

3
Ще один пункт, який слід додати до цього списку питань: чи вам коли-небудь потрібно змінити функціональність команди в режимі "польоту"? зміни в сценарії відображатимуться на всіх сесіях, тоді як функції та псевдоніми повинні бути перезавантажені або переосмислені на основі кожного сеансу.
Stratus3D

3
Хороша відповідь. Ще одна важлива річ (для мене): створюючи "ярлик" для інших утиліт, краще використовувати псевдонім, тому що наявне автозаповнення буде просто працювати з псевдонімами, але не зі сценаріями або функціями (так +1 для псевдонімів). Наприклад, створивши alias g='gradle'автозавершення gradle при використанні мого gпсевдоніму, але не вийду з нього, коли я використовую сценарій gradle $*або функцію зgradle $@
Yoav Aharoni

37

Я думаю, що це до смаку кожної людини. Для мене логіка йде так:

  • Спочатку я намагаюся зробити псевдонім, тому що це найпростіший.
  • Якщо річ занадто складна, щоб вміститися в один рядок, я намагаюся зробити її функцією.
  • Коли функція починає зростати за десяток рядків, я розміщую її в сценарії.

Дійсно нічого не обмежує вас робити щось, що працює .


6
Я часто пропускаю параметр функції і одразу роблю сценарій. Але я згоден, що це частково питання смаку
Бернхард

2
Функція починає мати сенс, якщо вона потрібна в декількох сценаріях.
Нілс

7
... або якщо вам потрібні побічні ефекти для зміни поточної оболонки.
Гленн Джекман

має сенс, людина!
дослідник

15

Принаймні частково це питання особистого смаку. З іншого боку, є кілька чітких функціональних відмінностей:

  • псевдоніми: підходить лише для простої заміни тексту, без аргументів / параметрів
  • функції: легко записати / використовувати, повна можливість написання оболонок, доступна лише всередині bash
  • сценарії: більш-менш схожі функції, але доступні (дзвоняться) і поза басом

Дивлячись на сценарії оболонок, які я робив за останні кілька років, я більш-менш перестала писати псевдоніми (тому що всі вони, як правило, переростають у функції з часом) і роблю сценарії лише в тому випадку, якщо їм потрібно бути доступними також з не-баш-середовищ.

PS: Що стосується alias command="bash bashscriptname"насправді, я не бачу причин для цього. Навіть якщо bashscriptnameце не в $ PATH, досить просто alias c=/path/to/script.


1
У alias command="bash bashscriptname"сценарії не обов'язково бути виконаним; у alias c=/path/to/scriptцьому має.
Мартін - マ ー チ ン

Це зовсім неправда, що функції "доступні лише всередині Bash". Якщо ви хочете сказати, що вони є лише Bash-функцією, це просто помилково (оболонка Борна і кожна сумісна похідна має їх); і якщо ви хочете сказати, що вони є властивістю інтерактивних оболонок, це теж не точно (хоча псевдоніми, змінні та функції, визначені у файлі, який завантажується при запуску інтерактивними оболонками, очевидно, не завантажуватимуться неінтерактивними оболонками).
трійчатка

@tripleee Сенс був більше схожий на "ви не можете exec()
захищати

11

Ось кілька додаткових моментів про псевдоніми та функції:

  • Одноіменний псевдонім та функція можуть співіснувати
  • Прізвище імен псевдоніму спочатку шукається (див. перший приклад)
  • псевдоніми не можуть бути (не) встановлені в підрозділах або неінтерактивному середовищі (див. другий приклад)

Наприклад:

alias f='echo Alias'; f             # prints "Alias"
function f { echo 'Function'; }; f  # prints "Alias"
unalias f; f                        # prints "Function"

Як ми бачимо, існують окремі простори імен для псевдонімів та функцій; більше деталей можна знайти за допомогою declare -A -p BASH_ALIASESі declare -f f, який друкує їх визначення (обидва зберігаються в пам'яті).

Приклад, що показує обмеження псевдонімів:

alias a='echo Alias'
a        # OK: prints "Alias"
eval a;  # OK: prints "Alias"
( alias a="Nested"; a );  # prints "Alias" (not "Nested")
( unalias a; a );         # prints "Alias"
bash -c "alias aa='Another Alias'; aa"  # ERROR: bash: aa: command not found

Як ми бачимо, псевдоніми не є нестабільними, на відміну від функцій. Також їх використання обмежено інтерактивними сеансами.

Нарешті, зауважте, що ви можете мати довільні обчислення в псевдонімі, оголосивши функцію, яка негайно викликає її, як-от так:

alias a_complex_thing='f() { do_stuff_in_function; } f'

яка вже широко використовується у випадку псевдонімів Git. Перевага цього при оголошенні функції полягає в тому, що ваш псевдонім не може бути просто перезаписаний джерелом (або використанням .) сценарію, який буває оголошений однойменною функцією.


10

Коли писати сценарій ...

  • Сценарії збирають компоненти програмного забезпечення (ака. Інструменти, команди, процеси, виконувані файли, програми) на більш складні компоненти, які самі можуть бути зібрані в ще складніші компоненти.
  • Сценарії зазвичай робляться виконаними, тому їх можна назвати по імені. При виклику створюється новий підпроцес для запуску сценарію. Копії будь-яких exportзмінних змін та / або функцій передаються за значенням сценарію. Зміни цих змінних не повертаються до батьківського сценарію.
  • Сценарії також можуть бути завантажені (джерела), як якщо б вони були частиною виклику сценарію. Це аналогічно тому, що деякі інші мови називають "імпортувати" або "включати". Коли вони отримані, вони виконуються в межах існуючого процесу. Жоден підпроцес не створюється.

Коли написати функцію ...

  • Функції - це ефективно попередньо завантажені сценарії оболонки. Вони виконуються трохи краще, ніж викликати окремий скрипт, але тільки якщо він повинен бути прочитаний з механічного диска. Сьогоднішнє розповсюдження флеш-накопичувачів, SSD-дисків і звичайне кешування Linux у невикористаній оперативній пам'яті роблять це поліпшення в основному незмірним.
  • Функції служать основним засобом Баша для досягнення модульності, інкапсуляції та повторного використання. Вони покращують чіткість, надійність та ремонтопридатність сценаріїв.
  • Правила синтаксису для виклику функції ідентичні правилам виклику виконуваного файлу. Замість виконуваної програми буде викликано функцію з тим самим іменем, що і виконуваний файл.
  • Функції локальні для сценарію, в якому вони перебувають.
  • Функції можуть бути експортовані ( скопійовані за значенням ), щоб їх можна було використовувати всередині названих сценаріїв. Таким чином, функції поширюються лише на дитячі процеси, ніколи на батьків.
  • Функції створюють команди для багаторазового використання, які часто збираються в бібліотеки (скрипт з лише визначеннями функцій), який повинен отримувати інші сценарії.

Коли писати псевдонім ...

У таких сценаріях, як сценарії бібліотеки, іноді потрібен псевдонім для функції, наприклад, коли функція перейменована, але потрібна зворотна сумісність. Це можна досягти, створивши просту функцію зі старим іменем, яка передає всі її аргументи новій функції ...

# A bash in-script 'alias'
function oldFunction () { newFunction "$@"; }

9

Ще одна річ, в яку я не вірю, була піднята: функція виконується в контексті процесу виклику, тоді як сценарій виділяє нову оболонку.

Це може бути важливим для продуктивності - функція швидша, оскільки вона не працює fork()і не працює exec(). У звичайних обставинах різниця є тривіальною, але якщо ви налагоджуєте систему, яка не має пам’яті та обробляє сторінку, це може призвести до великої зміни.

Крім того, якщо ви хочете змінити своє поточне середовище оболонки, вам слід скористатися функцією. Наприклад, функція може змінити пошук команди $PATHдля поточної оболонки, але сценарій не може, оскільки вона працює на копії fork / exec $PATH.


Як працює таке поширення функцій на дітей?
HappyFace

1
@HappyFace У Bash ви можете export -fфункціонувати, хоча точні внутрішні дії цього дещо незрозумілі. Це не є переносним для традиційної оболонки Борна.
tripleee

7

Сценарій та псевдонім, сценарій та функції не є взаємовиключними. Ви можете і зберігати псевдоніми та функції в сценаріях.

Сценарії - це просто код, який стає стійким . Корисні функції та псевдоніми, які ви хочете використовувати в майбутньому, зберігаються в сценаріях. Однак сценарій часто є сукупністю декількох функцій.

Оскільки псевдоніми не параметризовані , вони дуже обмежені; зазвичай для визначення деяких параметрів за замовчуванням.

Функція являє собою окремий блок коду , чітко визначена концепція декількох рядків коди , які не можуть розділені на більш дрібні, корисні частини; той, який може бути використаний безпосередньо або іншим за допомогою інших функцій.


5

Якщо це має бути дуже швидким, зробіть це псевдонімом або функцією.

Якщо він повинен бути використаний за межами бажаної оболонки, зробіть це сценарієм. 1

Якщо він бере аргументи, зробіть це функцією або сценарієм.

Якщо він повинен містити спеціальні символи, зробіть його псевдонімом або сценарієм. 2

Якщо йому потрібно працювати з судо, зробіть його псевдонімом або сценарієм. 3

Якщо ви хочете легко змінити його, не виходячи з системи та ввійти, скрипт простіше. 4

Виноски

1 Або зробіть його псевдонімом, поставте його ~/.envта встановіть export ENV="$HOME/.env", але його складно зробити так, щоб він працював портативно.

2 Імена функцій повинні бути ідентифікаторами, тому вони повинні починатися з літери, а можуть містити лише літери, цифри та підкреслення. Наприклад, у мене є псевдонім alias +='pushd +1'. Це не може бути функцією.

3 І додайте псевдонім alias sudo='sudo '. Дітто будь-яка інша команда, наприклад strace, gdbі т.д., яка приймає команду як перший аргумент.

4 Див. Також: шлях. Звичайно, ви також можете зробити source ~/.bashrcчи подібне, але це часто має інші побічні ефекти.


1
Я не знав, що ти можеш псевдонім +у баш. Цікаво, що після тестування я виявив, що в bash ви можете зробити +псевдонім, але не функцію, як ви кажете, але zsh є зворотним - +може бути функцією, але не псевдонімом.
Кевін

В zshтебе треба писати alias -- +='some command here'.
Мікель

Якимось чином я не думаю, що псевдонім +є портативним. Дивіться специфікацію POSIX на Alias ​​Names
jw013,

3
Резюме на покриття sudoвикористання. Щодо виноски 4, я зберігаю свої псевдоніми ~/.bash_aliasesта визначення функцій, ~/.bash_functionsщоб я міг їх легко повторно source(без жодної небезпеки побічних ефектів).
Ентоні Геоґеган

3

Просто додати кілька приміток:

  • За допомогою sudo можна використовувати лише окремий скрипт (наприклад, якщо вам потрібно відредагувати системний файл), наприклад:
sudo v /etc/rc.conf  #where v runs vim in a new terminal window;
  • Лише псевдоніми або функції можуть замінити системні команди під тим самим іменем (якщо припустити, що ви додаєте свої сценарії dir до кінця PATH, що, на мою думку, є доцільним для безпеки в разі випадкового або зловмисного створення сценарію з іменем, ідентичним системній команді), наприклад:
alias ls='ls --color=auto'  #enable colored output;
  • Псевдоніми та функції займають менше пам’яті та часу для виконання, але потребують часу для завантаження (оскільки оболонка повинна інтерпретувати їх усі, перш ніж показувати вам запит). Враховуйте це, якщо регулярно запускаєте нові процеси оболонки, наприклад:
# pressing key to open new terminal
# waiting for a few seconds before shell prompt finally appears.

Крім цього, ви можете використовувати найпростішу можливу форму, тобто спочатку розглянути псевдонім, потім функцію, а потім сценарій.


5
Псевдоніми можна використовувати і при sudo. Але спочатку вам потрібно alias sudo='sudo '.
Мікель

Хоча це правда, що виконання сценарію миттєво споживає більше пам’яті протягом тривалості fork + exec, але багато коду, завантаженого в пам’ять поточного екземпляра оболонки, змушує споживати більше пам’яті вперед, часто зберігаючи код, який тільки звикає досить рідко.
трійчатка

2

Моє правило:

  • псевдоніми - одна команда, без параметрів
  • функції - одна команда деяких параметрів
  • скрипт - кілька команд, ніяких параметрів

1

У середовищі з декількома користувачами (або з декількома системами) я використовую сценарії для всього, навіть якщо це просто закінчується короткою обгорткою "виконувати щось ....".

Звичайно, це технічно повільніше / менш ефективно, ніж псевдонім або функція, але це майже ніколи не має значення - і за умови, що це на шляху, сценарій завжди працює.

Ваша функція може бути викликана з cron, із чогось із зменшеним або модифікованим середовищем, як-от sudo або env, або користувач може просто використовувати іншу оболонку для вас - все або, що може порушити псевдонім або функцію.

Якщо у вас є щось чутливе до продуктивності, розглядайте це як особливий випадок, а ще краще, вважайте, що це тригер для перезапису на більш функціональній мові сценаріїв.

Якщо ми говоримо про функції, які будуть використовуватися лише в інших сценаріях, то ви також можете розглянути можливість визначення стандартної оболонки та написання сценарію бібліотеки функцій, який просто може бути. я отримав усі інші сценарії.

Т


0

Приклад ситуації, коли ви, швидше за все, хотіли б використовувати псевдонім.

Я знаю, що це стара публікація, але я хотів би зазначити ситуацію, коли мені майже довелося використовувати комбінацію псевдоніму зі сценарієм, і я вирішив не використовувати функцію.

У мене є скрипт в ~/.bin/call, setupякий робить наступне: 1

  1. приводить мене до певного каталогу.
  2. визначає кілька змінних.
  3. друкує повідомлення про стан каталогу. 2

Справа в тому, що якби я просто запустив, setup <project-name>я б не визначав ці змінні, і я б взагалі не потрапив до каталогу. Я вважав найкращим рішенням було додати цей скрипт до PATHі додати alias setup=". ~/.bin/setup"до ~/.bashrcбудь-якого іншого.

Примітки:

  1. Я використовував скрипт для цього завдання, а не функцію не тому, що він особливо довгий, а тому, що я можу його редагувати, і мені не потрібно джерело файлу після редагування, якщо я хочу оновити його використання.
  2. Подібний випадок, який мені сподобався, коли я створив сценарій, щоб перезавантажити всі свої dotfiles. 3
  1. Сценарій доступний у моєму сховищі dotfiles під .bin/.
  2. Про сценарій : Я даю цьому сценарію аргумент, який є назвою проекту, який я визначив у розширеному. Згодом сценарій знає, що він приведе мене до потрібного каталогу відповідно до певного csvфайлу. Змінні, які він визначає, взяті з makefile у цьому каталозі. Сценарій запускається згодом ls -lі git statusпоказує мені, що там відбувається.
  3. Цей сценарій також доступний у моєму сховищі dotfiles .bin/.

1
Гм, здається, це має бути просто функцією, а не комбінацією псевдонімів-скриптів. (BTW, середовище написано "середовища", а не "навколишнє середовище")
Wildcard

Дякую за коментар щодо друкарської помилки, я виправлю це на наступному зобов'язанні. Що стосується використання функції замість сценарію - можливо, я буду використовувати функцію для цієї конкретної задачі і скину ці псевдоніми. Справа в тому, що іноді досить просто використовувати скрипт та псевдонім, якщо час від часу редагувати цей сценарій .
Дорон Бехар

0

Коли писати сценарій

Коли ви можете запустити команду з іншого інструменту, ніж оболонки.

Сюди входить vim (для мене): маючи написані фільтри та інші програми як сценарії, я можу зробити щось на кшталт :%!my-filterвідфільтрувати файл через програму з мого редактора.

Якби my-filterфункція чи псевдонім, це було б неможливо.

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