Страницы

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

воскресенье, 16 февраля 2020 г.

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

#bash #shell #zsh


Представим, что есть некая программа, которая отдает данные вот в таком формате:

$ 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 "\n$line"
    elif [[ $last_line == \[INFO\]* ]];
    then
        echo -en "\r$line"
    else
        echo -en "$line"
    fi

    last_line="$line"
done

    


Ответы

Ответ 1



вот такая минипрограмма для интерпретатора 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-последовательность «подвешена» функция определения границы слова.

Ответ 2



./foo.bs | awk '{$1==l?p="\r":p="\n"}{printf "%s%-80s",p,$0}{l=$1}' По поводу длинных строк. Тут вам, для начала, необходимо определиться с тем, что вы хотите получить. Можно, например, отключить перенос строк: printf %b '\033[?7l'. Тогда длинные строки будут "обрезаться" по краю окна. Если нужно выводить строку целиком, с переносом, то можно заморочиться с backspace (работает далеко не везде), либо со сложным высчитыванием реального количества выведенных строк (tput cols в помощь). В общем, простого пути нет. Ну и следует помнить, что все эти пляски со спецсимволами очень сильно зависят от настроек как терминала на стороне клиента, так и на стороне сервера.

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

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