LINUX.ORG.RU

Если в баше все так плохо, не пора ли перестать им пользоваться для скриптов?

 ,


5

7

Прочитал на днях вот эту статейку: http://www.dwheeler.com/essays/filenames-in-shell.html. Это просто жесть. Я наверное не видел ни одного баш скрипта, который бы делал все правильно, как там описано. Не легче ли использовать какой-нибудь там питон для этого, а не терпеть внезапные унижения собственным шеллом, когда ты впервые запустишь скрипт на файлах с русскими символами / пробелами / переносами строк / еще какими-нибудь внезапными названиями?

Ответ на: комментарий от TGZ

А ну тогда другое дело. Странно, что не практикуют объективно-ориентированный подход в типовых задачах. Во всяком случае не видел.

Очевидно, потому что парсить попросту удобнее.

Или ты преувеличиваешь возможности bash?

Ну сам попробуй. Как получить нужные и понятные тебе имена переменных в баше я уже писал. А уж что ты будешь делать с этими $var/cmdline и пр. - твоё дело. В примере выше просто выводится их реальные пути. Хочешь содержимое - можно например

cmdline=`cat $var/cmdline`
exec_binary=`readlink $var/exe`
echo +10 > $var/oom_adj
Да, вместо точки будет слэш, но не думаю, что это сильно помешает делать всякое со свойствами объекта.

Stanson ★★★★★
()
Ответ на: комментарий от aidan

Во-первых, я не пойму, где в твоём примере вывод именно определенного столбца (+ где там указано имя конкретного файла?). Во-вторых, оно и сложнее и длиннее шелла. В-третьих, повторю, тема про замену шелл-скриптов в Линуксе прямо сейчас. Напишу ещё покрупнее «ПРЯМО СЕЙЧАС».

Ты не понял, в повершелле никто не работает с файлами, командлет Get-EventLog как раз и возвращает список логов, причем это не строки текста - а объекты, по-этому «второй столбец» - совершенно бессмысленное понятие, у объектов нету столбцов, у них есть конкретные поля, например:

Get-EventLog -LogName Application | ? EntryType -eq Error

Вернет все записи лога Application с типом лога Error. Но если все же надо именно второй столбец, то:

Select-String «name» server.log | % {($_.Line -split " ")[1]}

anonymous
()
Ответ на: комментарий от tailgunner

А пользоваться Жабой - это всё равно, что для купания надевать скафандр водолаза-глубоководника.

как то так, но я больше про недостатки баша чем про достоинства явы для скриптов.

мне тут идея пришла, покритикуй, пжл (ну и кому интересно - присоединяйтесь)

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

Вместо единого потока вывода как сейчас в шелле, есть предложение использовать «структурированные подпотоки».

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

Для примера возьмём вывод lspci:

00:00.0 Host bridge: Advanced Micro Devices, Inc. [AMD] RS880 Host Bridge
00:01.0 PCI bridge: Advanced Micro Devices, Inc. [AMD] RS780/RS880 PCI to PCI bridge (int gfx)
00:0a.0 PCI bridge: Advanced Micro Devices, Inc. [AMD] RS780/RS880 PCI to PCI bridge (PCIE port 5)
00:11.0 SATA controller: Advanced Micro Devices, Inc. [AMD/ATI] SB7x0/SB8x0/SB9x0 SATA Controller [AHCI mode]
00:12.0 USB controller: Advanced Micro Devices, Inc. [AMD/ATI] SB7x0/SB8x0/SB9x0 USB OHCI0 Controller

данные могли бы быть разбиты на следующий поток

((адрес), (тип), (производитель), (модель), (код производителя) (описание), (дополнительно))

в программе формирующей этот поток (т.е. в lspci) код формирования потока выглядел бы примерно так

pprint(stream(
stream(printf("%d:%d.%d", d.addr.a, d.addr.b, d.addr.c), "address:%s"), 
stream(printf("%s", d.type), "type:%s::"), 
stream(printf("%s", d.producer), "producer:%s"), 
stream(printf("%s", d.model), "model:%s"), 
stream(printf("%s", d.producer_code), "producer code:[%s]"), 
stream(printf("%s", d.description), "description:%s"), 
stream(printf("%s", d.info), "more info:%s"),
"main:%s\n"//имя:формат родительского потока
))

