Сортировка текстового файла по длине строки, включая пробелы

143

У меня есть файл CSV, который выглядит так

AS2345, ASDF1232, Mr. Plain Example, 110 Binary ave., Атлантида, Род-Айленд, 12345, (999) 123-5555, 1,56
AS2345, ASDF1232, Mrs. Plain Example, 1121110 Ternary st. 110 Бинарный просп., Атлантида, РИ, 12345, (999) 123-5555, 1,56
AS2345, ASDF1232, Mr. Plain Example, 110 Binary ave., Liberty City, RI, 12345, (999) 123-5555, 1,56
AS2345, ASDF1232, Mr. Plain Example, 110 Ternary ave., Some City, RI, 12345, (999) 123-5555, 1,56

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

cat $@ | awk '{ print length, $0 }' | sort -n | awk '{$1=""; print $0}'
гнусавый
источник
25
Я бы очень хотел жить на Бинарной авеню или Тернари-стрит, эти люди наверняка согласятся с такими вещами, как «8192 - круглое число»
schnaader

Ответы:

233

Ответ

cat testfile | awk '{ print length, $0 }' | sort -n -s | cut -d" " -f2-

Или, чтобы выполнить исходную (возможно, непреднамеренную) подсортировку любых строк одинаковой длины:

cat testfile | awk '{ print length, $0 }' | sort -n | cut -d" " -f2-

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

Линии одинаковой длины - что делать в случае галстука:

В вопросе не указывалось, требуется ли дальнейшая сортировка для строк одинаковой длины. Я предположил, что это нежелательно, и предложил использовать -s( --stable), чтобы предотвратить сортировку таких строк друг против друга и сохранить их в относительном порядке, в котором они встречаются во входных данных.

(Те, кто хочет большего контроля над сортировкой этих связей, могут рассмотреть --keyвариант сортировки .)

Почему попытка решения вопроса не удается (восстановление строки awk):

Интересно отметить разницу между:

echo "hello   awk   world" | awk '{print}'
echo "hello   awk   world" | awk '{$1="hello"; print}'

Они соответственно уступают

hello   awk   world
hello awk world

В соответствующем разделе руководства (gawk) упоминается только в стороне, что awk собирается перестроить весь $ 0 (на основе разделителя и т.д.), когда вы изменяете одно поле. Думаю, это не безумное поведение. В нем есть:

«Наконец, бывают случаи, когда удобно заставить awk перестроить всю запись, используя текущее значение полей и OFS. Для этого используйте, казалось бы, безобидное присваивание:»

 $1 = $1   # force record to be reconstituted
 print $0  # or whatever else with $0

«Это заставляет awk восстановить запись».

Тестовый ввод, включающий несколько строк одинаковой длины:

aa A line   with     MORE    spaces
bb The very longest line in the file
ccb
9   dd equal len.  Orig pos = 1
500 dd equal len.  Orig pos = 2
ccz
cca
ee A line with  some       spaces
1   dd equal len.  Orig pos = 3
ff
5   dd equal len.  Orig pos = 4
g
Neillb
источник
2
хемайл, да, спасибо. Я попытался сопоставить форму решения, предложенного OP, где это возможно, чтобы он мог сосредоточиться только на важных различиях между его и моим.
neillb
2
Стоит отметить, что cat $@он тоже сломан. Вы совершенно определенно хотите процитировать, какcat "$@"
tripleee
29

Решение AWK от neillb отлично подходит, если вы действительно хотите его использовать, awkи оно объясняет, почему там возникают проблемы , но если вы хотите, чтобы работа была выполнена быстро и не заботится о том, что вы делаете, одно из решений - использовать sort()Функция Perl с настраиваемой подпрограммой для перебора входных строк. Вот один лайнер:

perl -e 'print sort { length($a) <=> length($b) } <>'

Вы можете поместить это в свой конвейер везде, где вам это нужно, либо получив STDIN (от catили перенаправление оболочки), либо просто передать имя файла perl в качестве другого аргумента и позволить ему открыть файл.

В моем случае мне сначала нужны были самые длинные строки, поэтому я поменял местами $aи $bв сравнении.

Калеб
источник
1
Это лучшее решение, потому что awk вызывает неожиданную сортировку, когда входной файл содержит числовые и буквенно-цифровые строки. Здесь команда oneline: $ cat testfile | perl -e 'print sort {length ($ a) <=> length ($ b)} <>'
alemol
1
Быстро! Выполнил 465000 строк в файле (по одному слову в строке) за <1 секунду, когда вывод перенаправлялся в другой файл - таким образом:cat testfile.txt | perl -e 'print sort { length($a) <=> length($b) } <>' > out.txt
cssyphus
1
Windows с StrawberryPerl работает:type testfile.txt | perl -e "print sort { length($a) <=> length($b) } <>" > out.txt
bryc
14

Вместо этого попробуйте эту команду:

awk '{print length, $0}' your-file | sort -n | cut -d " " -f2-
анубхава
источник
11

Результаты тестов

