Как удалить несколько новых строк в 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, вы можете вызвать такую ​​команду на OS 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).

llua
источник
+1, чтобы показать, что мне кажется ошибкой: $ (<file) действительно не читает файл? почему он отбрасывает завершающие символы новой строки? (да, я только что проверил, спасибо, что указал на это!)
Оливье Дюлак
2
@OlivierDulac $()отбрасывает завершающие символы новой строки. Это дизайнерское решение. Я предполагаю, что это облегчит интеграцию в другие строки: echo "On $(date ...) we will meet."было бы плохо с переводом строки, который выводит почти каждая команда оболочки в конце.
Хауке Лагинг
@HaukeLaging: хорошая точка зрения, вероятно, это источник такого поведения
Оливье Дюлак
Я добавил особый случай , чтобы избежать добавления «\ п» опустошить файлы: [[ $a == '' ]] || printf '%s\n' "$a" >"$file".
Дэвид Чамберс
Чтобы удалить несколько строк новой строки в начале файла, вставьте 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.

jfg956
источник
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"
jfg956
источник
0

Это быстро набрать, и, если вы знаете, sed, легко запомнить:

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

Он использует скрипт sed для удаления начальных пустых строк из полезных однострочных скриптов для sed , на который ссылается Alexey, выше, и tac (reverse cat).

В быстром тесте на 18-мегабайтном файле с 64 000 строк подход Алексея был более быстрым (0,036 против 0,046 секунд).

freeB
источник