awk избирательная нумерация страниц для печати - PullRequest
2 голосов
/ 11 марта 2019

У меня есть строка, содержащая номера страниц .pdf-файла, который я хочу распечатать, но я хочу минимизировать длину строки, чтобы заменить все последовательные числа тире, чтобы сделать выборочную печать с AWK. Кто-нибудь может мне помочь с кодом для awk?

от

1,3,4,5,6,7,8,9,11,12,13,15,16,17,18,19,20,21,22,23,24,25,26,27 , 28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,53

в

1,3-13,15-51,53

Ответы [ 4 ]

2 голосов
/ 11 марта 2019

Сложная часть этой проблемы заключается в том, что вы не знаете, как напечатать предыдущие значения, пока не получите следующее.

Вот скрипт, который работает разумно при подаче нескольких строк ввода, рассматривая каждую строку как отдельный набор чисел для обработки. Он полностью игнорирует пустые поля (начальные, соседние или конечные запятые). Предполагается, что поля в каждой строке все числовые и отсортированы в порядке возрастания. На самом деле он не работает с отрицательными числами в данных (форматирование с использованием тире для разделения диапазонов становится неуклюжим - но данные верны), но оно устраивает нули.

Это не самый компактный код, который возможен, но я считаю, что ясность важнее сжатия, особенно когда оно правильно (оптимизация или сжатие происходят позже, если это необходимо).

BEGIN { FS = "," }

function print_range()
{
    if (lo == hi)
        printf "%s%d", pad, lo
    else
        printf "%s%d-%d", pad, lo, hi
    pad = ","
}

{
    lo = ""
    hi = ""
    pad = ""
    for (i = 1; i <= NF; i++)
    {
        if ($i == "")   # Ignore empty fields - could report them
            continue
        else if (lo == "")
            hi = lo = $i
        else if ($i == hi + 1)
            hi = $i
        else
        {
            # Previous range complete - print it
            print_range()
            lo = hi = $i 
        }
    }
    print_range()
    print ""
}

Я использовал файл script.awk, содержащий приведенный выше код, и вызвал awk -f script.awk data* для генерации вывода, показанного ниже.

Вот некоторые примерные данные (основанные на данных из вопроса):

1,3,4,5,6,7,8,9,11,12,13,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,53,
1,3,4,5,6,7,8,9,11,12,13,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,53
1,3,4,5,6,7,8,9,11,12,13,15,16,17,18,19,20,21,22,23,24,25,26,27,28,30,31,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,53
3,4,5,6,7,8,9,11,12,13,15,16,17,18,19,20,21,22,23,24,25,26,27,28,30,31,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,53
3,4,5,6,7,8,9,11,12,13,15,16,17,18,19,20,21,22,23,24,25,26,27,28,30,31,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51

Первая строка имеет запятую; другие нет. В третьей и последующих строках пропущены записи 29 и 32, поэтому в данных присутствует 2-элементный диапазон 30-31. Последние две строки имеют многоэлементный диапазон в начале вместо одного элемента; последняя строка имеет многоэлементный диапазон в конце вместо одного элемента.

Выходные данные из сценария:

1,3-9,11-13,15-51,53
1,3-9,11-13,15-51,53
1,3-9,11-13,15-28,30-31,33-51,53
3-9,11-13,15-28,30-31,33-51,53
3-9,11-13,15-28,30-31,33-51

Легко настроить печать, чтобы определить, если hi == lo + 1, и принять решение печатать значения, разделенные запятыми, вместо значений, разделенных тире, если это предпочтительнее.

1 голос
/ 11 марта 2019

Ниже приведена очень короткая программа awk, которая сделает это:

awk 'BEGIN{FS=OFS=","}
     {gsub(/,+/,","); gsub(/^,|,$/,"")}
     {delete a; for(i=2;i<NF;++i) a[i]=($i-$(i-1) == 1 && $(i+1)-$i == 1)} 
     {for(i=1;i<=NF;++i) if (a[i]) $i=""}
     {gsub(/,,+/,"-"); print}' file

Выполнены следующие шаги:

  1. Очистка: удаление пустых полей

    {gsub(/,+/,","); gsub(/^,|,$/,"")}
    
  2. Выполните проверку: проверьте, не было ли предыдущее поле на единицу меньше, а следующее поле больше, чем текущее поле. Сохраните эту информацию в новом массиве.

    {delete a; for(i=2;i<NF;++i) a[i]=($i-$(i-1) == 1 && $(i+1)-$i == 1)}
    
  3. Удалить: , если предыдущее условие истинно, удалить значение

    {for(i=1;i<=NF;++i) if (a[i]) $i=""}
    
  4. Дефис: Несколько запятых означают последовательность, замените их дефисом.

    {gsub(/,,+/,"-"); print}
    

Шаг 2 и 3 можно на самом деле объединить, используя две временные переменные (a отслеживая исходное значение предыдущего поля и b истинное временное значение)

awk 'BEGIN{FS=OFS=","}
     {gsub(/,+/,","); gsub(/^,|,$/,"")}
     {a=$1; for(i=2;i<NF;++i) {b=$i; $i=($i-a == 1 && $(i+1)-$i == 1) ? "" : b; a=b}}
     {gsub(/,,+/,"-"); print}' file
1 голос
/ 11 марта 2019

Попробуйте это:

printf "1,3,4,5,6,7,8,9,11,12,13,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,53," |
  awk '
  function handleRange () {
     if (previous == 0) {
       first=$0
     } else if (previous != ( $0 - 1 )) {
       if ((previous - first) == 0) {
         print previous
       } else if ((previous - first) == 1) {
         print first ORS previous
       } else {
         print first "-" previous
       }
       first=$0
     }
     previous=$0
  }
   /[0-9]/ { handleRange(); }
   END     { handleRange(); }
  ' RS=, ORS=,

Выход:

1,3-9,11-13,15-51,53,
1 голос
/ 11 марта 2019
awk -e 'BEGIN { ORS = ","; RS = ","; O = -1 } { if(N + 1 == $1) { N += 1;} else { if (O != -1 && O != N) { print O,"-",N } else { print N; } O = $1; N = $1; } }'
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...