Мы используем sqllldr от Oracle для загрузки файлов данных, созданных третьей стороной. Файлы данных различаются по размеру, а некоторые из них очень большие.
Поля файла данных разделяются символом «|».
Пример: field1|field2|field3|field4|field5
field3
может быть:
value1|value2|value3
Мне нужно заключить field3
в кавычки, если он содержит |.
Я сделал для этого сценарий оболочки, но он немного медленный — около 16 минут на обработку файла с 47 000 000 строк.
Я думал сделать это в awk, но я не настолько знаком с синтаксисом, а сроки не позволяют учиться/разрабатывать/отлаживать.
Будет ли это значительно быстрее в awk?
Есть ли простой способ сделать это?
Спасибо
Стало бы field1|field2|value1|value2|value3|field4|field5
field1|field2|"value1|value2|value3"|field4|field5
?
@WilliamPursell Извините, я забыл упомянуть, что известно, что существует 5 полей, поэтому поле 3 находится между полем 2 и полем 4.
@dawg Да, точно
Данный:
$ cat file
field1|field2|field3|field4|field5
field1|field2||field4|field5
field1|field2|value1|value2|field4|field5
field1|field2|value1|value2|value3|field4|field5
Вы можете использовать этот awk:
awk ' BEGIN{FS=OFS = "|"} # sep fields on |
NF<=5{print; next} # if there are <=5, we are done with line
{s=$1 OFS $2 OFS "\"" # form first 2 fields + "
# now loop through the extra fields adding to string after quote:
for (i=3;i<=NF-5+3;i++) s=(i<NF-5+3) ? s $i OFS : s $i
s=s "\"" OFS $(NF-5+4) OFS $(NF) # finish the string
print s # then print it
}' file
Отпечатки:
field1|field2|field3|field4|field5
field1|field2||field4|field5
field1|field2|"value1|value2"|field4|field5
field1|field2|"value1|value2|value3"|field4|field5
И да - это было бы значительно быстрее с awk по сравнению с только оболочкой.
Если вам нужен один лайнер, я бы использовал Perl следующим образом:
perl -F'[|]' -lpE 's/^([^|]+\|[^|]+\|)(.*)(\|[^|]+\|[^|]+)$/\1"\2"\3/ if scalar @F!=5' file
Тот же вывод.
Ух ты! Выглядит отлично - посмотрю вечером, когда подключусь к работе. Спасибо!
Я протестировал это, и это (awk, а не perl) — единственное работающее решение. Некоторые другие тоже работали, за исключением того, что они заключали в двойные кавычки все третьи поля, независимо от того, были ли лишние «|» или нет. Большое спасибо @dawg!!! И спасибо всем за неожиданно быстрые ответы!
Теперь я потрачу неделю или около того, пытаясь понять, как работает awk ;о)
@philb опубликованное мной решение не заключает в двойные кавычки все третьи поля. Можете ли вы сказать мне, как это не работает?
@ed-morton Извините, мой комментарий был расплывчатым. Я сказал "некоторые" - но не твое. Ваш тоже сработал. Я отметил это как решение, потому что каким-то образом я увидел ваш ответ только после того, как увидел этот...
Я отвечал на ваше заявление о том, что это решение «является единственным решением, которое работает». Вы видели мой ответ после этого, потому что я ответил после этого, вот и все. Надеюсь, вы приняли ответ, который вы собираетесь использовать, а не первый ответ, который вы получили, но в любом случае не беспокойтесь, мне просто любопытно, была ли у меня проблема после просмотра вашего комментария, спасибо, что сообщили мне, что это не так. .
@philb: Спасибо, что приняли! Я добавил комментарии, чтобы помочь вам понять awk...
Используйте этот однострочник Perl, используя splice, который выполняется ~1,5 минуты на 47e6 строк:
perl -F'[|]' -lane '@first = splice @F, 0, 2; @last = splice @F, -2, 2; print join "|", @first, ( @F > 1 ? q{"} . ( join "|", @F ) . q{"} : @F ), @last;' in_file
Вход:
field1|field2|field3|field4|field5
field1|field2||field4|field5
field1|field2|val1|val2|val3|field4|field5
Выход:
field1|field2|"field3"|field4|field5
field1|field2|""|field4|field5
field1|field2|"val1|val2|val3"|field4|field5
Однострочник Perl использует следующие флаги командной строки:-e
: говорит Perl искать код в строке, а не в файле.-n
: перебирать ввод по одной строке за раз, назначая его $_
по умолчанию.-l
: удалите разделитель строк ввода (по умолчанию "\n"
в *NIX) перед выполнением кода в строке и добавьте его при печати.-a
: разделить $_
на массив @F
по пробелам или по регулярному выражению, указанному в опции -F
.-F'/[|]/'
: Разделить на @F
на |
, а не на пробел.
СМОТРИТЕ ТАКЖЕ:
perldoc perlrun: как запустить интерпретатор Perl: переключатели командной строки
Ориентир:
# Make input file with 47e6 lines:
perl -le '
$s =
"field1|field2|field3|field4|field5
field1|field2||field4|field5
field1|field2|val1|val2|val3|field4|field5";
print $s for 1..15_666_667;
' > in_file.txt
wc -l in_file.txt
# 47_000_001
time perl -F'[|]' -lane '@first = splice @F, 0, 2; @last = splice @F, -2, 2; print join "|", @first, ( @F > 1 ? q{"} . ( join "|", @F ) . q{"} : @F ), @last;' in_file.txt > out_file.txt
Работает в среднем 1 мин 31 сек. Измерено 3 раза с использованием Perl 5, версии 30, Subversion 3 (v5.30.3), созданной для darwin-thread-multi-2level, работающей на MacBook Pro, macOS 10.14.6.
С любым awk в любой оболочке на каждой машине Unix:
$ awk -F'|' 'NF>5{sub(/^([^|]*\|){2}/,"&\""); sub(/(\|[^|]*){2}$/,"\"&")} 1' file
field1|field2|field3|field4|field5
field1|field2||field4|field5
field1|field2|"value1|value2"|field4|field5
field1|field2|"value1|value2|value3"|field4|field5
Альтернативы:
С sed, у которого есть -E
для включения ERE (например, GNU и BSD/OSX sed):
$ sed -E 's/^(([^|]*\|){2})(.*\|.*)((\|[^|]*){2})/\1"\3"\4/' file
field1|field2|field3|field4|field5
field1|field2||field4|field5
field1|field2|"value1|value2"|field4|field5
field1|field2|"value1|value2|value3"|field4|field5
С любым POSIX sed:
$ sed 's/^\(\([^|]*|\)\{2\}\)\(.*|.*\)\(\(|[^|]*\)\{2\}\)/\1"\3"\4/' file
field1|field2|field3|field4|field5
field1|field2||field4|field5
field1|field2|"value1|value2"|field4|field5
field1|field2|"value1|value2|value3"|field4|field5
С GNU awk для gensub():
$ awk '{$0=gensub(/^(([^|]*\|){2})(.*\|.*)((\|[^|]*){2})$/,"\\1\"\\3\"\\4",1)} 1' file
field1|field2|field3|field4|field5
field1|field2||field4|field5
field1|field2|"value1|value2"|field4|field5
field1|field2|"value1|value2|value3"|field4|field5
С GNU awk для третьего аргумента для соответствия():
$ awk 'match($0,/^(([^|]*\|){2})(.*\|.*)((\|[^|]*){2})$/,a){$0=a[1] "\"" a[3] "\"" a[4]} 1' file
field1|field2|field3|field4|field5
field1|field2||field4|field5
field1|field2|"value1|value2"|field4|field5
field1|field2|"value1|value2|value3"|field4|field5
Вышеприведенное было выполнено с образцом входного файла , созданным @dawg :
$ cat file
field1|field2|field3|field4|field5
field1|field2||field4|field5
field1|field2|value1|value2|field4|field5
field1|field2|value1|value2|value3|field4|field5
Еще один авк
$ cat philb2
field1|field2|field3|field4|field5
field1|field2||field4|field5
field1|field2|value1|value2|field4|field5
field1|field2|value1|value2|value3|field4|field5
$ awk -F"|" ' NF==5{print; next} {OFS = "|"; v1=$(NF);v2=$(NF-1);f1=$1;f2=$2;$1=$2 = ""; m=substr($0,3,length($0)-length(v1 v2)-4); print f1,f2,"\"" m "\"",v2,v1; } ' philb2
field1|field2|field3|field4|field5
field1|field2||field4|field5
field1|field2|"value1|value2"|field4|field5
field1|field2|"value1|value2|value3"|field4|field5
$
Спасибо, но поле 3 не должно быть в кавычках, если в нем нет разделителей.
@philb .. Я только что обновил ответ .. не могли бы вы проверить.
Не сработало (извините, я не в восторге от синтаксиса форматирования...): ` IN: поле1|поле2|поле3|поле4|поле5 поле1|поле2||поле4|поле5 поле1|поле2|значение1|значение2|поле4|поле5 поле1 |field2|value1|value2|value3|field4|field5 OUT: field1|field2|field3|field4|field5|field2|"field3"|field4|field5 field1|field2||field4|field5|field2|""|field4 |поле5 поле1|поле2|"значение1|значение2"|поле4|поле5 поле1|поле2|"значение1|значение2|значение3"|поле4|поле5 `
@philb.. извините.. запутался.. вы можете проверить мое обновление 2.. если все в порядке.. я оставлю это в ответе
Учитывая ввод:
a|b|c|d|e|f|g
, как узнать, является ли третье полеc
илиc|d|e
?