У меня есть этот текстовый файл:
worked
working
works
tested
tests
find
found
Он содержит миллион слов без пробелов. Он может содержать символы Юникода.
Самое длинное слово — «работает»:
awk '{print length, $0}' test.txt | sort -nr | head -1
7 working
Мне нужно создать биграмму, триграмму (максимум 7 столбцов)
w,wo,wor,work,worke,worked,
w,wo,wor,work,worki,workin,working
w,wo,wor,work,works,,
t,te,tes,test,teste,tested,
t,te,tes,test,tests,,
f,fi,fin,find,,,,
f,fo,fou,foun,found,,
желательно в awk (потому что это быстро)
Прямой подход будет следующим:
awk -vn=7 -vOFS=, \
'{s=$0; for (i=1;i<=n;i++) $i=i<=length(s)? substr(s,1,i): ""}1'
# or
# '{s=$0; for (i=1;i<=n;i++) $i=substr(s, i<=length(s)?1:n, i)}1'
w,wo,wor,work,worke,worked,
w,wo,wor,work,worki,workin,working
w,wo,wor,work,works,,
t,te,tes,test,teste,tested,
t,te,tes,test,tests,,
f,fi,fin,find,,,
f,fo,fou,foun,found,,
Если вы заранее не знаете максимальную длину строки, вы можете использовать:
awk '
BEGIN {max_length=1}; { a[NR]=$1; m=length($1); if (m > max_length) {max_length=m} }
END { for(i=1;i<=NR;i++) {for (j=1; j<=max_length;j++) {if (j<=length(a[i])){printf "%s", substr(a[i],1,j)}; if (j<max_length){ printf "%s", "," } }; printf "\n"} }
' file
w,wo,wor,work,worke,worked,
w,wo,wor,work,worki,workin,working
w,wo,wor,work,works,,
t,te,tes,test,teste,tested,
t,te,tes,test,tests,,
f,fi,fin,find,,,
f,fo,fou,foun,found,,
Awk не очень хорошо справляется с последовательностями компоновки Unicode. Возможно, стоит обратиться к Perl за удобной заменой.
Вот краткий тест со случайной строкой на вьетнамском языке, в которой используется множество акцентов.
bash$ printf '%s\n' "người" "đặt" | xxd
00000000: 6e67 c6b0 e1bb 9d69 0ac4 91e1 bab7 740a ng.....i......t.
Вот демонстрация того, как nawk
ведет себя с этим вводом:
bash$ printf '%s\n' "người" "đặt" |
> awk -vn=7 -vOFS=, '{s=$0; for (i=1;i<=n;i++) $i=i<=length(s)? substr(s,1,i): ""}1'
n,ng,ng?,ngư,ngư?,ngư?,ngườ
?,đ,đ?,đ?,đặ,đặt,
Вот быстрая и грязная реализация Perl:
bash$ printf '%s\n' "người" "đặt" |
> perl -CSD -ne 'BEGIN { $n = 7 }
> chomp; $sep = ""; for my $i (1..$n) {
> print ($_ =~ "\\X{$i}" ? "$sep$&" : "$sep"); $sep = ","; }
> print "\n";'
n,ng,ngư,ngườ,người,,
đ,đặ,đặt,,,,
Вот демонстрация с более знакомым английским (ну, заимствованным) словом.
bash$ printf '%s\n' $'re\u0301sume\u0301' |
> perl -CSD -ne 'BEGIN { $n = 7 }
> chomp; $sep = ""; for my $i (1..$n) {
> print ($_ =~ "\\X{$i}" ? "$sep$&" : "$sep"); $sep = ","; }
> print "\n";'
r,ré,rés,résu,résum,résumé,
Если это не очевидно, Perl прекрасно знает, что U+0301 является комбинирующим символом, который должен быть графически соединен с предыдущим базовым символом, и поэтому рассматривает полученный кластер как один символ (или, точнее, графему), насколько касается регулярного выражения \X
. Поскольку у Awk нет этих знаний, он не может этого сделать. (Возможно, см. также Эквивалентность Юникода в Википедии.)
Если вам действительно нужны биграммы и триграммы, а не префиксы определенной длины, это тоже легко.
bash$ printf '%s\n' "người" "đặt" |
> perl -CSD -ne 'chomp; $sep = "";
> for my $i (0..length($_)) {
> break unless ($_ =~ "\\X{$i}\\K\\X{2}");
> print "$sep$&";
> $sep = ",";
> print ($_ =~ "\\X{$i}\\K\\X{3}" ? "$sep$&" : "");
> } print "\n";'
ng,ngư,gư,gườ,ườ,ười,ời
đặ,đặt,ặt
Использование любого awk (по крайней мере, для ввода символов английского языка), если вы уже знаете максимальную длину входной строки:
$ cat tst.awk
BEGIN { FS=OFS = ","; maxLen=7 }
{
curLen = length($0)
out = substr($0,1,1)
for ( i=2; i<=curLen; i++ ) {
out = out OFS substr($0,1,i)
}
$0 = out
$maxLen = $maxLen
print
}
$ awk -f tst.awk file
w,wo,wor,work,worke,worked,
w,wo,wor,work,worki,workin,working
w,wo,wor,work,works,,
t,te,tes,test,teste,tested,
t,te,tes,test,tests,,
f,fi,fin,find,,,
f,fo,fou,foun,found,,
в противном случае с помощью двухпроходного подхода сначала вычислите максимальную длину:
$ cat tst.awk
BEGIN { FS=OFS = "," }
{ curLen = length($0) }
NR == FNR {
if ( curLen > maxLen ) {
maxLen = curLen
}
next
}
{
out = substr($0,1,1)
for ( i=2; i<=curLen; i++ ) {
out = out OFS substr($0,1,i)
}
$0 = out
$maxLen = $maxLen
print
}
$ awk -f tst.awk file file
w,wo,wor,work,worke,worked,
w,wo,wor,work,worki,workin,working
w,wo,wor,work,works,,
t,te,tes,test,teste,tested,
t,te,tes,test,tests,,
f,fi,fin,find,,,
f,fo,fou,foun,found,,
Учитывая вышесказанное, я
length()
только один раз перед началом цикла, чтобы awk не вызывал его на каждой итерации цикла.$0
только вне цикла, чтобы избежать повторного разделения и/или повторной конструкции awk $0
на каждой итерации цикла.$maxLen = $maxLen
после цикла итак что он будет портативным, быстрым и будет работать с входным файлом любого размера.
@tripleee отмечает в комментарии, что они получили неправильный вывод на MacOS из приведенного выше сценария при использовании символов Юникода. awk по умолчанию в MacOS, как известно, содержит ошибки, и у меня нет Mac для тестирования, поэтому я не собираюсь это исследовать, но, кстати, вот что я вижу из второго сценария выше, учитывая ввод @tripleee и использование gawk 5.3.0 на Cygwin с LC_ALL='en_US.UTF-8'
:
$ printf '%s\n' "người" "đặt" > file
$ awk -f tst.awk file file
n,ng,ngư,ngườ,người
đ,đặ,đặt,,
$ awk -b -f tst.awk file file
n,ng,ng▒,ngư,ngư▒,ngư▒,ngườ,người
▒,đ,đ▒,đ▒,đặ,đặt,,
$ printf '%s\n' 'résumé' 'zoölogy' > file
$ awk -f tst.awk file file
r,ré,rés,résu,résum,résumé,
z,zo,zoö,zoöl,zoölo,zoölog,zoölogy
$ awk -b -f tst.awk file file
r,r▒,ré,rés,résu,résum,résum▒,résumé
z,zo,zo▒,zoö,zoöl,zoölo,zoölog,zoölogy
$ printf '%s\n' $'re\u0301sume\u0301' > file
$ awk -f tst.awk file file
r,re,ré,rés,résu,résum,résume,résumé
$ awk -b -f tst.awk file file
r,re,re▒,ré,rés,résu,résum,résume,résume▒,résumé
Вот что делает -b
(из руководства):
-б --символы как байты
Заставьте gawk обрабатывать все входные данные как однобайтовые символы. Кроме того, весь вывод, записанный с помощью print или printf, рассматривается как однобайтовые символы.
Обычно gawk следует стандарту POSIX и пытается обработать входные данные в соответствии с текущей локалью (см. «Где вы находитесь»). Имеет значение). Часто это может включать преобразование многобайтовых символы в широкие символы (внутренне), что может привести к проблемам. или путаница, если входные данные не содержат действительных многобайтовых данных персонажи. Этот вариант — простой способ сказать наблюдателю: «Руки прочь от меня». данные!"