Как удалить новую строку, если она является последним символом в файле?

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

0013600   n   t  >  \n

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

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

Любые идеи, как это сделать?

Ответ 1

perl -pe 'chomp if eof' filename >filename2

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

perl -pi -e 'chomp if eof' filename

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

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

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

Ответ 2

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

Простая форма, которая работает в 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)

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

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

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

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

Ответ 3

Вы можете сделать это с помощью 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
}

Обновление

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

Ответ 4

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

Ответ 5

Gawk

   awk '{q=p;p=$0}NR>1{print q}END{ORS = ""; print p}' file

Ответ 6

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

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 ed до конца файла. Затем мы получаем числовое положение конца файла с помощью tell. Мы используем это число для поиска одного символа, а затем мы читаем этот символ. Если это новая строка, мы обрезаем файл символу перед этой новой строкой, иначе мы ничего не делаем.

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

Ответ 7

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

/bin/echo -n $(cat $file)

Ответ 8

Вот хорошее, аккуратное решение 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"))

Ответ 9

Еще один perl WTDI:

perl -i -p0777we's/\n\z//' filename

Ответ 10

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

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

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

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

Ответ 12

Использование dd:

file='/path/to/file'
[[ "$(tail -c 1 "${file}" | tr -dc '\n' | wc -c)" -eq 1 ]] && \
    printf "" | dd  of="${file}" seek=$(($(stat -f "%z" "${file}") - 1)) bs=1 count=1
    #printf "" | dd  of="${file}" seek=$(($(wc -c < "${file}") - 1)) bs=1 count=1

Ответ 13

Предположим, что тип файла Unix и вы хотите, чтобы последняя новая строка работала.

sed -e '${/^$/d}'

Он не будет работать с несколькими символами новой строки...

* Работает только в том случае, если последняя строка является пустой строкой.

Ответ 14

perl -pi -e 's/\n$// if(eof)' your_file

Ответ 15

Еще один ответ FTR (и мой любимый!): echo/cat - вещь, которую вы хотите снять и захватить вывод через обратные ссылки. Окончательная новая строка будет удалена. Например:

# Sadly, outputs newline, and we have to feed the newline to sed to be portable
echo thingy | sed -e 's/thing/sill/'

# No newline! Happy.
out=`echo thingy | sed -e 's/thing/sill/'`
printf %s "$out"

# Similarly for files:
file=`cat file_ending_in_newline`
printf %s "$file" > file_no_newline

Ответ 16

POSIX SED:

'$ {/^ $/d}'

$ - match last line


{ COMMANDS } - A group of commands may be enclosed between { and } characters. This is particularly useful when you want a group of commands to be triggered by a single address (or address-range) match.

Ответ 17

Единственный раз, когда я хотел это сделать, - это использовать код для гольфа, а затем я только что скопировал свой код из файла и вставлял его в оператор echo -n 'content'>file.

Ответ 18

sed ':a;/^\n*$/{$d;N;};/\n$/ba' file

Ответ 19

У меня была аналогичная проблема, но я работал с файлом Windows, и мне нужно сохранить эти CRLF - мое решение в linux:

sed 's/\r//g' orig | awk '{if (NR>1) printf("\r\n"); printf("%s",$0)}' > tweaked

Ответ 20

sed -n "1 x;1 !H
$ {x;s/\n*$//p;}
" YourFile

Должно удалить любое последствие \n в файле. Не работает над огромным файлом (из-за ограничения буфера sed)

Ответ 21

рубин:

ruby -ne 'print $stdin.eof ? $_.strip : $_'

или

ruby -ane 'q=p;p=$_;puts q if $.>1;END{print p.strip!}'

Ответ 22

Это хорошее решение, если вам нужно работать с каналами/перенаправлением вместо чтения/вывода из файла или в файл. Это работает с одной или несколькими строками. Он работает вне зависимости от того, есть ли перевод строки.

# with trailing newline
echo -en 'foo\nbar\n' | sed '$s/$//' | head -c -1

# still works without trailing newline
echo -en 'foo\nbar' | sed '$s/$//' | head -c -1

# read from a file
sed '$s/$//' myfile.txt | head -c -1

Детали:

  • head -c -1 усекает последний символ строки, независимо от того, что это за символ. Поэтому, если строка не заканчивается новой строкой, вы потеряете символ.
  • Поэтому для решения этой проблемы мы добавим еще одну команду, которая добавит завершающий символ новой строки, если его нет: sed '$s/$//'. Первый $ означает применить команду только к последней строке. s/$// означает замену "конца строки" словом "ничего", которое в основном ничего не делает. Но у него есть побочный эффект добавления завершающего символа новой строки:

Примечание: Mac по умолчанию head не поддерживает опцию -c. Вы можете сделать brew install coreutils и использовать вместо него ghead.