Сортуйте текстовий файл за довжиною рядка, включаючи пробіли


137

У мене є файл CSV, який виглядає приблизно так

AS2345, ASDF1232, Містер рівнинний приклад, просп. Бінарний проспект 110, Атлантида, RI, 12345, (999) 123-5555,1.56
AS2345, ASDF1232, Місіс Плейн Приклад, 1121110 Тернар, вул. 110 Бінарний проспект .., Атлантида, РІ, 12345, (999) 123-5555,1.56
AS2345, ASDF1232, Mr. Plain Example, пр. Бінарний проспект 110, місто Ліберті, RI, 12345, (999) 123-5555,1.56
AS2345, ASDF1232, Містер рівнинний приклад, проспект Тернар 110, Деякі міста, Річ, 12345, (999) 123-5555,1.56

Мені потрібно сортувати його за довжиною лінії, включаючи пробіли. Наступна команда не включає пробіли, чи є спосіб її змінити, щоб вона працювала для мене?

cat $@ | awk '{ print length, $0 }' | sort -n | awk '{$1=""; print $0}'

21
Я дуже хотів би жити на Бінарному проспекті або на Тернарній вулиці, ці люди, безумовно, погодились би з такими речами, як "8192 - це кругле число"
шнадер

Відповіді:


224

Відповідь

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

"Це змушує відновити запис".

Тестовий вхід, включаючи рядки однакової довжини:

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

1
heemayl, так, дякую. Я намагався, коли це можливо, відповідати формі спроб рішення ОП, щоб дозволити йому зосередитись лише на важливих відмінностях між моїми та моїми.
neillb

1
Варто зазначити, що cat $@також порушено. Ви абсолютно точно хочете його процитувати, як-отcat "$@"
триплея

27

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

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

Ви можете помістити це у свій конвеєр, де вам це потрібно, або отримавши STDIN (від catабо перенаправлення оболонки) або просто дайте ім'я файлу perl як інший аргумент і дайте йому відкрити файл.

У моєму випадку мені спочатку потрібні були найдовші рядки, тому я замінився $aі $bпорівняння.


Це краще рішення, оскільки awk викликає несподіване сортування, коли вхідний файл містить числові та алфавітно-цифрові рядки Тут команда oneline: $ cat testfile | perl -e 'сортувати друк {length ($ a) <=> length ($ b)} <>'
alemol

Швидко! Чи було 465 000 файлів рядків (одне слово на рядок) за <1 секунду, коли вихід перенаправлявся в інший файл - таким чином:cat testfile.txt | perl -e 'print sort { length($a) <=> length($b) } <>' > out.txt
cssyphus

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-

10

Результати порівняння

Нижче наводяться результати орієнтиру в рішеннях інших відповідей на це питання.

Метод випробування

  • 10 послідовних пробіжок на швидкій машині, усереднені
  • Perl 5.24
  • awk 3.1.5 (gawk 4.1.0 разів було ~ 2% швидше)
  • Вхідний файл - 550 Мб, 6 мільйонів монструктивності (англ. National 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:

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як менш ефективні і потенційно менш точним (він перетворює число переміщуються), так що, ймовірно , не використовувати його , якщо вам не потрібно.
філс

документація nb для -n: "Сортування чисельно. Число починається з кожного рядка і складається з необов'язкових пробілів, необов'язкового знака" - "та нуля чи більше цифр, можливо, розділених тисячами роздільників, необов'язково супроводжуючись символом десяткової крапки та нулем або більше цифр . Порожнє число трактується як "0". Локал "LC_NUMERIC" вказує символ десяткової крапки та роздільник тисяч. За замовчуванням порожній - пробіл або вкладка, але "LC_CTYPE '" може змінити це ".
філс


2

1) чисте рішення awk. Припустимо, що довжина рядка не може бути більше> 1024

cat filename | awk 'ПОЧАТОК {min = 1024; s = "";} {l = довжина ($ 0); якщо (l <хв) {min = l; s = $ 0;}} END {print s} '

2) рішення однієї з лінійних файлів, якщо всі рядки мають лише 1 слово, але може бути перероблено для будь-якого випадку, коли всі рядки мають однакову кількість слів:

LINES = $ (назва файлу cat); за k в $ LINES; зробити printf "$ k"; відлуння $ k | wc -L; зроблено | сортувати -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).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.