У меня есть текстовый файл с пустыми строками, разделяющими блоки текста. Я хотел бы использовать инструменты командной строки *NIX, чтобы перетасовать этот файл, соблюдая структуру блоков. Другими словами, в выводе я хотел бы видеть измененный порядок блоков; строки и их порядок внутри блока остаются прежними.
Пример входного файла:
line 1
line 2
line 10
line 20
line 30
line 100
line 200
Выходной файл (после перемешивания):
line 10
line 20
line 30
line 1
line 2
line 100
line 200
Конечно, при повторном запуске должен быть другой порядок блоков.
Первая строка файла всегда непустая. Двойных пустых строк нет. Последняя строка файла всегда пустая.
Я написал очень простой скрипт на Python, который считывает все строки в списке списков, перемешивает их и выводит. Мне интересно, смогу ли я сделать это стандартными инструментами *NIX.
решение1
В POSIX вы можете сделать что-то вроде:
<file awk '
BEGIN{srand(); n=rand()}
{print n, NR, $0}
!NF {n=rand()}
END {if (NF) print n, NR+1, ""}' |
sort -nk1 -k2 |
cut -d' ' -f3-
То есть, добавьте к каждой строке <a-random-number-that-changes-with-each-paragraph>
номер строки, затем отсортируйте ее по первому номеру, а затем по второму, чтобы сохранить порядок строк в абзацах и удалить лишние номера.
Возможно, вам захочется sed '$d'
удалить завершающую пустую строку.
Помните, что в большинстве awk
реализаций srand()
для заполнения генератора псевдослучайных чисел используется время эпохи unix, поэтому вы можете получить тот же результат, если выполните его дважды в одну и ту же секунду (историческая ошибка теперь запечатлена в спецификации POSIX, несмотря на мои усилия, к сожалению).
решение2
Используя инструменты GNU, эта команда делит абзацы на группы, разделенные символами NUL, перемешивает их, а затем удаляет символы NUL:
$ sed '1s/^/\n/; s/^$/\x00/' input | shuf -z | sed '1d; s/\x00//'
line 100
line 200
line 10
line 20
line 30
line 1
line 2
Альтернативный подход без использования NUL
Поскольку не все инструменты поддерживают символы NUL, вот альтернатива. Это читает абзацы, заменяет ~
новые строки, затем перемешивает, затем преобразует ~
обратно в новые строки перед отображением результатов:
$ awk '{gsub(/\n/, "~")} 1' RS= input | shuf | awk '{gsub(/~/, "\n")} 1' ORS="\n\n"
line 10
line 20
line 30
line 100
line 200
line 1
line 2
Если ваш текст может содержать ~
, то используйте в качестве временного разделителя строк другой символ, которого в тексте не будет.
решение3
Использование Perl:
perl -MList::Util -00 -e 'chomp(my @a=<>); print join("\n\n", List::Util::shuffle @a) . "\n";' < input
Или разложить в виде скрипт-файла:
#!/usr/bin/perl
use List::Util 'shuffle';
local $/ = ""; ## paragraph mode
chomp(my @a = <>);
print join("\n\n", shuffle @a) . "\n";