обратный порядок абзацев в файле

8

У меня есть файл, содержащий текст в абзацах (строки с текстом, разделенным одной или несколькими пустыми строками). Я хотел бы изменить порядок абзацев (то есть последний абзац станет первым, ...), предпочтительно с помощью sed.

Я ищу команду sed, которая будет делать с файлом абзацев, что tacбудет делать с файлом строк.

Мартин Вегтер
источник

Ответы:

6

Использование sedне столь прямолинейно , как упомянуто Joseph R. . Тем не менее, вы могли бы сказать:

sed '/./{H;d;};x;s/\n/={NL}=/g' inputfile | \
sed -e 's/^={NL}=//' -e '1!G;h;$!d' | \
sed G | sed 's/={NL}=/\'$'\n/g'

Учитывая пример ввода:

Para 1 line 1
Para 1 line 2
Para 1 line 3

Para 2 line 1
Para 2 line 2
Para 2 line 3

Para 3 line 1
Para 3 line 2
Para 3 line 3

это даст:

Para 3 line 1
Para 3 line 2
Para 3 line 3

Para 2 line 1
Para 2 line 2
Para 2 line 3

Para 1 line 1
Para 1 line 2
Para 1 line 3

Стоит отметить, что это решение (как и альтернативное Perl) требует пустой строки в конце входного файла, чтобы работать должным образом.

devnull
источник
6

Это решение использует оба tacи perlдля чтения параграфа за раз. Не требует чтения всего файла в память.

tac file | perl -00 -lpe '$_ = join "\n", reverse split /\n/'

Переверните все строки файла, затем для каждого перевернутого абзаца переверните строки.

Гленн Джекман
источник
Это выглядит очень элегантно и эффективно. Тем не менее, это решение также объединяет несколько пустых (т.е. разделяющих) строк в одну
Martin Vegter
3

Возможно, есть способ сделать это sed, но я сомневаюсь, что это будет просто. Вот как я бы сделал это в Perl:

perl -n00e 'push @paragraphs,$_; END{print for reverse @paragraphs}' your_file

Это работает, потому что определение разделителя входной записи как нулевого символа ( -00) указывает Perl работать в режиме абзаца. Определение абзаца 1 в Perl точно соответствует вашему определению.


1 Посмотрите под заголовкомOther values for $/

Джозеф Р.
источник
это действительно работает. Единственная небольшая проблема заключается в том, что она не сохраняет несколько пустых строк, разделяющих абзацы. Вместо этого все абзацы разделены ровно одной пустой строкой.
Мартин Вегтер
1

Если ваши абзацы всегда разделены одной пустой строкой:

sed '/^$/s/^/\x02/' infile | tr \\n$'\002' $'\003'\\n | \
sed 's/^\x03//;1s/\x03$//;1!G;h;$!d;$a\' | tr $'\003' \\n

Это довольно легко увидеть , как это работает , если вы разбить его на куски и работать sed '/^$/s/^/\x02/' infileпотом sed '/^$/s/^/\x02/' infile | tr \\n$'\002' $'\003'\\nи так далее ...


Если ваши абзацы разделены одной или несколькими пустыми строками, например

Para 1 line 1
Para 1 line 2

Para 2 line 1


Para 3 line 1
Para 3 line 2

Para 4 line 1
Para 4 line 2



Para 5 line 1

и вы хотите изменить порядок абзацев, но сохранив порядок «пустых блоков», вы можете прочитать файл дважды:
1-й: превратить абзацы в одну строку (удаляя пустые блоки между ними) и повернуть их вспять и
2-й: превратить пустые блоки в отдельные строки «индексируют» количество пустых строк в каждом блоке (и удаляют непустые строки),
затем pasteрезультаты и обрабатывают выходные данные для восстановления новых строк:

paste -d $'\004' <(sed '/^$/s/^/\x02/' infile | tr \\n$'\002' $'\003'\\n | \
sed -e '/^\x03$/d;s/^\x03//;s/\x03$//;1!G;h;$!d;$a\') \
<(sed -E '/^$/!d;//{:a;N;/^(\n){1,}$/ba;s/\n/\x02/g;s/(.*)\x02.*/\1/}' infile) \
| sed '$!s/\x04/\n/;$s/\x04$//' | tr $'\003\002' \\n\\n

какие выводы:

Para 5 line 1

Para 4 line 1
Para 4 line 2


Para 3 line 1
Para 3 line 2

Para 2 line 1



Para 1 line 1
Para 1 line 2

Если вы не возражаете против дополнительной строки в выводе, вы можете оставить последнюю sed:

paste -d $'\n' <(sed '/^$/s/^/\x02/' infile | tr \\n$'\002' $'\003'\\n | \
sed -e '/^\x03$/d;s/^\x03//;s/\x03$//;1!G;h;$!d;$a\') \
<(sed -E '/^$/!d;//{:a;N;/^(\n){1,}$/ba;s/\n/\x02/g;s/(.*)\x02.*/\1/}' infile) | \
tr $'\003\002' \\n\\n

Они предполагают, что первая и последняя строки не пусты (и нет \x02, \x03или \x04во входных данных).

оборота don_crissti
источник
1

Вы МОЖЕТЕ сделать это с одним экземпляром sed; трубы не нужны. Поскольку sedтолько один проход по документу и часть файла, необходимая в качестве начала вывода, находится в конце файла, потребуется удерживать весь файл в памяти внутри sed(в области удержания) - так что это может плохо масштабируется. Но это точно отвечает на вопрос:

:getpara
   ${
      s/$/\
/
      G
      s/\n\n$//
      q
   }
   N
   /\n$/!bgetpara
G
h
$!d
s/\n\n$//
q

Если нет завершающего символа новой строки, все равно работает нормально. Если есть один завершающий символ новой строки, он подавляется в выходных данных (т. Е. В выходных данных не будет ведущего символа новой строки). Если на входе (например) 5 завершающих строк новой строки, на выходе будет 4 ведущих строки новой строки.

Пробелы между абзацами сохраняются.

Пробелы в пустой строке НЕ рассматриваются как разрыв абзаца, но это особенность, а не ошибка. :)

Вы также можете сделать это как гораздо менее читаемый однострочный:

sed ':k;${;s/\(\(\n\).*\)$/\1\2/;G;s/\n\n$//;q;};N;/\n$/!bk;G;h;$!d;s/\n\n$//;q' inputfile

Хотя это работает только с GNU sed. (Обратите внимание на хитрое использование обратных ссылок для выполнения s/$/\n/. Без этого он не был бы буквальным однострочным, поскольку содержал бы обратную косую черту.)

Wildcard
источник
так что вы хлестаете файл, верно? Похоже, вы положили все это в трюмное пространство. ж / G;h. Вы могли бы упомянуть что-то об ограничениях ввода или подобное.
mikeserv
Я не тестировал однострочник, потому что я работаю с моего Mac и у меня нет под sedрукой GNU , но версия скрипта определенно сохраняет пробелы между абзацами. Я только что проверил это на вашем входе. Вы тестировали версию скрипта?
Подстановочный
@mikeserv: определенно верно. (Будет обновление сегодня вечером.)
Wildcard
0
gem install facets

ruby -r facets/string \
     -e 'puts $stdin.read.strip.shatter(/\n\n+/).reverse.join("")' < file

Это должно сохранить интервал между абзацами (будучи более читабельным, чем sed:)). Хотя, реквизит devnull для потрясающего ответа.

Amadan
источник