Як видалити кілька нових рядків на EOF?


25

У мене є файли, які закінчуються в одному або декількох нових рядках і повинні закінчуватися лише в одному новому рядку. Як я можу це зробити за допомогою інструментів Bash / Unix / GNU?

Приклад неправильного файлу:

1\n
\n
2\n
\n
\n
3\n
\n
\n
\n

Приклад виправленого файлу:

1\n
\n
2\n
\n
\n
3\n

Іншими словами: Між EOF та останнім новим рядком символу файлу має бути рівно один новий рядок.

Довідкова реалізація

Прочитайте вміст файлу, відріжте один новий рядок, поки в кінці не буде більше двох нових рядків, запишіть його назад:

#! /bin/python

import sys

with open(sys.argv[1]) as infile:
    lines = infile.read()

while lines.endswith("\n\n"):
    lines = lines[:-1]

with open(sys.argv[2], 'w') as outfile:
    for line in lines:
        outfile.write(line)

Уточнення: Звичайно, трубопроводи дозволяються, якщо це більш елегантно.

Відповіді:


16
awk '/^$/ {nlstack=nlstack "\n";next;} {printf "%s",nlstack; nlstack=""; print;}' file

2
+1: рішення awk (майже) завжди елегантні і читабельні!
Олів'є Дулак

@OlivierDulac Дійсно. Коли я побачив sedпропозицію, я просто подумав про OMG ...
Hauke ​​Laging

1
це не працює на OSX Mavericks, використовуючи останній доступний awk від Homebrew. Це помилки з awk: illegal statement. brew install mawkі зміна команди mawkпрацює на роботу.
tjmcewan

@noname Я навіть не розумію питання ...
Hauke ​​Laging

Будь-який awk, у якому не працює сценарій, - це сильно зламаний awk - припиніть його використовувати і отримайте новий awk, тому що якщо він не може цього зробити, то хто знає, які інші поломки у нього є.
Ед Мортон

21

З корисних однорядкових скриптів для sed .

# Delete all trailing blank lines at end of file (only).
sed -e :a -e '/^\n*$/{$d;N;};/\n$/ba' file

4
Дякую, я використав наступне, щоб зробити це замість кількох файлів: find . -type f -name '*.js' -exec sed --in-place -e :a -e '/^\n*$/{$d;N;};/\n$/ba' {} \;
jakub.g

@ jakub.g на місці і рекурсивний - саме те, що мені потрібно. Дякую тобі.
Буттер Буткус

Щоб додати до відмінного коментаря від @ jakub.g, ви можете викликати таку команду в ОС X:find . -type f -name '*.js' -exec sed -i '' -e :a -e '/^\n*$/{$d;N;};/\n$/ba' {} \;
davejagoda

18

Оскільки у вас вже є відповіді з більш підходящими інструментами sed і awk; Ви можете скористатися тим, що $(< file)позбавляє проміжних порожніх рядків.

a=$(<file); printf '%s\n' "$a" > file

Цей дешевий злом не буде працювати з видаленням порожніх рядків, які можуть містити пробіли та інші символи, що не надруковані, лише для видалення порожніх рядків. Він також не працюватиме, якщо файл містить нульові байти.

У оболонках, крім bash та zsh, використовуйте $(cat file)замість $(<file).


+1, щоб зазначити, що схоже на помилку: $ (<файл) насправді не читає файл? Чому він відкидає сліди нових рядків? (так, я щойно перевірив, дякую, що вказав на це!)
Олів'є Дулак,

2
@OlivierDulac $()відкидає нові лінії . Це дизайнерське рішення. Я припускаю, що це полегшить інтеграцію в інші рядки: echo "On $(date ...) we will meet."було б зло з новим рядком, який майже кожна команда оболонки видає в кінці.
Hauke ​​Laging

@HaukeLaging: добре, це, мабуть, джерело такої поведінки
Олів'є Дулак