Ниже приведены результаты сравнительного анализа решений из других ответов на этот вопрос.

Метод испытания

  • 10 последовательных прогонов на быстрой машине, в среднем
  • Perl 5.24
  • awk 3.1.5 (gawk в 4.1.0 раза быстрее на ~ 2%)
  • Входной файл - это чудовище размером 550 МБ, 6 миллионов строк (British National Corpus txt).

Полученные результаты

  1. perlРешение Калеба заняло 11,2 секунды.
  2. мое perlрешение заняло 11,6 секунды
  3. neillb в awkраствор # 1 потребовалось 20 секунд
  4. neillb в awkраствор # 2 занял 23 секунды
  5. awkрешение анубхавы заняло 24 секунды
  6. awkРешение Джонатана заняло 25 секунд
  7. Fretz в bashраствор принимает 400x длиннее , чем awkрастворы ( с использованием усеченного тестового примера 100000 строк). Работает нормально, просто занимает вечность.

Другое perlрешение

perl -ne 'push @a, $_; END{ print sort { length $a <=> length $b } @a }' file
Крис Кокнат
источник
6

Чистый Баш:

declare -a sorted

while read line; do
  if [ -z "${sorted[${#line}]}" ] ; then          # does line length already exist?
    sorted[${#line}]="$line"                      # element for new length
  else
    sorted[${#line}]="${sorted[${#line}]}\n$line" # append to lines with equal length
  fi
done < data.csv

for key in ${!sorted[*]}; do                      # iterate over existing indices
  echo -e "${sorted[$key]}"                       # echo lines with equal length
done
Фриц Г. Менер
источник
3

length()Функция делает включать пробелы. Я бы внес незначительные изменения в ваш конвейер (включая отказ от UUOC ).

awk '{ printf "%d:%s\n", length($0), $0;}' "$@" | sort -n | sed 's/^[0-9]*://'

Команда sedнапрямую удаляет цифры и двоеточие, добавленные awkкомандой. В качестве альтернативы можно сохранить форматирование от awk:

awk '{ print length($0), $0;}' "$@" | sort -n | sed 's/^[0-9]* //'
Джонатан Леффлер
источник
2

Я обнаружил, что эти решения не будут работать, если ваш файл содержит строки, начинающиеся с номера, поскольку они будут отсортированы численно вместе со всеми подсчитанными строками. Решение состоит в том, чтобы дать sortв -g(вообще-числовой сортировки) флаг вместо -n(числовой сортировки):

awk '{ print length, $0 }' lines.txt | sort -g | cut -d" " -f2-
Маркус Амальтея Магнусон
источник
2
Привет, Маркус. Я не наблюдаю, как содержимое строки (числовое или нет) - в отличие от длины строки - оказывает какое-либо влияние на сортировку, за исключением строк с совпадающей длиной. Вы это имели в виду? В таких случаях я не обнаружил, что переключение методов сортировки с -nна ваш предложило -gкакие-либо улучшения, поэтому я не ожидаю. В своем ответе я обратился к вопросу о том, как запретить подсортировку строк одинаковой длины (с помощью --stable). Независимо от того, имели ли вы это в виду или нет, спасибо, что обратил на это мое внимание! Я также добавил продуманный ввод для тестирования.
neillb
4
Нет, позвольте мне объяснить, разбив это на части. Просто awkчасть будет генерировать список строк с префиксом длины строки и пробелом. Подключение к нему sort -nбудет работать, как ожидалось. Но если какая-либо из этих строк уже имеет номер в начале, эти строки будут начинаться с длины + пробела + числа. sort -nигнорирует это пространство и будет рассматривать его как одно число, состоящее из длины + числа. -gВместо этого использование флага остановится на первом пробеле, что приведет к правильной сортировке. Попробуйте сами, создав файл с несколькими строками с префиксом цифр и пошагово выполните команду.
Маркус Амальтея Магнусон
1
Я также обнаружил, что sort -nне учитывает пространство и производит неправильную сортировку. sort -gвыводит правильный порядок.
Роберт Смит
Я не могу воспроизвести описанную проблему -nв sort (GNU coreutils) 8.21. В infoдокументации описывается -gкак менее эффективный и потенциально менее точный (он преобразует числа в числа с плавающей запятой), поэтому, вероятно, не используйте его, если вам это не нужно.
phils
Документация nb для -n: "Сортировка в числовом виде. Число начинается с каждой строки и состоит из необязательных пробелов, необязательного знака '-' и нуля или более цифр, которые могут быть разделены разделителями тысяч, за которыми необязательно следует десятичная точка и ноль или более цифр . Пустое число обрабатывается как '0'. Локаль 'LC_NUMERIC' определяет десятичный знак и разделитель тысяч. По умолчанию пробелом является пробел или табуляция, но языковой стандарт 'LC_CTYPE' может это изменить ".
phils
2

С POSIX Awk:

{
  c = length
  m[c] = m[c] ? m[c] RS $0 : $0
} END {
  for (c in m) print m[c]
}

пример

Стивен Пенни
источник
2

1) чистое решение awk. Предположим, что длина строки не может быть больше> 1024, тогда

кошка имя файла | awk 'BEGIN {min = 1024; s = "";} {l = длина ($ 0); если (l <min) {min = l; s = $ 0;}} КОНЕЦ {print s} '

2) одно линейное решение bash, предполагающее, что все строки содержат только 1 слово, но может быть переработано для любого случая, когда все строки имеют одинаковое количество слов:

LINES = $ (имя файла кошки); для k в $ LINES; сделать printf "$ k"; эхо $ k | туалет -L; сделано | sort -k2 | голова -n 1 | вырезать -d "" -f1

Михаил Юниверг
источник
1

Вот многобайтовый метод сортировки строк по длине. Это требует:

  1. wc -m вам доступен (он есть в macOS).
  2. Текущая локаль поддерживает многобайтовые символы, например, путем установки LC_ALL=UTF-8. Вы можете установить это либо в вашем .bash_profile, либо просто добавив его перед следующей командой.
  3. testfile имеет кодировку символов, соответствующую вашему языку (например, UTF-8).

Вот полная команда:

cat testfile | awk '{l=$0; gsub(/\047/, "\047\"\047\"\047", l); cmd=sprintf("echo \047%s\047 | wc -m", l); cmd | getline c; close(cmd); sub(/ */, "", c); { print c, $0 }}' | sort -ns | cut -d" " -f2-

Объясняя по частям:

  • l=$0; gsub(/\047/, "\047\"\047\"\047", l);← создает копию каждой строки в переменной awk lи дважды экранирует каждую, 'чтобы строка могла безопасно отображаться как команда оболочки ( \047одинарная кавычка в восьмеричной системе счисления).
  • cmd=sprintf("echo \047%s\047 | wc -m", l);← это команда, которую мы выполним, которая отображает экранированную строку wc -m.
  • cmd | getline c;← выполняет команду и копирует возвращаемое значение счетчика символов в переменную awk c.
  • close(cmd); ← закрыть канал к команде оболочки, чтобы избежать ограничения системы на количество открытых файлов в одном процессе.
  • sub(/ */, "", c);← обрезает пробелы из значения количества символов, возвращаемого wc.
  • { print c, $0 } ← выводит количество символов в строке, пробел и исходную строку.
  • | sort -ns← сортирует строки (по количеству добавленных символов) численно ( -n) и поддерживает стабильный порядок сортировки ( -s).
  • | cut -d" " -f2- ← удаляет добавленные значения счетчика символов.

Он медленный (всего 160 строк в секунду на быстром Macbook Pro), потому что он должен выполнять подкоманду для каждой строки.

В качестве альтернативы, просто сделайте это только с помощью gawk(начиная с версии 3.1.5, gawk поддерживает многобайтовость), что будет значительно быстрее. Очень сложно выполнить все экранирование и двойные кавычки, чтобы безопасно передать строки через команду оболочки из awk, но это единственный метод, который я смог найти, который не требует установки дополнительного программного обеспечения (gawk по умолчанию недоступен в macOS).

Куинн Комендант
источник
1

используя Raku (ранее известный как Perl6)

~$ cat "BinaryAve.txt" | raku -e 'given lines() {.sort(*.chars).join("\n").say};'

AS2345,ASDF1232, Mr. Plain Example, 110 Binary ave.,Atlantis,RI,12345,(999)123-5555,1.56
AS2345,ASDF1232, Mr. Plain Example, 110 Ternary ave.,Some City,RI,12345,(999)123-5555,1.56
AS2345,ASDF1232, Mr. Plain Example, 110 Binary ave.,Liberty City,RI,12345,(999)123-5555,1.56
AS2345,ASDF1232, Mrs. Plain Example, 1121110 Ternary st.                                        110 Binary ave..,Atlantis,RI,12345,(999)123-5555,1.56

Чтобы отменить сортировку, добавьте .reverseв середину цепочки вызовов методов - сразу после .sort(). Вот код, который .charsвключает пробелы:

~$ cat "number_triangle.txt" | raku -e 'given lines() {.map(*.chars).say};'
(1 3 5 7 9 11 13 15 17 19 0)
~$ cat "number_triangle.txt"
1
1 2
1 2 3
1 2 3 4
1 2 3 4 5
1 2 3 4 5 6
1 2 3 4 5 6 7
1 2 3 4 5 6 7 8
1 2 3 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9 0

Вот сравнение времени между awk и Raku с использованием текстового файла размером 9,1 МБ от Genbank:

~$ time cat "rat_whole_genome.txt" | raku -e 'given lines() {.sort(*.chars).join("\n").say};' > /dev/null
    
    real    0m1.308s
    user    0m1.213s
    sys 0m0.173s
    
~$ #awk code from neillb
~$ time cat "rat_whole_genome.txt" | awk '{ print length, $0 }' | sort -n -s | cut -d" " -f2-  > /dev/null
    
    real    0m1.189s
    user    0m1.170s
    sys 0m0.050s

HTH.

https://raku.org

ликующий1
источник