В ответ на Recursive Descent CSV parser in BASH, I (the original author оба сообщений) сделал следующую попытку перевести его в AWK сценарий, для сравнения скорости обработки данных с этими языками сценариев. Перевод не является переводом 1: 1 из-за нескольких смягчающих факторов, но для тех, кто заинтересован, эта реализация быстрее при обработке строк, чем другая.AWK: Рекурсивный спуск CSV Parser
Первоначально у нас было несколько вопросов, которые были отменены благодаря Джонатану Леффлеру. Хотя название говорит CSV
, мы обновили код до DSV
, что означает, что вы можете указать любой отдельный символ в качестве разделителя полей, если вы сочтете это необходимым.
Этот код готов к вскрытию.
Основные характеристики
- Нет наложенные ограничения на длину ввода, длины поля, или количество поля
- Буквальное Цитируется Поля через двойные кавычки
"
- ANSI C Управляющие последовательности как defined here в разделе 1.1.2 [1] [2] [3]
- Пользовательский разделитель ввода: The Art Of UNIX Programming (DSV) [4]
- Пользовательские Выход Сами разделяющие [5]
- UCS-2 и UCS-4 Escape-последовательности [6]
[1] Цитируемые поля являются буквальным содержанием, поэтому интерпретации последовательности исключений не выполняются на цитированном контенте. Однако можно добиться конкатенации кавычек, простого текста и интерпретированных последовательностей в одном поле для достижения желаемого эффекта. Например:
one,two,three:\t"Little Endians," and one Big Endian Chief
Есть три поля линии CSV, где третье поле эквивалентно:
three: Little Endians, and one Big Endian Chief
[2] примеров, описанных в опорном материале как «реализация конкретной», или обладающие «неопределенным поведением», не будут поддерживаться, поскольку они не являются переносимыми по определению или слишком неоднозначными, чтобы быть надежными. Если escape-последовательность не определена здесь или в справочном материале, обратная косая черта будет проигнорирована, и один самый следующий символ будет рассматриваться как значение обычного текста. Целочисленные escape-последовательности символа значения не будут поддерживаться, это ненадежный метод, который не масштабируется хорошо на нескольких платформах и излишне, увеличивает сложность анализа по доверенности проверки.
[3] Октальные символы escap должны быть в 3-значном восьмеричном формате. Если это не восьмеричная восьмеричная escape-последовательность, это однозначная escape-последовательность. Шестнадцатеричные escape-последовательности должны быть в двухзначном шестнадцатеричном формате. Если первые два символа, следующие за идентификатором escape-последовательности, недействительны, интерпретация не будет выполнена, и сообщение будет напечатано при стандартной ошибке. Любые оставшиеся шестнадцатеричные цифры игнорируются.
[4] Пользовательский разделитель ввода iDelimiter
должен быть одним символом. Многострочные записи не будут поддерживаться, и использование такого противоречия всегда должно быть неодобрительно. Это уменьшает переносимость записи данных, что делает ее специфичной для файла, местоположение и происхождение которого (внутри этого файла) могут быть неизвестны. Например, grep
в файле для содержимого может возвращать неполную запись, потому что содержимое может начинаться с любой предыдущей строки, ограничивая сбор данных полным анализом базы данных сверху вниз.
[5] Пользовательский разделитель oDelimiter
может быть любым желательным строковым значением. Вывод скрипта всегда завершается одной новой строкой. Это функция правильного вывода терминала. В противном случае ваш разобранный вывод CSV и подсказка терминала будут использовать одну и ту же строку, создающую запутанную ситуацию. Кроме того, большинство интерпретаторов, таких как консоли, являются линейными устройствами, которые ожидают, что новая строка будет сигнализировать о завершении записи ввода-вывода. Если вы обнаружите, что конечная новая строка нежелательна, обрезайте ее.
[6] 16-битные Юникода управляющие последовательности доступны через следующие обозначения:
\uHHHH Unicode character with hex value HHHH (4 digits)
и 32-битные Unicode управляющие последовательности поддерживаются с помощью:
\UHHHHHHHH Unicode character with hex value HHHHHHHH (8 digits)
Особая благодарность всем членам сообщества SO, чей опыт, время и вклад привели меня к созданию такого замечательно полезного инструмента для обработки информации.
Листинг: dsv.awk
#!/bin/awk -f
#
###############################################################
#
# ZERO LIABILITY OR WARRANTY LICENSE YOU MAY NOT OWN ANY
# COPYRIGHT TO THIS SOFTWARE OR DATA FORMAT IMPOSED HEREIN
# THE AUTHOR PLACES IT IN THE PUBLIC DOMAIN FOR ALL USES
# PUBLIC AND PRIVATE THE AUTHOR ASKS THAT YOU DO NOT REMOVE
# THE CREDIT OR LICENSE MATERIAL FROM THIS DOCUMENT.
#
###############################################################
#
# Special thanks to Jonathan Leffler, whose wisdom, and
# knowledge defined the output logic of this script.
#
# Special thanks to GNU.org for the base conversion routines.
#
# Credits and recognition to the original Author:
# Triston J. Taylor whose countless hours of experience,
# research and rationalization have provided us with a
# more portable standard for parsing DSV records.
#
###############################################################
#
# This script accepts and parses a single line of DSV input
# from <STDIN>.
#
# Record fields are seperated by command line varibale
# 'iDelimiter' the default value is comma.
#
# Ouput is seperated by command line variable 'oDelimiter'
# the default value is line feed.
#
# To learn more about this tool visit StackOverflow.com:
#
# http://stackoverflow.com/questions/10578119/
#
# You will find there a wealth of information on its
# standards and development track.
#
###############################################################
function NextSymbol() {
strIndex++;
symbol = substr(input, strIndex, 1);
return (strIndex < parseExtent);
}
function Accept(query) {
#print "query: " query " symbol: " symbol
if (symbol == query) {
#print "matched!"
return NextSymbol();
}
return 0;
}
function Expect(query) {
# special case: empty query && symbol...
if (query == nothing && symbol == nothing) return 1;
# case: else
if (Accept(query)) return 1;
msg = "dsv parse error: expected '" query "': found '" symbol "'";
print msg > "/dev/stderr";
return 0;
}
function PushData() {
field[fieldIndex++] = fieldData;
fieldData = nothing;
}
function Quote() {
while (symbol != quote && symbol != nothing) {
fieldData = fieldData symbol;
NextSymbol();
}
Expect(quote);
}
function GetOctalChar() {
qOctalValue = substr(input, strIndex+1, 3);
# This isn't really correct but its the only way
# to express 0-255. On unicode systems it won't
# matter anyway so we don't restrict the value
# any further than length validation.
if (qOctalValue ~ /^[0-7]{3}$/) {
# convert octal to decimal so we can print the
# desired character in POSIX awks...
n = length(qOctalValue)
ret = 0
for (i = 1; i <= n; i++) {
c = substr(qOctalValue, i, 1)
if ((k = index("", c)) > 0)
k-- # adjust for 1-basing in awk
ret = ret * 8 + k
}
strIndex+=3;
return sprintf("%c", ret);
# and people ask why posix gets me all upset..
# Special thanks to gnu.org for this contrib..
}
return sprintf("\0"); # if it wasn't 3 digit octal just use zero
}
function GetHexChar(qHexValue) {
rHexValue = HexToDecimal(qHexValue);
rHexLength = length(qHexValue);
if (rHexLength) {
strIndex += rHexLength;
return sprintf("%c", rHexValue);
}
# accept no non-sense!
printf("dsv parse error: expected " rHexLength) > "/dev/stderr";
printf("-digit hex value: found '" qHexValue "'\n") > "/dev/stderr";
}
function HexToDecimal(hexValue) {
if (hexValue ~ /^[[:xdigit:]]+$/) {
# convert hex to decimal so we can print the
# desired character in POSIX awks...
n = length(hexValue)
ret = 0
for (i = 1; i <= n; i++) {
c = substr(hexValue, i, 1)
c = tolower(c)
if ((k = index("", c)) > 0)
k-- # adjust for 1-basing in awk
else if ((k = index("abcdef", c)) > 0)
k += 9
ret = ret * 16 + k
}
return ret;
# and people ask why posix gets me all upset..
# Special thanks to gnu.org for this contrib..
}
return nothing;
}
function BackSlash() {
# This could be optimized with some constants.
# but we generate the data here to assist in
# translation to other programming languages.
if (symbol == iDelimiter) { # separator precedes all sequences
fieldData = fieldData symbol;
} else if (symbol == "a") { # alert
fieldData = sprintf("%s\a", fieldData);
} else if (symbol == "b") { # backspace
fieldData = sprintf("%s\b", fieldData);
} else if (symbol == "f") { # form feed
fieldData = sprintf("%s\f", fieldData);
} else if (symbol == "n") { # line feed
fieldData = sprintf("%s\n", fieldData);
} else if (symbol == "r") { # carriage return
fieldData = sprintf("%s\r", fieldData);
} else if (symbol == "t") { # horizontal tab
fieldData = sprintf("%s\t", fieldData);
} else if (symbol == "v") { # vertical tab
fieldData = sprintf("%s\v", fieldData);
} else if (symbol == "0") { # null or 3-digit octal character
fieldData = fieldData GetOctalChar();
} else if (symbol == "x") { # 2-digit hexadecimal character
fieldData = fieldData GetHexChar(substr(input, strIndex+1, 2));
} else if (symbol == "u") { # 4-digit hexadecimal character
fieldData = fieldData GetHexChar(substr(input, strIndex+1, 4));
} else if (symbol == "U") { # 8-digit hexadecimal character
fieldData = fieldData GetHexChar(substr(input, strIndex+1, 8));
} else { # symbol didn't match the "interpreted escape scheme"
fieldData = fieldData symbol; # just concatenate the symbol
}
NextSymbol();
}
function Line() {
if (Accept(quote)) {
Quote();
Line();
}
if (Accept(backslash)) {
BackSlash();
Line();
}
if (Accept(iDelimiter)) {
PushData();
Line();
}
if (symbol != nothing) {
fieldData = fieldData symbol;
NextSymbol();
Line();
} else if (fieldData != nothing) PushData();
}
BEGIN {
# State Variables
symbol = ""; fieldData = ""; strIndex = 0; fieldIndex = 0;
# Output Variables
field[itemIndex] = "";
# Control Variables
parseExtent = 0;
# Formatting Variables (optionally set on invocation line)
if (iDelimiter != "") {
# the algorithm in place does not support multi-character delimiter
if (length(iDelimiter) > 1) { # we have a problem
msg = "dsv parse: init error: multi-character delimiter detected:";
printf("%s '%s'", msg, iDelimiter);
exit 1;
}
} else {
iDelimiter = ",";
}
if (oDelimiter == "") oDelimiter = "\n";
# Symbol Classes
nothing = "";
quote = "\"";
backslash = "\\";
getline input;
parseExtent = (length(input) + 2);
# parseExtent exceeds length because the loop would terminate
# before parsing was complete otherwise.
NextSymbol();
Line();
Expect(nothing);
}
END {
if (fieldIndex) {
fieldIndex--;
for (i = 0; i < fieldIndex; i++)
{
printf("%s", field[i] oDelimiter);
}
print field[i];
}
}
Как запустить скрипт "как Pro"
# Spit out some CSV "newline" delimited:
echo 'one,two,three,AWK,CSV!' | awk -f dsv.awk
# Spit out some CSV "tab" delimited:
echo 'one,two,three,AWK,CSV!' | awk -v oDelimiter=$'\t' -f dsv.awk
# Spit out some CSV "ASCII Group Separator" delimited:
echo 'one,two,three,AWK,CSV!' | awk -v oDelimiter=$'\29' -f dsv.awk
Если вам нужны некоторые управления пользовательского вывода сепараторов, но не уверены, что использовать, вы можете обратиться к this handy ASCII chart
Будущие планы:
- библиотека C Реализация консольного приложения
- C Реализация
- Представление в The Internet Engineering Task Force для возможной стандартизации
Philosp hy
Временные последовательности всегда должны использоваться для создания многострочных данных поля в базе данных на основе строк, а цитирование всегда должно использоваться для сохранения и конкатенации содержимого поля записи. Это самый простой (и, следовательно, самый эффективный) способ реализации парсера записи этого типа.Я рекомендую всем разработчикам программного обеспечения и образовательным учреждениям заняться этим делом и заявить об этом, чтобы обеспечить переносимость и точное получение разделенных разделителями записей на основе строк.
CSV не имеет официальных спецификаций, кроме RFC 4180, и не содержит никаких полезных переносных типов записей. Я надеюсь, что разработчик с опытом более 15 лет станет официально признанным стандартом для переносных CSV/DSV Records.
Если вы хотите дать обзор этого сценария или самого документа: .. ** [Вы можете отправить обзор кода] (http://codereview.stackexchange.com/questions/11803/rate-my- csv-dsv-parser-code-and-presentation) ** –
Ссылка на диаграмму Ascii нарушена. Я не знаю оригинальные «функции», предлагаемые этим графиком, но есть много доступных в режиме онлайн, и 'man 7 ascii' может вывести его на локальную CMD-линию. – shellter