тогда, так как сохраняется совместимость с тем что есть сейчас (поскольку основной поток текст), то грепать поток можно как обычно

lspci | grep 'what do you need'

но можно и переформатировать поток как тебе удобно без лишних телодвижений

lspci | streamf #1d #2d:%s #5d:%s

где # - операция обращения к подпотоку, цифра - номер подпотока, d - данные, f - формат результат вывода был бы такой

00:00.0 Host bridge AMD
00:01.0 PCI bridge AMD
00:0a.0 PCI bridge AMD
00:11.0 SATA controller AMD/ATI
00:12.0 USB controller AMD/ATI

Нужно конечно научить шелл отдавать описание формата потока программы с очередью подпотоков, вроде

#lspci
stream description
main:%s
  address:%s
  type:%s
...

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

Кстати, может уже такое есть?

AndreyKl ★★★★★
()
Последнее исправление: AndreyKl (всего исправлений: 1)
Ответ на: комментарий от tailgunner

Я сказал, для чего шелл сделан.

Ты не правильно сказал. Шелл сделан для обработки структурированных данных. Потому что неструктурированных данных в природе нет, они бесполезны, т.к. не могут содержать информацию.

anonymous
()
Ответ на: комментарий от anonymous

Ты не понял, в повершелле никто не работает с файлами, командлет Get-EventLog как раз и возвращает список логов, причем это не строки текста - а объекты, по-этому «второй столбец» - совершенно бессмысленное понятие, у объектов нету столбцов, у них есть конкретные поля, например:

В общем случае задача — распарсить некий текстовый файл. В момент его открытия я даже не представляю, в каком там всё формате, задача формулируется по ходу его просмотра. Откуда PS знает, что у меня там? Логи томката, список координат домов или наблюдения за влажностью в Жигулёвске? Пока что из объяснений в этой теме, я вижу только что все его преимущества идут из-за того, что у файлов есть некая метаинформация, по которой понятно, в каком там всё формате представлено. А вот для общего случая, в твоём примере:

Select-String «name» server.log | % {($_.Line -split " ")[1]}

это ничем не отличается от шелла.

Разница примерно как между «grep transmission syslog» и «journalctl -u transmission». Неважно, что там внутри, но с точки зрения пользователя — просто кто-то заморочился и сделал некий предварительный разбор структуры. Да, получилось удобней, но как это прикрутить к абсолютно любому текстовому файлу?

aidan ★★★★
()
Ответ на: комментарий от AndreyKl

Кстати, может уже такое есть?

Да есть.

Вот в это:

pprint(stream(
stream(printf("%d:%d.%d", d.addr.a, d.addr.b, d.addr.c), "address:%s"), 
stream(printf("%s", d.type), "type:%s::"), 
stream(printf("%s", d.producer), "producer:%s"), 
stream(printf("%s", d.model), "model:%s"), 
stream(printf("%s", d.producer_code), "producer code:[%s]"), 
stream(printf("%s", d.description), "description:%s"), 
stream(printf("%s", d.info), "more info:%s"),
"main:%s\n"//имя:формат родительского потока
))

дописываешь то, что не дописал, чтобы реально работало, а в первой строчке этого всего пишешь какой-нибудь #!/usr/bin/tcc -r (ну или то, что это вот написанное жрёт и выполняет). Вот и всё, и пользуйся своим велосипедом в своих объектно-ориентированных скриптах сколько влезет.

Stanson ★★★★★
()
Ответ на: комментарий от aidan

в >А вот для общего случая, в твоём примере:

что такое «для общего случая»?

что вы собрались парсить в ЛЮБОМ текстовом файле? и в логах, и в войне и мир?

next_time ★★★★★
()
Ответ на: комментарий от next_time

что вы собрались парсить в ЛЮБОМ текстовом файле? и в логах, и в войне и мир?

Всё, что захочу.

