Каково общее мнение о «бесполезном использовании кошки»?


41

Когда я передаю несколько unix-команд, таких как grep, sed, tr и т. Д., Я склонен указывать входной файл, который обрабатывается с помощью cat. Так что-то вроде cat file | grep ... | awk ... | sed ....

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

Я посмотрел на проблему и наткнулся на статью Википедии о UUOC и награде «Бесполезное использование кошек», и мне кажется, что приведенные аргументы с точки зрения эффективности.

Ближайший вопрос, с которым я здесь столкнулся, был такой: расточительно называть кота? - но это не совсем то, что я спрашиваю.

Я предполагаю, что лагерь UUOC предлагает использовать cmd1 args < file | cmd2 args | cmd3 ..или если команда имеет опцию для чтения из файла, а затем для передачи файла в качестве аргумента.

Но мне cat file | cmd1 ... | cmd2кажется, что намного легче читать и понимать. Мне не нужно запоминать разные способы отправки входных файлов для разных команд, и процесс логически протекает слева направо. Сначала ввод, потом первый процесс ... и так далее.

Я не понимаю, какие аргументы приводятся в отношении бесполезного использования кошки? Я понимаю, что если я запускаю задание cron, которое запускается каждые 2 секунды и выполняет много операций, то в этом случае cat может быть расточительным. Но в противном случае, каково общее согласие относительно использования кошки?


16
Я согласен, что здесь вызов cat может быть неэффективным, но он значительно облегчает понимание и редактирование команды позже, и (что важно, IMO) разделяет каждую отдельную команду на выполнение только одного задания, что значительно упрощает ее выполнение. с.
Фоши

4
Общее мнение заключается в том, что нет консенсуса.
JWG

2
Это в значительной степени дублирует stackoverflow.com/questions/11710552/useless-use-of-cat (хотя и предшествует ему).
трипл

1
Не забывайте, что это < file cmd1 args | cmd2 args ...тоже работает ... так что ваш аргумент " слева направо " является недействительным. Я часто использую это для ясности - порядок, который я показал, может заставить людей остановиться, что нехорошо. С увеличением числа потоков становится нормой, это становится все менее проблемным IMO ...
Attie

Ответы:


21

Это бесполезно в том смысле, что его использование не приводит ни к чему другому, возможно, более эффективные варианты не могут (т.е. дают правильные результаты).

Но catгораздо мощнее, чем просто cat somefile. Посоветуйтесь man catили прочитайте, что я написал в этом ответе . Но если вам абсолютно положительно нужно только содержимое одного файла, вы можете получить некоторое преимущество в производительности, если не будете использовать catсодержимое файла.

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

Это также зависит от того, что вы пишете. Если это ваша собственная оболочка и удобные методы для вашего настольного компьютера, никто, кроме вас, не будет заботиться. Если вы наткнетесь на случай, когда следующему инструменту в цепочке будет лучше искать его, и распространите его как часто используемую часть программного обеспечения в какой-то минимальной системе Linux на низкопроизводительном маршрутизаторе или аналогичном устройстве с реальными ограничениями на способность обработки, это другое. Это всегда зависит от контекста.


3
Затраты на производительность незначительны? Во многих случаях это: oletange.blogspot.dk/2013/10/useless-use-of-cat.html
Оле Танге,

15

В каждодневном использовании командной строки это не сильно отличается. Особенно вы не заметите никакой разницы в скорости, поскольку время на ЦП, которое не используется cat, ваш ЦП просто простаивает. Даже если вы просматриваете сотни или тысячи (или даже сотни тысяч) элементов во всей практичности, это не будет иметь большого значения, если вы не находитесь в очень загруженной системе (Load Average / N CPU> 1).

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

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

Речь идет не о сохранении одного дескриптора файла, 17 КБ ОЗУ и 0,004 секунды процессорного времени. Речь идет о всей философии использования UNIX. «Сила левого поворота» на моей иллюстрации - это не просто перенаправление ввода, это философия UNIX. Полное уклонение от этого сделает вас намного лучше, чем окружающие, и вы заслужите уважение тех, кто понимает.


