it-roy-ru.com

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

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

0013600   n   t  >  \n

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

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

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

142
Todd Partridge 'Gen2ly'
Perl -pe 'chomp if eof' filename >filename2

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

Perl -pi -e 'chomp if eof' filename

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

На веб-сайте awk это было описано как «богохульство Perl».

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

201
pavium

Вы можете воспользоваться тем, что 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 )

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

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

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

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

52
mklement0

Вы можете сделать это с помощью 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 и поддерживает редактирование на месте.

43
Thor
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

16
Dennis Williamson

простофиля

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

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

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

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

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;
}

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

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

8
jrockway

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

Постскриптум В духе "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"))
5
steveha

Еще один Perl WTDI:

Perl -i -p0777we's/\n\z//' filename
4
ysth
 $ Perl -e 'local $ /; $ _ = <>; s/\ п $ //; print 'a-text-file.txt 

Смотрите также Подберите любой символ (включая символы новой строки) в sed .

3
Sinan Ünür
Perl -pi -e 's/\n$// if(eof)' your_file
2
Vijay

Используя дд:

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
2
cpit

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

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

Это не будет работать на нескольких новых строках ...

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

2
LoranceStinson

Еще один ответ 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
1
Nicholas Wilson

POSIX САС:

'$ {/ ^ $/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.
1
Oleg Mazko

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

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

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

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

1
sorontar

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

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

Рубин:

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

или же:

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

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

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

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

# 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/$// означает заменить «конец строки» на «ничто», которое в основном ничего не делает. Но у него есть побочный эффект добавления завершающего символа новой строки, если его нет.

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

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

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

0
NeronLeVelu