Я додав особливий випадок , щоб уникнути додавання «\ п» спустошити файли: [[ $a == '' ]] || printf '%s\n' "$a" >"$file".
davidchambers

Щоб зняти кілька нових рядків з початку файлу, вставляйте tac в процес (я використовую gnu coreutils на Mac, тому gtac для мене):a=$(gtac file.txt); printf '%s\n' "$a" | gtac > file.txt
r_alex_hall


4

Це питання позначене , але рішення ніхто не запропонував ed.

Ось один:

ed -s file <<'ED_END'
a

.
?^..*?+1,.d
w
ED_END

або, що еквівалентно,

printf '%s\n' a '' . '?^..*?+1,.d' w | ed -s file

ed розмістить вас в останньому рядку буфера редагування за замовчуванням при запуску.

Перша команда ( a) додає порожній рядок до кінця буфера (порожній рядок у сценарії редагування - це цей рядок, а крапка ( .) - лише для повернення в командний режим).

Друга команда ( ?) шукає найближчий попередній рядок, який містить щось (навіть символи пробілу), а потім видаляє все до кінця буфера з наступного рядка далі.

Третя команда ( w) записує файл назад на диск.

Доданий порожній рядок захищає решту файлу від видалення у випадку, якщо в кінці вихідного файлу немає порожніх рядків.


3

Ось рішення Perl, яке не потребує одночасного читання більше одного рядка в пам'яті:

my $n = 0;
while (<>) {
    if (/./) {
        print "\n" x $n, $_;
        $n = 0;
    } else {
        $n++;
    }
}

або, як однолінійний:

perl -ne 'if (/./) { print "\n" x $n, $_; $n = 0 } else { $n++ }'

Це зчитує файл у рядок за один раз і перевіряє кожен рядок, щоб побачити, чи містить він не новий рядок. Якщо цього не відбувається, він збільшує лічильник; якщо це так, він друкує кількість нових рядків, зазначених лічильником, а потім самим рядком, а потім скидає лічильник.

Технічно навіть буферизація одного рядка в пам’яті не потрібна; можна було б вирішити цю проблему, використовуючи постійний об'єм пам'яті, читаючи файл у шматки фіксованої довжини та обробляючи його символом за допомогою символу за допомогою станкової машини. Однак я підозрюю, що це було б зайвим чином для типового випадку використання.


1

Якщо ваш файл достатньо малий, щоб потрапити в пам'ять, ви можете використовувати це

perl -e 'local($/);$f=<>; $f=~s/\n*$/\n/;print $f;' file

0

У python (я знаю, що це не те, що ви хочете, але це набагато краще, оскільки він оптимізований, і прелюдія до версії bash), не переписуючи файл і не читаючи весь файл (що добре, якщо файл дуже великий):

#!/bin/python
import sys
infile = open(sys.argv[1], 'r+')
infile.seek(-1, 2)
while infile.read(1) == '\n':
  infile.seek(-2, 1)
infile.seek(1, 1)
infile.truncate()
infile.close()

Зауважте, що він не працює у файлах, де символ EOL не є \ \ n '.


0

Версія bash, що реалізує алгоритм python, але менш ефективна, оскільки потребує багатьох процесів:

#!/bin/bash
n=1
while test "$(tail -n $n "$1")" == ""; do
  ((n++))
done
((n--))
truncate -s $(($(stat -c "%s" "$1") - $n)) "$1"

0

Цей швидкий набір, і, якщо ви знаєте sed, легко запам’ятати:

tac < file | sed '/[^[:blank:]]/,$!d' | tac

Він використовує сценарій sed для видалення провідних порожніх рядків з корисних сценаріїв для одного рядка sed , на які посилається Олексій, вище та tac (зворотний кот).

У швидкому тесті на 64 Мб, 64 000 рядкових файлів, підхід Олексія був швидшим (0,036 проти 0,046 секунди).

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