Я создал сценарий Bash, который извлекает слова из текстового файла с помощью grep и sed , а затем сортирует их с помощью сортировки и считает повторы с помощью > wc , затем снова сортируйте по частоте. Пример вывода выглядит так:

12 the
 7 code
 7 with
 7 add
 5 quite
 3 do
 3 well
 1 quick
 1 can
 1 pick
 1 easy

Теперь я хотел бы объединить все слова с одинаковой частотой в одну строку, например так:

12 the
 7 code with add
 5 quite
 3 do well
 1 quick can pick easy

Есть ли способ сделать это с помощью Bash и стандартного набора инструментов Unix? Или мне придется написать сценарий / программу на более сложном языке сценариев?

1
BarbaraKwarc 3 Сен 2017 в 23:51

6 ответов

Лучший ответ

С awk:

$ echo "12 the
 7 code
 7 with
 7 add
 5 quite
 3 do
 3 well
 1 quick
 1 can
 1 pick
 1 easy" | awk '{cnt[$1]=cnt[$1] ? cnt[$1] OFS $2 : $2} END {for (e in cnt) print e, cnt[e]} ' | sort -nr
12 the
7 code with add
5 quite
3 do well
1 quick can pick easy

Вы можете сделать нечто подобное с ассоциативными массивами Bash 4. awk проще и POSIX, хотя. Используйте это.


Объяснение :

  1. awk разделяет строку на разделитель в FS, в данном случае это горизонтальный пробел по умолчанию;
  2. $1 - это первое поле счетчика - используйте его для сбора элементов с одинаковым количеством в ассоциативном массиве, который определяется счетчиком с cnt[$1];
  3. cnt[$1]=cnt[$1] ? cnt[$1] OFS $2 : $2 является троичным присваиванием - если cnt[$1] не имеет значения, просто присвойте ему второе поле $2 (RH :). Если оно имеет предыдущее значение, объедините $2, разделив его значением OFS (LH :);
  4. В конце выведите значение ассоциативного массива.

Поскольку ассоциативные массивы awk неупорядочены, вам нужно снова отсортировать по числовому значению первого столбца. gawk может выполнять внутреннюю сортировку, но ее так же легко вызвать sort. Входные данные для awk не нужно сортировать, поэтому вы можете исключить эту часть конвейера.


Если вы хотите, чтобы цифры были правильно выровнены (как в вашем примере):

$ awk '{cnt[$1]=cnt[$1] ? cnt[$1] OFS $2 : $2} 
     END {for (e in cnt) printf "%3s %s\n", e, cnt[e]} '

Если вы хотите gawk отсортировать численно по убыванию значений вы можете добавить PROCINFO["sorted_in"]="@ind_num_desc" перед обходом массива:

$ gawk '{cnt[$1]=cnt[$1] ? cnt[$1] OFS $2 : $2} 
            END {PROCINFO["sorted_in"]="@ind_num_desc"
               for (e in cnt) printf "%3s %s\n", e, cnt[e]} '
3
dawg 3 Сен 2017 в 21:15

В следующий раз вы попытаетесь манипулировать текстом с помощью комбинации grep, sed и shell и ..., остановитесь и просто используйте вместо этого awk - конечный результат будет более понятным, простым, более эффективным, более переносимым и т. д. ...

$ cat file
It was the best of times, it was the worst of times,
it was the age of wisdom, it was the age of foolishness.

.

$ cat tst.awk
BEGIN { FS="[^[:alpha:]]+" }
{
    for (i=1; i<NF; i++) {
        word2cnt[tolower($i)]++
    }
}
END {
    for (word in word2cnt) {
        cnt = word2cnt[word]
        cnt2words[cnt] = (cnt in cnt2words ? cnt2words[cnt] " " : "") word
        printf "%3d %s\n", cnt, word
    }
    for (cnt in cnt2words) {
        words = cnt2words[cnt]
        # printf "%3d %s\n", cnt, words
    }
}
$
$ awk -f tst.awk file | sort -rn
  4 was
  4 the
  4 of
  4 it
  2 times
  2 age
  1 worst
  1 wisdom
  1 foolishness
  1 best

.

$ cat tst.awk
BEGIN { FS="[^[:alpha:]]+" }
{
    for (i=1; i<NF; i++) {
        word2cnt[tolower($i)]++
    }
}
END {
    for (word in word2cnt) {
        cnt = word2cnt[word]
        cnt2words[cnt] = (cnt in cnt2words ? cnt2words[cnt] " " : "") word
        # printf "%3d %s\n", cnt, word
    }
    for (cnt in cnt2words) {
        words = cnt2words[cnt]
        printf "%3d %s\n", cnt, words
    }
}
$
$ awk -f tst.awk file | sort -rn
  4 it was of the
  2 age times
  1 best worst wisdom foolishness

Просто раскомментируйте любую строку printf, которая вам нравится в приведенном выше сценарии, чтобы получить любой тип вывода, который вы хотите. Вышеуказанное будет работать в любом awk в любой системе UNIX.

0
Ed Morton 3 Сен 2017 в 22:59

Вы начинаете с отсортированных данных, поэтому новая строка нужна только при изменении первого поля.

echo "12 the
 7 code
 7 with
 7 add
 5 quite
 3 do
 3 well
 1 quick
 1 can
 1 pick
 1 easy" |
awk '
   {
      if ($1==last) { 
         printf(" %s",$2) 
      } else { 
         last=$1;
         printf("%s%s",(NR>1?"\n":""),$0)
      }
    }; END {print}'
1
Walter A 3 Сен 2017 в 22:01

Использование miller nest глагола:

mlr -p  nest --implode --values --across-records -f 2 --nested-fs ' ' file

Выход:

12 the
7 code with add
5 quite
3 do well
1 quick can pick easy
0
agc 4 Сен 2017 в 06:34

С одним выражением GNU awk (без конвейера sort):

awk 'BEGIN{ PROCINFO["sorted_in"]="@ind_num_desc" }
     { a[$1]=(a[$1])? a[$1]" "$2:$2 }END{ for(i in a) print i,a[i]}' file

Выход:

12 the
7 code with add
5 quite
3 do well
1 quick can pick easy

Бонус альтернативное решение с использованием инструмента GNU datamash:

datamash -W -g1 collapse 2 <file

Вывод (разделенные запятыми свернутые поля):

12  the
7   code,with,add
5   quite
3   do,well
1   quick,can,pick,easy
2
RomanPerekhrest 4 Сен 2017 в 06:26

AWK :

awk '{a[$1]=a[$1] FS $2}!b[$1]++{d[++c]=$1}END{while(i++<c)print d[i],a[d[i]]}' file

СЭД :

sed -r ':a;N;s/(\b([0-9]+).*)\n\s*\2/\1/;ta;P;D'
2
mop 4 Сен 2017 в 11:04