Логи томката, список координат домов или наблюдения за влажностью в Жигулёвске?

aidan ★★★★
()
Ответ на: комментарий от Legioner

тогда, это хреновый инструмент получается: у шарпа порог вхождения, действительно, выше, чем у баша, а если разницы, в итоге, не заметно...

next_time ★★★★★
()
Ответ на: комментарий от aidan

буду искать Логи томката, список координат домов или наблюдения за влажностью в Жигулёвске?
«Война и Мир»

...

next_time ★★★★★
()
Ответ на: комментарий от next_time

Ух-ты! Три точки! Одни наркоманы в девелопменте.

«Война и Мир»

Филологи занимаются анализом текстов, да. Можешь поискать работы по прикладной филологии.

aidan ★★★★
()
Ответ на: комментарий от AndreyKl

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

Кто-то уже предлагал:

— Это всё обернуть в JSON, хранить JSON´овские объекты в переменных и гонять их по пайпам.

— Для работы с самим JSON´ом (вычленение поля/модификация) использовать jshon/jq/что там ещё.

— Написать аналоги coreutils (json-grep, json-sort, json-find, json-df, …), принимающие и отдающие JSON.

— Переходники, парсящие вывод команды и отдающие его в JSON для всего остального.

anonymous
()
Ответ на: комментарий от aidan

это ничем не отличается от шелла.

А с чего оно должно отличаться от шелла? Эта задача абсолютно на всех известных ЯП будет решаться одинаково - пробегаем по списку строк, выбираем нужные, выбираем из каждой строки требуемый столбец. Так уж она поставлена, чтобы решаться везде одинаково.

В общем случае задача — распарсить некий текстовый файл.

В реальности не стоит задача парсинга файлов. Стоит задача получения каких-то конкретных данных и их обработка. Парсить _приходиться_ в баше, потому как данные представлены обычным текстом. Если данные представлены в виде объектов - очевидно, ничего парсить не надо.

anonymous
()
Ответ на: комментарий от aidan

Пока что из объяснений в этой теме, я вижу только что все его преимущества идут из-за того, что у файлов есть некая метаинформация

Нету никаких файлов. Get-EventLog - командлет, который возвращает информацию о логах. Откуда там он ее берет и как - неизвестно, пусть хоть из астрала достает.

anonymous
()
Ответ на: комментарий от AndreyKl

Модернизация языка? Можно, но предлагаемых типов мало, и даже их вкорежить без поломки совместимости будет нетривиально.

Передавать между программами не просто текст, а нечто структурированное хотя бы до уровня JSON? Но кто и как будет это генерировать? Microsoft смогла (?) провернуть трюк «между программами передаются потоки объектов» только потому, что она полностью контролирует свою софтверную экосистему.

В общем, нужно либо строить новую инфраструктуру, в которой команды генерят не текст, а структурированную информацию, либо конструировать на замену регулярным выражениям нечто более крутое (что-то вроде линз Augeaus), и вставлять их после команд, генерирующих текст. Правда, остается вопрос об интеграции работы со структурированными данными в шелл. Возможно, как-то переиспользовать ассоциативные массивы bash...

В общем, это исследовательская проблема. Она требует людей с квалификацией отцов-основателей Unix, а в наличии одни недоучки с понтами, вроде поцеринга.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

Microsoft смогла (?) провернуть трюк «между программами передаются потоки объектов» только потому, что она полностью контролирует свою софтверную экосистему.

на линуксе достаточно договориться комитету Дебиан и Ред Хат

next_time ★★★★★
()
Ответ на: комментарий от next_time

на линуксе достаточно договориться комитету Дебиан и Ред Хат

Во-первых, Redhat не станет договариваться с Debian и уже продемонстрировал это; во-вторых, Redhat не проявляет никакой заинтересованности в замене или улучшении шелла (хотя параноики могут пофантазировать о повсеместном внедрении Dbus).

tailgunner ★★★★★
()
Ответ на: комментарий от anonymous