2
Если вы думаете о повороте налево на оживленную трассу с 6 полосами движения без светофора, то вам может потребоваться повернуть направо или выбрать другой маршрут. * nix дает вам выбор нескольких маршрутов. Это вопрос личных предпочтений и читабельности. Если вы хотите «cat file | cmd1 | cat | cmd2 | more», продолжайте. (Иногда это полезно, если cmd1 разбивает на страницы - cat устранит это.) $ CPU time << $ Brain time.
MikeP

1
@MikeP catне устранит пагинацию, хотя трубопроводы на что - то может устранить пейджинг некоторых приложений.
трипл

14

Я часто использую cat file | myprogramв примерах. Иногда меня обвиняют в бесполезном использовании кошки ( http://www.iki.fi/era/unix/award.html ). Я не согласен по следующим причинам:

Легко понять, что происходит.

При чтении команды UNIX вы ожидаете команду, за которой следуют аргументы и перенаправление. Это есть возможность поставить перенаправление где - нибудь , но это редко - таким образом , у людей будет больше времени на чтение пример. Полагаю

    cat foo | program1 -o option -b option | program2

легче читать, чем

    program1 -o option -b option < foo | program2

Если вы переместите перенаправление в начало, вы вводите в заблуждение людей, которые не привыкли к этому синтаксису:

    < foo program1 -o option -b option | program2

и примеры должны быть легкими для понимания.

Это легко изменить.

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

Подчеркивается, что программа не даст сбой, если STDIN не является обычным файлом.

Не безопасно предполагать, что, если program1 < fooработает, то cat foo | program1также будет работать. Тем не менее, это на практике можно предположить обратное. Эта программа работает, если STDIN является файлом, но завершается неудачей, если вход является каналом, потому что она использует поиск:

    # works
    < foo perl -e 'seek(STDIN,1,1) || die;print <STDIN>'

    # fails
    cat foo | perl -e 'seek(STDIN,1,1) || die;print <STDIN>'

Я посмотрел на снижение производительности на http://oletange.blogspot.dk/2013/10/useless-use-of-cat.html Вывод не использовать, cat file |если сложность обработки похожа на простой grep и производительность важнее, чем удобочитаемость. Для других ситуаций cat file |это хорошо.


1
Наконец, ответ с реальными оценками. Я также отмечаю в своем комментарии, что «иногда» кошка может быть быстрее. Единственный раз, когда я могу себе представить, что «бесполезное использование кошки» является реальным ущербом, это если вы выполняете тривиальную обработку для huuuge-файлов (или если процесс может специально использовать stdin, например команду tail) ... unix.stackexchange. com / a / 225608/8337
rogerdpack

12

Я думаю, что позиция некоторых из тех, кто комментирует что-то, что является UUOC, заключается в том, что если человек действительно понимает синтаксис Unix и оболочки, он не будет использовать cat в этом контексте. Это похоже на использование плохой грамматики: я могу написать предложение, используя плохую грамматику, и все же донести свою мысль, но я также демонстрирую свое плохое понимание языка и, соответственно, мое плохое образование. Поэтому говорить, что что-то является UUOC, - это еще один способ сказать, что кто-то не понимает, что он делает.

Что касается эффективности, то если вы выполняете конвейер из командной строки, машине требуется меньше времени для выполнения, cat somefile |чем вам, чтобы подумать о том, может ли она быть более эффективной в использовании < somefile. Это просто не имеет значения.


6
В течение долгого времени я знал, что существуют другие способы выражения cat somefile | progв shell без cat, например, prog < somefileно они всегда казались мне в неправильном порядке, особенно с помощью цепочки команд, соединенных вместе. Теперь я вижу это так же элегантно, как < somefile progи трюк, спасибо. Я исчерпал оправдания, которые я оставил, чтобы использовать кошку.
Алекс

4

Я не знал о награде до сегодняшнего дня, когда какой-то новичок попытался наколоть UUOC на меня для одного из моих ответов. Это было cat file.txt | grep foo | cut ... | cut .... Я дал ему часть своего разума, и только после этого посетил ссылку, которую он дал мне, ссылаясь на происхождение награды и практику этого. Дальнейшие поиски привели меня к этому вопросу. К сожалению, несмотря на сознательное рассмотрение, ни один из ответов не содержал моего обоснования.

Я не хотел защищаться, обучая его. В конце концов, в мои молодые годы я написал бы команду так, grep foo file.txt | cut ... | cut ...потому что всякий раз, когда вы выполняете частые одиночные grepоперации, вы изучаете расположение аргумента файла, и вы уже знаете, что первым является шаблон, а последующими - имена файлов.

Это был осознанный выбор, когда я ответил на вопрос с catпрефиксом частично из-за причины «хорошего вкуса» (по словам Линуса Торвальдса), но главным образом из-за неотразимой причины работы.

Последняя причина более важна, поэтому я изложу ее в первую очередь. Когда я предлагаю трубопровод в качестве решения, я ожидаю, что его можно будет использовать повторно. Вполне вероятно, что конвейер будет добавлен в конце или объединен в другой конвейер. В этом случае файл , имеющий аргумент Grep щурит многократное использование, и вполне возможно , сделать это тихо и без сообщения об ошибке , если файл существует аргумент. И. е. grep foo xyz | grep bar xyz | wcдаст вам , сколько строк в xyzсодержат в barто время как вы ожидаете , количество строк , которые содержат как fooи bar. Необходимость изменить аргументы команды в конвейере перед ее использованием подвержена ошибкам. Добавьте к этому возможность молчаливых неудач, и это станет особенно коварной практикой.

Не менее важна и первая причина, так как много «хорошего вкуса» просто является интуитивным подсознательным обоснованием таких вещей, как молчаливые неудачи, описанные выше, о которых вы не можете вспомнить в тот момент, когда кто-то, нуждающийся в образовании, говорит «но это не так». этот кот бесполезен ".

Тем не менее, я постараюсь также осознать прежнюю причину «хорошего вкуса», которую я упомянул. Эта причина связана с духом ортогонального дизайна Unix. grepне делает cutи lsне делает grep. Поэтому, по крайней мере, grep foo file1 file2 file3идет вразрез с духом дизайна. Ортогональный способ сделать это cat file1 file2 file3 | grep foo. Теперь, grep foo file1это просто особый случай grep foo file1 file2 file3, и если вы не относитесь к нему одинаково, вы, по крайней мере, используете мозговые циклы, пытаясь избежать бесполезного награждения кошек.

Это приводит нас к аргументу, который grep foo file1 file2 file3объединяет и catобъединяет, так что это уместно, cat file1 file2 file3но потому, что catмы не объединяемся, cat file1 | grep fooпоэтому мы нарушаем дух как catвсемогущего, так и всемогущего Unix. Что ж, если бы это было так, тогда Unix потребовалась бы другая команда, чтобы прочитать вывод одного файла и выложить его на стандартный вывод (не разбивать его на страницы или что-то еще, просто наплевать на стандартный вывод). Таким образом, у вас возникнет ситуация, когда вы говорите cat file1 file2или говорите dog file1и добросовестно помните, чтобы избежать того, cat file1чтобы избежать получения награды, и при этом избегать, dog file1 file2так как, надеюсь, дизайн dogвыдаст ошибку, если будет указано несколько файлов.

Надеемся, что в этот момент вы сочувствуете разработчикам Unix за то, что вы не включили отдельную команду для разбрызгивания файла на стандартный вывод, а также назвали catконкатенацию вместо того, чтобы давать ей другое имя. <edit>есть такая собака, несчастный <оператор. К сожалению, его размещение в конце трубопровода препятствует легкому составлению. Не существует синтаксически или эстетически чистого способа разместить его в начале. К сожалению, недостаточно универсален, поэтому вы начинаете с собаки, а просто добавляете другое имя файла, если хотите, чтобы оно обрабатывалось после предыдущего. (С >другой стороны, он не в два раза хуже. У него почти идеальное расположение в конце. Обычно он не является частью многоразового использования конвейера, и, соответственно, он символически выделяется.)</edit>

Следующий вопрос: почему важно иметь команды, которые просто плюют файл или объединяют несколько файлов в стандартный вывод без какой-либо дальнейшей обработки? Одна из причин - избегать использования каждой отдельной команды Unix, работающей со стандартным вводом, чтобы знать, как анализировать хотя бы один аргумент файла командной строки и использовать его в качестве ввода, если он существует. Вторая причина заключается в том, чтобы пользователям не приходилось запоминать: (а) куда идут аргументы имени файла; и (b) избежать ошибки тихого конвейера, как упомянуто выше.

Это подводит нас к тому, почему grepесть дополнительная логика. Логическое обоснование состоит в том, чтобы обеспечить свободное владение пользователями для команд, которые используются часто и автономно (а не как конвейер). Это небольшой компромисс ортогональности для значительного увеличения удобства использования. Не все команды должны быть разработаны таким образом, и команды, которые не часто используются, должны полностью избегать дополнительной логики аргументов файла (помните, что дополнительная логика приводит к ненужной хрупкости (возможность ошибки)). Исключением является разрешение файловых аргументов, как в случае grep. (кстати, обратите внимание, что у lsнего совершенно другая причина не просто принимать, а в значительной степени требовать аргументы файла)

Наконец, то, что можно было бы сделать лучше, - это если такие исключительные команды, как grep(но не обязательно ls), генерируют ошибку, если доступен стандартный ввод. Это разумно, потому что команды включают логику, которая нарушает ортогональный дух всемогущего Unix для удобства пользователя. Для дальнейшего удобства пользователя, т.е. для предотвращения страданий, вызванных молчаливым отказом, такие команды не должны стесняться нарушать собственное нарушение, предупреждая пользователя, если существует возможность молчаливого отказа.


1
Как обсуждалось на межсайтовом дубликате этого ответа и вопроса, grep pattern f1 f2 f3это не простая конкатенация . grepзнает о файлах и печатает имена файлов (и, необязательно, номера строк и все остальное). grep . /sys/kernel/mm/transparent_hugepage/*хороший способ печати имени файла: file-content с множеством однострочных файлов. Классический дизайн Unix заключается в том, что большинство утилит работают *.txtбез необходимости cat. catпредназначен для объединения нескольких файлов в один поток.
Питер Кордес

@PeterCordes Я не так много писал о grep. Есть существенные проблемы, которые я наблюдал относительно устойчивости к ошибкам / копированию-вставке; Корректность в сравнении с производительностью, которую вы предпочитаете игнорировать в пользу какой-то мелкой / второстепенной проблемы.
Случайная строка

Вы делаете некоторые интересные и правильные замечания, особенно об ошибках, которые вы можете получить при копировании / вставке конвейеров. Я бы посоветовал заменить ваш grepпример такой программой cut, у которой нет причин заботиться о нескольких файлах, и она всегда может быть запитана с его стандартного ввода. Некоторые утилиты, например tr, вообще не принимают файловые аргументы, а работают только как фильтры, поэтому выбор между catи <.
Питер Кордес

1
Моя самая большая проблема с вашим постом заключается в том, что вы не учитываете перенаправление ввода. <file cmd1 | cmd2 >outПризнаюсь, это не замечательно, но к этому вполне можно привыкнуть. Вы продолжаете рассуждать о «духе всемогущего Unix» насмешливо, что для меня совершенно неуместно, потому что звучит так, будто вы либо не понимаете, либо не хотите понять, как на самом деле думали дизайнеры Unix. Хорошо, если вам не нравится дизайн Unix, но он не глуп по своей сути. Я не уверен, предшествовал ли дизайн ОС синтаксису оболочки и как все это развивалось, но catв 1970-м стоило избежать лишнего !
Питер Кордес

1
@PeterCordes В ретроспективе я был довольно скучным в своем ответе, который отвлекает от самого важного момента - правильности во-первых, оптимизации во-вторых. Дополнительный catпомогает повторно использовать и соединять конвейеры, тогда как без него вы можете получить тихие сбои (ищите «тихо» в моем ответе).
случайная строка

2

В защиту бесполезного использования кота

(Несколько абзацев, чтобы помочь сбалансировать цунами ворчащих комментариев против этой практики)

Я слишком много лет использую bash как оболочку, так и в качестве языка сценариев для небольших сценариев (а иногда, к сожалению, для не очень маленьких). Давным-давно я узнал о «бесполезном использовании кошки» (UUoC). Я по-прежнему виновен в этом, по крайней мере, каждую неделю, но, честно говоря, я редко чувствую себя вынужденным избегать этого. Я считаю, что использование catvs < file- это скорее вкус, чем технические различия, и я написал этот ответ, чтобы защитить людей, плохо знакомых с Linux, которые разделяют мой вкус кcatот мысли, что в их пути что-то серьезно не так (и обратите внимание на несколько случаев, когда это происходит). Как и Линус Торвальдс, я также считаю, что зачастую вкус важнее, чем умение. Это не значит, что мой вкус лучше, чем у вас, но это значит, что если что-то имеет плохой вкус, я не сделаю это, не приобретя чего-то достойного.

Уже очевидно, что, как и автор вопроса, я чувствую, что использование cat очень естественно при работе над REPL, например, bash, где я исследую проблему, постепенно наращивая сложные команды. Вот очень типичный пример: у меня есть текстовый файл, и я не знаю о нем много. Я буду печатать, cat fileчтобы получить вкус содержимого. Если выход слишком много я ударил стрелок вверх и в зависимости от обстоятельств , я добавлю | headили | grep fooили | what_everрасширяя свою предыдущую команду, добавив обработку шагов. Этот способ постепенного перехода от простой команды к более сложной путем добавления одного шага обработки за другим мне кажется очень естественным (я делаю то же самое на ipython и мне нравится способpyfunctionalи подобные инструменты программирования охватывают этот стиль). Так что, работая над оболочкой bash, я уверен, что прерывание моего потока для удаления catявляется более бесполезным, чем позволить ему быть и страдать ... ну, в 99,9% случаев это не имеет никакого значения.

Конечно, при написании сценариев все может измениться. Но даже когда я пишу сценарии, я считаю, что люди, которые насмехаются над UUoC, игнорируют важные уроки: «Преждевременная оптимизация - корень всего зла» . И если вы не делаете что-то нетипичное, для UUoC действительно трудно быть местом, где потребуется оптимизация. Конечно, вам определенно нужно знать, что в этом неэффективно (это, кстати, дополнительный вызов процесса, поскольку об этом, похоже, мало кто упоминает). Обладая этими знаниями, если вам доведется работать в тех редких системах, где вызов процесса стоит дорого (например, некоторые встроенные системы или CygWin в меньшей степени), вы будете знать, что делать, если этого требует особая ситуация. Например, если вы звонитеcatмного раз в секунду (кстати, если вы все-таки оказались в таком положении, спросите себя, является ли bash подходящим инструментом для работы). Опять же, хотя: «сначала заставьте его работать правильно, а затем оптимизируйте его при необходимости» .

А как вы объясняете цунами жалоб на UUoC Ника?

Помимо того, что не у всех есть свой вкус, я полагаю, что основная причина, по которой многие люди жалуются на UUoC, не техническая, а человеческая: большинство новичков в Unix не знают об этой < file commandидиоме, поэтому более искушенному человеку хочется сыграть в «Старого гуру». " им. У него также будет возможность использовать причудливые слова («вызов процесса») и коснуться дорогой темы «оптимизация». Хорошее впечатление гарантировано, поэтому очень трудно устоять. Тогда новички примут совет Гуру за чистую монету и в течение долгого времени передадут его другим как «Единственная Истина» (и проголосуйте за этот ответ :-). Забавное примечание: вероятно, так легко исправить bash, чтобы избежать неэффективности UUoC, поэтому возникает вопрос, почему никто не добавил эту функцию или не сделал< filenameкошка файл после стольких лет. Темная душа предположила бы, что некоторые хакеры с седой бородой хотели бы оставить возможность насмехаться над нами ;-)



1

Что было бы действительно хорошо, так это оболочка, которая поддерживает синтаксис, такой как:

< filename cmd | cmd2 cmd2arg1... | cmd3

Между тем, я думаю, что cat filename | realcmd1...это приемлемо, так как он поддерживает синтаксис, стандартизированный с начальными командами, которые требуют имя файла в качестве аргумента.


17
Поддержка Bash и аналогичных оболочек < filename cmd | cmd2 .... Это достаточно близко?
garyjohn

@garyjohn: я думаю, что вы должны опубликовать это как ответ.
Кевин Рейд

14
Обязательный старый комментарий хакерских оболочек: Оболочки в стиле Борна поддерживали, < file command ...по крайней мере, с середины 80-х и, вероятно, еще с 70-х годов, когда shбыл написан оригинал . В более общем случае, перенаправления ввода / вывода анализируются слева направо и могут перемежаться в любом порядке в командной строке. Так cmd <file arg arg...что, также будет в силе.
Дейл Хэгглунд

3
Да, это отчасти из-за того, как легко набрать то, что я изобрел UUOC.
Рэндал Шварц

2
Один сдвинутый символ против четырех несмещенных не такая уж большая разница, и я бы предпочел запустить дополнительный процесс, который даже мой телефон едва замечает, чем направить файл в мое приглашение, что вызывает головную боль каждый раз, когда я его вижу ,
Аарон Миллер

0

Для всех, кто говорит, что кошку приемлемо использовать, потому что она «пахнет» лучше или «более читабельна», я бы сказал следующее:

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

Я также добавлю этот комментарий, так как давний пользователь Linux и администратор / инженер ... (и нас много), это заставляет нас кровоточить, когда мы видим это. Зачем? Потому что он использует ресурсы в системах, над которыми мы жестко контролируем ресурсы. Команда cat и сам канал используют дополнительную память и файловые дескрипторы, которые совершенно бесполезны. Вы связали ресурсы, которые нужны моей системе бесплатно, и вы НИЧЕГО не получили, что может объяснить использование этих ресурсов. Это огромное нет нет.

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

Как домашний пользователь, вы можете учиться на моих советах и ​​узнавать, как лучше делать что-либо, или вы можете выбрать ослепление "запахом" кошек, ваш выбор ... но знайте, что если вы будете открыто использовать эту практику, вас будут звать на этой практике все время, и вам придется молча признать, что они правы, а вы упрямы, потому что это правда. :-)


Я также являюсь давним пользователем Linux (и более ранних * nix) пользователей и разработчиков программного обеспечения. Вы не говорите за меня, когда говорите «это заставляет наши глаза кровоточить», чтобы видеть cat foo.txt | .... Другой ответ хорошо объясняет, почему это может быть полезным. Простая сводка дела в пользу: «$ CPU time << $ Brain time» (как прокомментировал @MikeP выше).
Джим DeLaHunt

Во-первых, это был ответ с 2017 года (способ возродить мертвых LOL). Во-вторых, обратите внимание, что как администратор или разработчик мы всегда должны стремиться минимизировать использование ресурсов, где это возможно. При написании приложений и сервисов вы пытаетесь следить за расходом памяти или ресурсов процессора, которые мало или совсем не приносят пользу правильному приложению / сервису? Ну, UUOC это именно так. Теперь есть совершенно правильное использование cat, я уверен, что это связано с передачей команды ... просто не часто. Так что я не могу говорить за вас, как вы говорите ... но если вы профессионал, я бы удивился, почему вы не с готовностью соглашаетесь (учитывая истинный сценарий UUOC).
Дэвид Дреггорс

Проблема заключается в том, что нехватка памяти или ЦП - это не единственные затраты для разработчика на оптимизацию. Стоимость человеческого времени, чтобы понять проблему, а также написать и отладить реализацию, также являются частью компромисса. Некоторые из ответов здесь основаны на суждении, что человеческое время гораздо более скудно и дорого, чем память или процессор. , ... Но это становится дискуссией, которая противоречит правилам. Пусть голосование за ответы говорит о том, какая точка зрения одобрена суперпользователями.
Джим DeLaHunt
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.