Страницы

Поиск по вопросам

суббота, 27 апреля 2019 г.

Как удалять последовательные дубликаты по определённому столбцу?

Представим, что есть некая программа, которая отдает данные вот в таком формате:
$ cat foo.bs #!/bin/bash
echo "[INFO] Инициализация временных сдвигов" sleep 1 echo "[INFO] Подготовка к временному увеличению скорости света" sleep 1 echo "[WARN] u.speed_of_light: NaN" sleep 1 echo "[INFO] Скорость света увеличена, начинаем создание темной материи" sleep 1 echo "[ERROR] java.lang.NullPointerException: null"
$ ./foo.bs [INFO] Инициализация временных сдвигов [INFO] Подготовка к временному увеличению скорости света [WARN] u.speed_of_light: NaN [INFO] Скорость света увеличена, начинаем создание темной материи [ERROR] java.lang.NullPointerException: null
Что хотелось бы получить:
[INFO] Подготовка к временному увеличению скорости света [WARN] u.speed_of_light: NaN [INFO] Скорость света увеличена, начинаем создание темной материи [ERROR] java.lang.NullPointerException: null
Пошаговое воспроизведение:
Выводим [INFO] Инициализация временных сдвигов Перезаписываем предыдущую строку, потому что у нее флаг [INFO], теперь последняя отображаемая строка [INFO] Инициализация временных сдвигов Выводим новую строку [WARN] u.speed_of_light: NaN. Выводим новую строку [INFO] Скорость света увеличена, начинаем создание темной материи потому что перезаписывать варнинги нельзя Выводим новую строку [ERROR] java.lang.NullPointerException: null.
Необходимо именно перезаписывать предыдущую выведенную строку (см. пункт 2), поскольку необходима индикация процесса.
Подскажите, есть ли стандартные методы для осуществления этого? Набросал простой скрипт, который решает конкретную задачу (хотя есть пара тонких моментов), но хотелось бы иметь стандартные средства для подобного.
#!/bin/bash
last_line=""
while IFS= read -r line do if [[ $line != \[INFO\]* ]]; then echo -e "
$line" elif [[ $last_line == \[INFO\]* ]]; then echo -en "
$line" else echo -en "$line" fi
last_line="$line" done


Ответ

вот такая минипрограмма для интерпретатора awk
x!=$номер;{x=$номер}
(где номер — это номер сортируемого столбца) делает примерно то, что вам нужно: удаляет последовательные дубликаты, встречющиеся в указанном столбце:
$ cat foo | awk 'x!=$1;{x=$1}' [INFO] Инициализация временных сдвигов [WARN] u.speed_of_light: NaN [INFO] Скорость света увеличена, начинаем создание темной материи [ERROR] java.lang.NullPointerException: null
но, как видите, печатается содержимое той строки, которая встретилась первой.
чтобы печаталось содержимое последней из встреченных строк, можно, конечно, «помудрить» с программой (насколько я понимаю, существенно её усложнив), а можно просто «перевернуть» содержимое файла — чтобы первой шла последняя строка, второй предпоследняя и т.д., а после обработки — «перевернуть» опять. сделать это можно программой tac (даже в названии отражён ей смысл: cat «наоборот»):
$ tac foo | awk 'x!=$1;{x=$1}' | tac [INFO] Подготовка к временному увеличению скорости света [WARN] u.speed_of_light: NaN [INFO] Скорость света увеличена, начинаем создание темной материи [ERROR] java.lang.NullPointerException: null

идея минипрограммы позаимствована отсюда: http://www.unixcl.com/2009/05/remove-duplicate-consecutive-fields-or.html

обновление
придумал, как обойтись без tac. минипрограмма, правда, значительно усложняется:
$ cat foo | awk 'END{print y}{if(x!=$1&&x!=""){print y};x=$1;y=$0}'
чтобы сортировать по другому столбцу, надо подставить его номер вместо 1 в оба вхождения $1
обновление2
из уточнений стало понятно, что требуется не просто конечный результат, а «интерактивное шоу», когда последняя выведенная строка стирается, если следующая за ней начинается тем же самым словом.
тогда можно сделать примерно так:
#!/bin/bash
prevline="" prevtag=""
while read line; do newtag=$(echo $line | cut -d ' ' -f 1) if [ -n "$prevline" ]; then if [ "$newtag" == "$prevtag" ]; then echo -n "$prevline" | sed 's/./\x08/g' else echo fi fi echo -n "$line" prevtag=$newtag prevline=$line done echo
\x08 — это символ «забоя» (backspace). к сожалению, программа sed, которой я воспользовался в данном случае, не воспринимает распрастранённую escape-последовательность \b в качестве данного символа. связано это с тем, что на данную escape-последовательность «подвешена» функция определения границы слова.

Комментариев нет:

Отправить комментарий