не знаю, джейсон как то видится труднее и толку не больше чем от предложенного. большой плюс предложенного выше в том что все старые программы просто работают, без дополнительных телодвижений. причём все старые программы работают и с новыми потоками (тогда они будут получать один поток как текст) и со старыми. +вводится минимум новых концептов, просто вместо одного потока текста, появляется несколько потоков с разными типами (обычно тоже текст), ну и типов данных чуток побольше чтобы работать можно было, а не думать как сложить 1.2 и 5.7 или как передать в функцию то что вернула предыдущая, да так чтобы парсить во внеочередной раз не пришлось.

AndreyKl ★★★★★
()
Ответ на: комментарий от AndreyKl

думать как сложить 1.2 и 5.7

Вот для этого можно просто взять zsh.

anonymous
()
Ответ на: комментарий от anonymous

Нету никаких файлов. Get-EventLog - командлет, который возвращает информацию о логах. Откуда там он ее берет и как - неизвестно, пусть хоть из астрала достает.

Хорошо, я скачал файл со списком полигонов неких георегионов. В какой астрал мне его запихнуть, чтобы «командлет» начал его понимать?

aidan ★★★★
()
Ответ на: комментарий от tailgunner

Модернизация языка?

да, мысль такая.
Причём вся текущая инфраструктура остаётся полностью рабочей без дополнительных костылей.

Можно, но предлагаемых типов мало,

почему, навскидку?

и даже их вкорежить без поломки совместимости будет нетривиально.

это скорее всего так.

Передавать между программами не просто текст, а нечто структурированное хотя бы до уровня JSON? Но кто и как будет это генерировать?

Ну где то так, уровня json. Генерить просто, я ж показал идею. просто вместо того чтобы вызывать один printf(«%s%s%s%d», a, b, c, d), надо будет обернуть каждую переменную (в данном случае) в вызов функции. Может можно ещё что то поинтереснее и попроще придумать. В общем насчёт «как» вопрос мне кажется тривиальным. Вот «кто» это да, вопрос интересный.

В общем, это исследовательская проблема. Она требует людей с квалификацией отцов-основателей Unix, а в наличии одни недоучки с понтами, вроде поцеринга.

Ну что делать, имеем то что имеем. Отцов-основателей на всех не хватает.

Понимаю что ты сравниваешь меня с поттерингом, но всё равно у меня такое чувство что я почти что гений, предложил мегаштуку. Очень простой концеп который позволил бы убрать бесконечный парсинг из шелл скриптов и полностью сохранить обратую совместимость. Аж руки чешутся реализовать, жаль занят сейчас на 60 часов в неделю...

AndreyKl ★★★★★
()
Последнее исправление: AndreyKl (всего исправлений: 1)
Ответ на: комментарий от AndreyKl

Можно, но предлагаемых типов мало,

почему, навскидку?

По опыту. Я люблю шелл и добровольно пытался писать на нем типа-GUI-программы (и до сих пор пишу что-то похожее для dpkg-reconfigure). Нужны взрослые структуры данных (и статическая типизация!!!11 ну или хотя бы чекер).

Передавать между программами не просто текст, а нечто структурированное хотя бы до уровня JSON? Но кто и как будет это генерировать?

Ну где то так, уровня json. Генерить просто, я ж показал идею. просто вместо того чтобы вызывать один printf(«%s%s%s%d», a, b, c, d), надо будет обернуть каждую переменную (в данном случае) в вызов функции.

И технически этого мало (хотя бы потому, что данные генерируются не только printf, но и берутся из файлов), и само требование модифицировать существующие программы - это слишком.

Понимаю что ты сравниваешь меня с поттерингом

Тебя? И в мыслях не было.

Аж руки чешутся реализовать, жаль занят сейчас на 60 часов в неделю...

Не говоря даже о технических проблемах, твое предложение включает в себя форк кучи утилит. Это non-starter, не трать время зря.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

Пока N == 1, не так уж страшно.

Ну прям-таки 1.

Если делать объектную инфраструктуру, то уже есть GObject, Qt, кто-то выше предлагал джаву и моно/дотнет.

Если просто текстовое представление в каком-нибудь формате, то есть JSON, XML, CSV, INI(?).

