Объединить строки, которые не соответствуют регулярному выражению

У меня есть файл, содержащий журналы из Интернета; упрощенная версия выглядит следующим образом:

en-GB,en-US;q=0.8,en    jsdjpksdkskd;lkskd;
en-GB,en-US;q=0.8,en    jsdjpksdkskd;lkskd;
en-GB,en-US;q=0.8,en    jsdjpksdkskd;lkskd;
Unix
Linux
en-GB,en-US;q=0.8,en    jsdjpksdkskd;lkskd;
START
Solaris
en-GB,en-US;q=0.8,en    jsdjpksdkskd;lkskd;
Aix
SCO

Я попробовал пару комбинаций регулярных выражений, чтобы определить Accept-Language, который является началом каждой строки, используя следующее с awk/sed:

/^[a-z]{2}(-[A-Z]{2})?/
/\*|[A-Z]{1,8}(-[A-Z0-9]{1,8})*/i  
/([^-;]*)(?:-([^;]*))?(?:;q=([0-9]\.[0-9]))?/

До сих пор мне не удалось заставить awk/sed получить следующие результаты:

en-GB,en-US;q=0.8,en    jsdjpksdkskd;lkskd;
en-GB,en-US;q=0.8,en    jsdjpksdkskd;lkskd;
en-GB,en-US;q=0.8,en    jsdjpksdkskd;lkskd;
en-GB,en-US;q=0.8,en    jsdjpksdkskd;lkskd;    Unix    Linux
en-GB,en-US;q=0.8,en    jsdjpksdkskd;lkskd;    STAR    Solaris
en-GB,en-US;q=0.8,en    jsdjpksdkskd;lkskd;    Aix    SCO

Любая помощь приветствуется. Файл содержит около 1 миллиона записей, поэтому я буду рад пойти по маршруту, который не использует sed/awk и повышает производительность.


person Amine Jaidi    schedule 23.12.2016    source источник
comment
Я полагаю, что в желаемом вами результате у вас есть дополнительная строка. Удалить один из первых трех?   -  person Rob Davis    schedule 24.12.2016


Ответы (3)


$ awk '/[a-z]{2}-[A-Z]{2}/ { print b; b=$0; next }  # @xx-XX empty buffer, refill
                           { b=b OFS $0 }           # otherwise append to buffer
                       END { print b }' file        # dump the buffer in the end

en-GB,en-US;q=0.8,en    jsdjpksdkskd;lkskd;
en-GB,en-US;q=0.8,en    jsdjpksdkskd;lkskd;
en-GB,en-US;q=0.8,en    jsdjpksdkskd;lkskd; Unix Linux
en-GB,en-US;q=0.8,en    jsdjpksdkskd;lkskd; START Solaris
en-GB,en-US;q=0.8,en    jsdjpksdkskd;lkskd; Aix SCO

Вы получите пустую строку для начала вывода. Кроме того, при желании используйте разделитель табуляции на выходе: awk -v OFS="\t" ....

person James Brown    schedule 25.12.2016
comment
Этот скрипт у меня не сработал, он объединяет все строки в одну. - person Amine Jaidi; 28.12.2016
comment
@AmineJaidi Это странно. Какова ваша среда и какой awk вы используете? - person James Brown; 28.12.2016
comment
Я на Redhat, не использую GAWK. Дело в том, что файл уже имеет \t разграничение, в основном проблема, которую я пытаюсь решить здесь, заключается в том, чтобы убедиться, что все строки, которые не начинаются с регулярного выражения Accept-language, должны быть добавлены к предыдущему. Приведенное ниже решение sed работает, было бы неплохо узнать, как это может сделать AWK. Мне не повезло. Я реализовал решение SED как часть функции сокращения в Hadoop, и оно довольно медленное. - person Amine Jaidi; 28.12.2016
comment
Первое, что приходит на ум, это то, что ваш awk не поддерживает {2} в регулярном выражении. Замените регулярное выражение: /[a-z]{2}-[A-Z]{2} на /[a-z][a-z]-[A-Z][A-Z]. - person James Brown; 28.12.2016
comment
Это было правильно, теперь скрипт работает, но он добавляет \n в начало файла :( - person Amine Jaidi; 28.12.2016
comment
Я знаю, я упомянул это в своем решении. Дай мне секунду, чтобы увидеть, легко ли это. - person James Brown; 28.12.2016
comment
Добавление if(b!="") перед print b; ... в первой строке должно решить проблему (или if(NR>1). - person James Brown; 28.12.2016

Основываясь на наблюдении, что мы можем различать два типа строк на =, вы можете использовать этот awk-скрипт:

файл.awk

$0 ~ /=/ { printf("%s%s", v,$0)
           v="\n"
           next
         } 
         { printf("\t%s", $0) } 
END      { printf("\n") }

Вы используете это так: awk -f file.awk yourfile

  • v в первой строке пусто, позже оно содержит разрыв строки
  • для строк с = мы печатаем $0 перед v
  • для других строк (обратите внимание на next в первом действии) мы печатаем $0 без новой строки, но с \t в качестве разделения
person Lars Fischer    schedule 23.12.2016
comment
Не кодируйте строку новой строки с помощью printf("\n"), вместо этого используйте print "", чтобы awk мог использовать любую строку новой строки, подходящую для среды, из которой она вызывается, например. \r\n. Кроме того, вам не нужен $0 ~, так как он используется по умолчанию. - person Ed Morton; 24.12.2016

Просто для удовольствия, вот решение sed:

sed -ne 1bgo \
   -e '/^[a-z][a-z]-[A-Z][A-Z]/ { x;p;s/.*//;x; };:go' \
   -e 'H;x;s/^\n//;s/\n/  /;x;${ x;p; }' < input

Это работает следующим образом:

  • Прочитайте каждую строку, но вместо того, чтобы печатать ее сразу, сохраните ее, добавив в пробел (H), за исключением удаления всех новых строк, которые отделяют ее от того, что уже было (x;s/^\n//;s/\n/ /;x). (Если вы хотите, чтобы в вашем выводе были вкладки, поместите их здесь, где я поставил пару пробелов.)

  • Если вы встретите строку, которая соответствует шаблону Accept-Language, очистите пространство хранения, прежде чем добавлять к нему что-либо. Распечатайте его и очистите (x;p;s/.*//;x). Затем продолжайте как обычно с добавлением и еще много чего.

  • Обращайтесь с первой и последней строками иначе, чем со всеми остальными: никогда не очищайте область хранения после прочтения только первой строки (1bgo пропускает ее до позиции, отмеченной :go), и всегда очищайте область хранения после чтения последней строки (${ x;p; }).

person Rob Davis    schedule 23.12.2016
comment
Это также отлично работает, я должен добавить! Единственная проблема с ним заключается в том, что для обработки данных в большем масштабе требуется много времени (особенно с Mapreduce). Спасибо за ваш ответ - person Amine Jaidi; 28.12.2016