Является ли использование цикла while для обработки текста вообще плохой практикой в оболочках POSIX?
Как отметил Стефан Шазелас , некоторые из причин, по которым не используется оболочка, - это концептуальность , надежность , удобочитаемость , производительность и безопасность .
Этот ответ объясняет аспекты надежности и удобочитаемости :
while IFS= read -r line <&3; do
printf '%s\n' "$line"
done 3< "$InputFile"
Для исполнения , то while
цикл и чтения являются чрезвычайно медленно при чтении из файла или труб, так как для чтения оболочки встроенных читает один символ за один раз.
Как насчет концептуальных аспектов и аспектов безопасности ?
shell
text-processing
cuonglm
источник
источник
yes
записать в файл так быстро?bash
, он читает один размер буфера за раз, попробуйте,dash
например. См. Также unix.stackexchange.com/q/209123/38906Ответы:
Да, мы видим ряд вещей, таких как:
Или хуже:
(не смейся, я видел много таких).
Как правило, от начинающих сценариев оболочки. Это наивные буквальные переводы того, что вы делаете в императивных языках, таких как C или python, но это не то, как вы делаете вещи в оболочках, и эти примеры очень неэффективны, абсолютно ненадежны (потенциально могут привести к проблемам безопасности), и если вам когда-нибудь удастся чтобы исправить большинство ошибок, ваш код становится неразборчивым.
Концептуально
В Си или большинстве других языков строительные блоки находятся всего на один уровень выше компьютерных инструкций. Вы говорите своему процессору, что делать, а затем что делать дальше. Вы берете свой процессор за руку и управляете им: вы открываете этот файл, читаете столько байтов, вы делаете это, вы делаете это с ним.
Оболочки - это язык более высокого уровня. Можно сказать, что это даже не язык. Они раньше всех интерпретаторов командной строки. Работа выполняется теми командами, которые вы запускаете, а оболочка предназначена только для их управления.
Одной из замечательных вещей, которые представил Unix, был канал и те потоки по умолчанию stdin / stdout / stderr, которые все команды обрабатывают по умолчанию.
За 45 лет мы не нашли лучше, чем этот API, чтобы использовать всю мощь команд и заставить их взаимодействовать для выполнения какой-либо задачи. Вероятно, это основная причина, по которой люди до сих пор используют снаряды.
У вас есть режущий инструмент и инструмент для транслитерации, и вы можете просто сделать:
Оболочка просто выполняет сантехнику (открывает файлы, настраивает каналы, вызывает команды), и когда все готово, она просто течет, а оболочка ничего не делает. Инструменты выполняют свою работу одновременно, эффективно в своем собственном темпе с достаточной буферизацией, чтобы не блокировать один другой, а просто красиво и в то же время так просто.
Однако вызов инструмента имеет свою стоимость (и мы разработаем ее с точки зрения производительности). Эти инструменты могут быть написаны с тысячами инструкций на языке C. Необходимо создать процесс, инструмент должен быть загружен, инициализирован, затем очищен, процесс уничтожен и ожидается.
Вызывать
cut
это как открыть кухонный ящик, взять нож, использовать его, помыть, высушить, положить обратно в ящик. Когда вы делаете:Это как для каждой строки файла: взять
read
инструмент из кухонного ящика (очень неуклюжий, потому что он не предназначен для этого ), прочитать строку, вымыть инструмент для чтения, положить его обратно в ящик. Затем запланируйте встречу для инструментаecho
иcut
инструмента, достаньте их из ящика, вызовите их, вымойте их, высушите их, положите обратно в ящик и так далее.Некоторые из этих инструментов (
read
иecho
) построены в большинстве оболочек, но это вряд ли имеет значение здесь , такecho
и по-cut
прежнему должны быть запущены в отдельных процессах.Это как разрезать лук, но помыть нож и положить его обратно в кухонный ящик между каждым кусочком.
Здесь очевидный способ - достать
cut
инструмент из ящика, нарезать весь лук и положить его в ящик после того, как вся работа будет выполнена.Таким образом, в оболочках, особенно для обработки текста, вы вызываете как можно меньше утилит и заставляете их взаимодействовать с задачей, а не запускаете тысячи инструментов подряд, ожидая, пока каждый из них запустится, запустится, очистится перед запуском следующей.
Дальнейшее чтение в ответ Брюса . Низкоуровневые внутренние инструменты обработки текста в оболочках (за исключением, может быть, для
zsh
) ограничены, громоздки и, как правило, не подходят для общей обработки текста.Представление
Как было сказано ранее, запуск одной команды имеет свою стоимость. Огромная стоимость, если эта команда не встроена, но даже если она встроена, цена велика.
И оболочки не предназначены для такой работы, они не претендуют на то, чтобы быть эффективными языками программирования. Это не просто интерпретаторы командной строки. Таким образом, небольшая оптимизация была сделана на этом фронте.
Также оболочки запускают команды в отдельных процессах. Эти строительные блоки не разделяют общую память или состояние. Когда вы делаете a
fgets()
илиfputs()
в C, это функция в stdio. stdio сохраняет внутренние буферы для ввода и вывода для всех функций stdio, чтобы избежать слишком частых системных вызовов.Соответствующие даже встроенные утилиты оболочки (
read
,echo
,printf
) не может сделать это.read
предназначен для чтения одной строки. Если он читает после символа новой строки, это означает, что следующая команда, которую вы запустите, пропустит его. Поэтомуread
приходится читать входные данные по одному байту за раз (некоторые реализации имеют оптимизацию, если входные данные представляют собой обычный файл, поскольку они читают фрагменты и выполняют поиск назад, но это работает только для обычных файлов и,bash
например, читает только 128-байтовые фрагменты, что все еще намного меньше чем текстовые утилиты сделают).То же самое на стороне вывода,
echo
не может просто буферизовать свой вывод, он должен выводить его сразу, потому что следующая команда, которую вы выполняете, не будет использовать этот буфер.Очевидно, что последовательное выполнение команд означает, что вы должны их ждать, это небольшой танец планировщика, который дает контроль над оболочкой, инструментами и обратно. Это также означает (в отличие от использования долго работающих экземпляров инструментов в конвейере), что вы не можете использовать несколько процессоров одновременно, когда они доступны.
Между этим
while read
циклом и (предположительно) эквивалентомcut -c3 < file
, в моем быстром тесте, в моих тестах соотношение времени процессора составляет около 40000 (одна секунда против полдня). Но даже если вы используете только встроенные функции оболочки:(здесь с
bash
), это все еще около 1: 600 (одна секунда против 10 минут).Надежность / разборчивость
Очень трудно правильно понять этот код. Примеры, которые я привел, слишком часто встречаются в дикой природе, но в них много ошибок.
read
это удобный инструмент, который может делать много разных вещей. Он может читать ввод от пользователя, разбивать его на слова для хранения в различных переменных.read line
вовсе не читать строку ввода, или , может быть , он читает строку в совершенно особым образом. На самом деле он читает слова из входных данных, разделенных этими словами,$IFS
и где обратную косую черту можно использовать для экранирования или символов новой строки.Со значением по умолчанию
$IFS
, на входе, как:read line
будет хранить"foo/bar baz"
в$line
не ," foo\/bar \"
как вы ожидали бы.Чтобы прочитать строку, вам действительно нужно:
Это не очень интуитивно понятно, но так оно и есть, помните, что оболочки не предназначены для такого использования.
То же самое для
echo
.echo
расширяет последовательности. Вы не можете использовать его для произвольного содержимого, такого как содержимое случайного файла. Вам нужноprintf
здесь вместо этого.И, конечно, есть типичное забывание процитировать вашу переменную, в которую все попадают. Итак, это больше:
Теперь еще несколько предостережений:
zsh
, что это не работает, если ввод содержит символы NUL, в то время как по крайней мере текстовые утилиты GNU не будут иметь проблемы.Если мы хотим решить некоторые из этих проблем выше, это становится:
Это становится все менее и менее разборчивым.
Существует ряд других проблем с передачей данных командам через аргументы или извлечением их выходных данных в переменных:
-
(или+
иногда)expr
,test
...Соображения безопасности
Когда вы начинаете работать с переменными оболочки и аргументами команд , вы входите в минное поле.
Если вы забыли заключить в кавычки свои переменные , забыли маркер конца опции , работали в локалях с многобайтовыми символами (норма в наши дни), вы наверняка внесете ошибки, которые рано или поздно станут уязвимыми.
Когда вы можете использовать петли.
TBD
источник
cut
например, это эффективно.cut -f1 < a-very-big-file
эффективен, так же эффективен, как если бы вы написали его на C. То, что ужасно неэффективно и подвержено ошибкам, вызываетсяcut
для каждой строкиa-very-big-file
в цикле оболочки, что и было сделано в этом ответе. Это совпадает с вашим последним утверждением о написании ненужного кода, что заставляет меня задуматься, может быть, я не понимаю ваш комментарий.Что касается концептуальности и разборчивости, оболочки обычно интересуются файлами. Их «адресуемая единица» - это файл, а «адрес» - это имя файла. Оболочки имеют все виды методов тестирования на существование файла, тип файла, форматирование имени файла (начиная с подстановки). У оболочек очень мало примитивов для работы с содержимым файлов. Программисты оболочки должны вызывать другую программу для работы с содержимым файла.
Как вы уже заметили, из-за ориентации файла и имени файла выполнение манипуляции с текстом в оболочке происходит очень медленно, но также требует нечеткого и искаженного стиля программирования.
источник
Есть несколько сложных ответов, дающих много интересных деталей для фанатов среди нас, но это действительно довольно просто - обработка большого файла в цикле оболочки слишком медленная.
Я думаю, что спрашивающий интересен в типичном типе сценария оболочки, который может начинаться с некоторого разбора командной строки, настройки среды, проверки файлов и каталогов и немного большей инициализации, прежде чем приступить к своей основной работе: пройти большой строковый текстовый файл.
Для первых частей (
initialization
) обычно не имеет значения, что команды оболочки медленные - это всего лишь несколько десятков команд, возможно, с несколькими короткими циклами. Даже если мы напишем эту часть неэффективно, обычно для инициализации потребуется меньше секунды, и это нормально - это происходит только один раз.Но когда мы приступаем к обработке большого файла, в котором могут быть тысячи или миллионы строк, для сценария оболочки не подходит длительная доля секунды (даже если это всего несколько десятков миллисекунд) для каждой строки, как это может сложить до нескольких часов.
Именно тогда нам нужно использовать другие инструменты, и прелесть сценариев оболочки Unix в том, что они делают это очень простым для нас.
Вместо того, чтобы использовать цикл для просмотра каждой строки, нам нужно пропустить весь файл через конвейер команд . Это означает, что вместо того, чтобы вызывать команды тысячи или миллионы раз, оболочка вызывает их только один раз. Это правда, что эти команды будут иметь циклы для построчной обработки файла, но они не являются сценариями оболочки и предназначены для быстрой и эффективной работы.
В Unix есть много замечательных встроенных инструментов, от простых до сложных, которые мы можем использовать для создания наших конвейеров. Я обычно начинаю с простых и использую только более сложные, когда это необходимо.
Я также попытался бы придерживаться стандартных инструментов, доступных на большинстве систем, и стараться, чтобы мое использование было переносимым, хотя это не всегда возможно. И если ваш любимый язык Python или Ruby, возможно, вы не будете возражать против дополнительных усилий, чтобы убедиться, что он установлен на каждой платформе, на которой должно работать ваше программное обеспечение :-)
Простые инструменты включают в себя
head
,tail
,grep
,sort
,cut
,tr
,sed
,join
(при слиянии 2 файлов), иawk
остроты, среди многих других. Удивительно, что некоторые люди могут делать с сопоставлением с шаблоном иsed
командами.Когда это становится более сложным, и вам действительно нужно применить некоторую логику к каждой строке,
awk
хороший вариант - либо однострочный (некоторые люди помещают целые сценарии awk в «одну строку», хотя это не очень удобно для чтения), либо в короткий внешний скрипт.Как
awk
и интерпретируемый язык (например, ваша оболочка), удивительно, что он может выполнять построчную обработку так эффективно, но он специально создан для этого и действительно очень быстр.Кроме того, существует
Perl
огромное количество других языков сценариев, которые очень хорошо обрабатывают текстовые файлы, а также содержат множество полезных библиотек.И наконец, есть старый добрый C, если вам нужна максимальная скорость и высокая гибкость (хотя обработка текста немного утомительна). Но это, вероятно, очень плохое использование вашего времени для написания новой C-программы для каждой задачи обработки файлов, с которой вы сталкиваетесь. Я много работаю с CSV-файлами, поэтому я написал несколько универсальных утилит на C, которые я могу использовать во многих различных проектах. По сути, это расширяет диапазон «простых, быстрых инструментов Unix», которые я могу вызывать из своих сценариев оболочки, так что я могу обрабатывать большинство проектов только за счет написания сценариев, что намного быстрее, чем написание и отладка кода C на каждый раз!
Некоторые последние подсказки:
export LANG=C
, иначе многие инструменты будут обрабатывать ваши обычные ASCII-файлы как Unicode, делая их намного медленнееexport LC_ALL=C
если вы хотитеsort
производить последовательный порядок, независимо от среды!sort
ваши данные, это, вероятно, займет больше времени (и ресурсов: процессор, память, диск), чем все остальное, поэтому постарайтесь свести к минимуму количествоsort
команд и размер файлов, которые они сортируютисточник
Да, но...
Правильный ответ Stéphane Chazelas основан на оболочку концепции делегирования каждый текст работы в конкретные бинарные файлы, как
grep
,awk
,sed
и другие.Поскольку bash способен на многое, он сам может сбросить вилки (даже чем запускать другого переводчика для выполнения всей работы).
Для примера, посмотрите на этот пост:
https://stackoverflow.com/a/38790442/1765658
а также
https://stackoverflow.com/a/7180078/1765658
проверить и сравнить ...
Конечно
Там нет соображений о вводе пользователя и безопасности !
Не пишите веб-приложение под Bash !
Но для многих задач администрирования сервера, где Баш может быть использован вместо оболочки , используя встроенные функции Баша может быть очень эффективными.
Мой смысл:
Написание таких инструментов, как bin utils , не такая же работа, как системное администрирование.
Так что не такие же люди!
Где системные администраторы должны знать
shell
, они могут писать прототипы , используя его предпочтительный (и самый известный) инструмент.Если эта новая утилита (прототип) действительно полезна, некоторые другие люди могут разработать специальный инструмент, используя более подходящий язык.
источник
bash
. (в 3 раза быстрее с ksh93 в моем тесте на моей системе).bash
как правило, самая медленная оболочка. Дажеzsh
в два раза быстрее по этому сценарию. У вас также есть несколько проблем с не заключенными в кавычки переменными и использованиемread
. Таким образом, вы на самом деле иллюстрируете многие из моих пунктов здесь.sh
, Awk , Sed ,grep
,ed
,ex
,cut
,sort
,join
... все с большей надежностью , чем Bash или Perl.bash
установленными по умолчанию.bash
встречается в основном только на Apple MacOS и систем GNU (я предполагаю , что это то , что вы называете основных дистрибутивов ), хотя многие системы также имеют его в качестве дополнительного пакета (напримерzsh
,tcl
,python
...)