korvin_ ★★★★★
()
Ответ на: комментарий от erzent

а чем тебе не нравиться 32 гб озу?

Я разве говорил, что не нравится? На десктопе такой объем излишен, да и не всякая мамка поддерживает 32 ГБ. 8 ГБ должно хватить.

Deleted
()
Ответ на: комментарий от tailgunner

Нужны взрослые структуры данных (и статическая типизация!!!11 ну или хотя бы чекер).

тогда вряд ли это будет модернизация.

И технически этого мало (хотя бы потому, что данные генерируются не только printf, но и берутся из файлов), и само требование модифицировать существующие программы - это слишком.

у нас остаётся вся инфраструктура. тот же авк, греп, кат. нужна будет утилита которая сможет из описания формата формировать потоки, навроде

grep filename hello | awk "{print $1:%s $2:%d}" #| echo #1d #2d

Кстати с помощью подобной утилиты можно было бы и для текущих команд написать оболочки (проблема о которой ты говорил, как я понимаю)

Тебя? И в мыслях не было.
а в наличии одни недоучки с понтами,

как то воспринял на свой счёт частично, вроде «много вас таких умных», без обид.. недопонимание вышло с моей стороны

Это non-starter, не трать время зря.

спасибо за критику. времени всё равно нет, тратить нечего..

AndreyKl ★★★★★
()
Последнее исправление: AndreyKl (всего исправлений: 1)
Ответ на: комментарий от korvin_

Пока N == 1, не так уж страшно.

Ну прям-таки 1.

Я не вижу второй.

Если делать объектную инфраструктуру, то уже есть GObject, Qt, кто-то выше предлагал джаву и моно/дотнет.

Объектная инфраструктура будет очень дорого стоить и не даст профита (кто постарше, помнит CORBA-based GNOME и KDE). В любом случае, столько ресурсов никто тратить не станет.

Если просто текстовое представление в каком-нибудь формате, то есть JSON, XML

Ну да, они есть, и? Закончи мысль..

INI(?).

Поцеринг, залогиньтесь.

tailgunner ★★★★★
()
Ответ на: комментарий от AndreyKl

у нас остаётся вся инфраструктура. тот же авк, греп, кат. нужна будет утилита которая сможет из описания формата формировать потоки, навроде
grep filename hello | awk «{print $1:%s $2:%d}» #| echo #1d #2d

Пока что я вижу, что ты планируешь модифицировать awk (и шелл? и echo?).

Кстати с помощью подобной утилиты можно было бы и для текущих команд написать оболочки (проблема о которой ты говорил, как я понимаю)

Суть писания линз - в том, что они не затрагивают команды, идущие в конвейерах перед линзами. И всё, что нужно для использования структурированного потока - консервативное расширение шелла для использования JSON-иерархий в командах селекции (а на первое время вообще подойдет отдельная команда).

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

Пока что я вижу, что ты планируешь модифицировать awk (и шелл? и echo?).

нет, я просто авком пользоваться не умею. я беру данные из файла, потом передаю их на вход авка, там выбираю первое и второе поле считая пробел разделителем, печатаю эти два поля разделяя пробелом и добавляя формат данных (просто дополнительные символы в строке, :%s и %d, наверное надо кстати :name:%s :hours:%d) потом передаю сформированную стоку вида «Андрей:name:%s 20:hours:%d» на вход утилиты(команды) #| , которая умеет формировать потоки из форматированной строки, а потом вызываю обычное эхо, а там шелл подставит данные из потоков по идее

AndreyKl ★★★★★
()
Ответ на: комментарий от tailgunner

Ну да, они есть, и? Закончи мысль..

Эм, что там заканчивать? Зачем строить какую-то новую инфраструктуру в дополнении к уже имеющимся?

Поцеринг, залогиньтесь.

Куда и какой пароль?

korvin_ ★★★★★
()
Ответ на: комментарий от korvin_

Если просто текстовое представление в каком-нибудь формате, то есть JSON, XML

Ну да, они есть, и? Закончи мысль..

Эм, что там заканчивать?

