Как я могу удалить новую строку, если это последний символ в файле? - PullRequest
146 голосов
/ 31 октября 2009

У меня есть несколько файлов, которые я хотел бы удалить последней новой строкой, если это последний символ в файле. od -c показывает, что команда, которую я запускаю, записывает файл с новой строкой:

0013600   n   t  >  \n

Я попробовал несколько трюков с sed, но лучшее, что я мог придумать, это не делать трюк:

sed -e '$s/\(.*\)\n$/\1/' abc

Есть идеи, как это сделать?

Ответы [ 22 ]

206 голосов
/ 31 октября 2009
perl -pe 'chomp if eof' filename >filename2

или, чтобы отредактировать файл на месте:

perl -pi -e 'chomp if eof' filename

[Примечание редактора: -pi -e изначально -pie, но, как отметили несколько комментаторов и объяснил @hvd, последний не работает.]

Это было описано как «богохульство perl» на сайте awk, который я видел.

Но в тесте это сработало.

53 голосов
/ 27 августа 2012

Вы можете воспользоваться тем, что shell подстановки команд удаляют завершающие символы новой строки :

Простая форма, которая работает в bash, ksh, zsh:

printf %s "$(< in.txt)" > out.txt

Портативная (POSIX-совместимая) альтернатива (чуть менее эффективная):

printf %s "$(cat in.txt)" > out.txt

Примечание:

  • Если in.txt заканчивается несколькими символами новой строки, команда подстановки удаляет всех из них - спасибо, @Sparhawk. (Он не удаляет пробельные символы, кроме завершающих символов новой строки.)
  • Поскольку этот подход считывает весь входной файл в память , рекомендуется использовать только файлы меньшего размера.
  • printf %s гарантирует, что новая строка не добавляется к выходу (это POSIX-совместимая альтернатива нестандартной echo -n; см. http://pubs.opengroup.org/onlinepubs/009696799/utilities/echo.html и https://unix.stackexchange.com/a/65819)

A руководство к другим ответам :

  • Если доступно Perl , перейдите к принятому ответу - это просто и эффективно для памяти (не читает весь ввод файл сразу).

  • В противном случае рассмотрим ghostdog74's Awk ответ - это неясно, но также эффективно для памяти ; более читаемый эквивалент (POSIX-совместимый):

    • awk 'NR > 1 { print prev } { prev=$0 } END { ORS=""; print }' in.txt
    • Печать задерживается на одну строку, поэтому последняя строка может обрабатываться в блоке END, где она печатается без запаздывания \n из-за установки разделителя выходной записи (OFS) на пустой строка.
  • Если вам нужно подробное, но быстрое и надежное решение, которое действительно редактирует на месте (в отличие от создания временного файла, который затем заменяет исходный) рассмотрим jrockway скрипт Perl .

44 голосов
/ 25 сентября 2012

Вы можете сделать это с помощью head из GNU coreutils, он поддерживает аргументы, относящиеся к концу файла. Итак, чтобы не использовать последний байт:

head -c -1

Чтобы проверить окончание новой строки, вы можете использовать tail и wc. В следующем примере результат сохраняется во временный файл, а затем перезаписывается оригинал:

if [[ $(tail -c1 file | wc -l) == 1 ]]; then
  head -c -1 file > file.tmp
  mv file.tmp file
fi

Вы также можете использовать sponge из moreutils для редактирования на месте:

[[ $(tail -c1 file | wc -l) == 1 ]] && head -c -1 file | sponge file

Вы также можете сделать функцию многократного использования, вставив ее в файл .bashrc:

# Example:  remove-last-newline < multiline.txt
function remove-last-newline(){
    local file=$(mktemp)
    cat > $file
    if [[ $(tail -c1 $file | wc -l) == 1 ]]; then
        head -c -1 $file > $file.tmp
        mv $file.tmp $file
    fi
    cat $file
}

Обновление

Как отмечено KarlWilbur в комментариях и используется в Ответ Сорентара , truncate --size=-1 может заменить head -c-1 и поддерживает редактирование на месте. 1030 *

16 голосов
/ 31 октября 2009
head -n -1 abc > newfile
tail -n 1 abc | tr -d '\n' >> newfile

Редактировать 2:

Вот awk версия (исправлено) , которая не накапливает потенциально огромный массив:

awk '{if (line) print line; line = $ 0} END {printf $ 0} 'abc

10 голосов
/ 31 октября 2009

простак

   awk '{q=p;p=$0}NR>1{print q}END{ORS = ""; print p}' file
8 голосов
/ 15 июня 2016

Очень простой метод для однострочных файлов, требующий эхо GNU от coreutils:

/bin/echo -n $(cat $file)
8 голосов
/ 02 ноября 2009

Если вы хотите сделать это правильно, вам нужно что-то вроде этого:

use autodie qw(open sysseek sysread truncate);

my $file = shift;
open my $fh, '+>>', $file;
my $pos = tell $fh;
sysseek $fh, $pos - 1, 0;
sysread $fh, my $buf, 1 or die 'No data to read?';

if($buf eq "\n"){
    truncate $fh, $pos - 1;
}

Открываем файл для чтения и добавления; открытие для добавления означает, что мы уже seek отправлены в конец файла. Затем мы получаем числовую позицию конца файла с tell. Мы используем это число для поиска одного символа, а затем читаем этот один символ. Если это новая строка, мы усекаем файл до символа перед новой строкой, в противном случае мы ничего не делаем.

Это выполняется в постоянном времени и постоянном пространстве для любого ввода и не требует больше дискового пространства.

5 голосов
/ 02 ноября 2009

Вот хорошее, аккуратное решение Python. Я не пытался быть кратким здесь.

Это изменяет файл на месте, а не делает копию файла и удаляет символ новой строки из последней строки копии. Если файл большой, это будет намного быстрее, чем решение Perl, которое было выбрано в качестве лучшего ответа.

Он усекает файл на два байта, если последние два байта равны CR / LF, или на один байт, если последний байт равен LF. Он не пытается изменить файл, если последние байты не являются (CR) LF. Он обрабатывает ошибки. Протестировано в Python 2.6.

Поместите это в файл с именем "striplast" и chmod +x striplast.

#!/usr/bin/python

# strip newline from last line of a file


import sys

def trunc(filename, new_len):
    try:
        # open with mode "append" so we have permission to modify
        # cannot open with mode "write" because that clobbers the file!
        f = open(filename, "ab")
        f.truncate(new_len)
        f.close()
    except IOError:
        print "cannot write to file:", filename
        sys.exit(2)

# get input argument
if len(sys.argv) == 2:
    filename = sys.argv[1]
else:
    filename = "--help"  # wrong number of arguments so print help

if filename == "--help" or filename == "-h" or filename == "/?":
    print "Usage: %s <filename>" % sys.argv[0]
    print "Strips a newline off the last line of a file."
    sys.exit(1)


try:
    # must have mode "b" (binary) to allow f.seek() with negative offset
    f = open(filename, "rb")
except IOError:
    print "file does not exist:", filename
    sys.exit(2)


SEEK_EOF = 2
f.seek(-2, SEEK_EOF)  # seek to two bytes before end of file

end_pos = f.tell()

line = f.read()
f.close()

if line.endswith("\r\n"):
    trunc(filename, end_pos)
elif line.endswith("\n"):
    trunc(filename, end_pos + 1)

P.S. В духе "Perl golf", вот мое самое короткое решение Python. Он отбирает весь файл из стандартного ввода в память, удаляет все символы новой строки с конца и записывает результат в стандартный вывод. Не так кратко, как Perl; вы просто не можете победить Perl за такие хитрые быстрые вещи, как этот.

Удалите «\ n» из вызова на .rstrip(), и он удалит все пробелы из конца файла, включая несколько пустых строк.

Поместите это в "slurp_and_chomp.py" и затем запустите python slurp_and_chomp.py < inputfile > outputfile.

import sys

sys.stdout.write(sys.stdin.read().rstrip("\n"))
4 голосов
/ 01 ноября 2009

Еще один Perl WTDI:

perl -i -p0777we's/\n\z//' filename
3 голосов
/ 13 ноября 2016

Быстрое решение использует утилиту gnu truncate:

[ -z $(tail -c1 file) ] && truncate -s-1

Тест будет верным, если в файле есть завершающая новая строка.

Удаление выполняется очень быстро, действительно на месте, новый файл не требуется, и поиск также читает с конца только один байт (tail -c1).

...