Мне показалось, там не хватает указания, откуда возьмутся данные в этих форматах. Видимо, я ошибся.

Зачем строить какую-то новую инфраструктуру в дополнении к уже имеющимся?

Зависит от того, какая это инфраструктура, и как она взаимодействует с имеющейся.

tailgunner ★★★★★
()
Ответ на: комментарий от AndreyKl

Я не понял, зачем для этого модифицировать программы.

ps auxv | proclist-lens | oselect "_.pid where _.user == $USER && _.rss > 500M" | xargs kill

Всё. Никаких модификаций существующих программ, вся магия - в proclist-lens. Другое дело, что это должна быть годная магия, какой была магия регэкспов 40 лет назад.

tailgunner ★★★★★
()
Последнее исправление: tailgunner (всего исправлений: 1)

Натерпелся я в своё время, когда надо было написать простой кроссплатформеннвй скрипт. Тут-то я и столкнулся с гну и не гну и тоже подумал о питонах.

GblGbl ★★★★★
()
Ответ на: комментарий от anonymous

А я, всегда считал что:

Shell — это командная оболочка. Но это не просто промежуточное звено между пользователем и операционой системой, это еще и мощный язык программирования. Программы на языке shell называют сценариями, или скриптами. Фактически, из скриптов доступен полный набор команд, утилит и программ UNIX. Если этого недостаточно, то к вашим услугам внутренние команды shell — условные операторы, операторы циклов и пр., которые увеличивают мощь и гибкость сценариев. Shell-скрипты исключительно хороши при программировании задач администрирования системы и др., которые не требуют для своего создания полновесных языков программирования.


И еще:

Для каких задач неприменимы скрипты

  • для ресурсоемких задач, особенно когда важна скорость исполнения (поиск, сортировка и т.п.)

  • для задач, связанных с выполнением математических вычислений, особенно это касается вычислений с плавающей запятой, вычислений с повышенной точностью, комплексных чисел (для таких задач лучше использовать C++ или FORTRAN)

  • для кросс-платформенного программирования (для этого лучше подходит язык C)

  • для сложных приложений, когда структурирование является жизненной необходимостью (контроль за типами переменных, прототипами функций и т.п.)

  • для целевых задач, от которых может зависеть успех предприятия.

  • когда во главу угла поставлена безопасность системы, когда необходимо обеспечить целостность системы и защитить ее от вторжения, взлома и вандализма.

  • для проектов, содержащих компоненты, очень тесно взаимодействующие между собой.

  • для задач, выполняющих огромный объем работ с файлами

  • для задач, работающих с многомерными массивами

  • когда необходимо работать со структурами данных, такими как связанные списки или деревья

  • когда необходимо предоставить графический интерфейс с пользователем (GUI)

  • когда необходим прямой доступ к аппаратуре компьютера

  • когда необходимо выполнять обмен через порты ввода-вывода или сокеты

  • когда необходимо использовать внешние библиотеки

  • для проприетарных, «закрытых» программ (скрипты представляют из себя исходные тексты программ, доступные для всеобщего обозрения)
julixs ★★★
()
Ответ на: комментарий от tailgunner

хотя параноики могут пофантазировать о повсеместном внедрении Dbus

s/пофантазировать/увидеть в кошмарах/

annulen ★★★★★
()
Ответ на: комментарий от annulen

параноики могут пофантазировать о повсеместном внедрении Dbus

s/пофантазировать/увидеть в кошмарах/

Глядя на триумфальное продавливание systemd, я думаю, что мы увидим это наяву.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

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

AndreyKl ★★★★★
()
Ответ на: комментарий от tailgunner

навскидку

ps auxv | proclist-lens

proclist-lens - команда не найдена. поставил augeas-lenses, не помогло. убунта. это ты привёл гипотетический пример или оно должно работать?

AndreyKl ★★★★★
()
Ответ на: комментарий от AndreyKl

Гипотетический. Я даже не уверен, что augeas - подходящий инструмент для работы, но из того, о чем я знаю, это самое близкое. И команды oselect тоже нет :/

tailgunner ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.