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

113

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

0013600   n   t  >  \n

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

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

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

  • 4
    символ новой строки - только один символ для символов новой строки unix. DOS переводы строк состоят из двух символов. Конечно, литерал "\ n" состоит из двух символов. Что вы на самом деле ищете?
  • 3
    Хотя представление может быть \n , в Linux это один символ
Показать ещё 6 комментариев
Теги:
awk
sed

21 ответ

172
Лучший ответ
perl -pe 'chomp if eof' filename >filename2

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

perl -pi -e 'chomp if eof' filename

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

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

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

  • 0
    Да, это работает.
  • 11
    Вы можете сделать это безопаснее, используя chomp . И это лучше, чем хлебать файл.
Показать ещё 11 комментариев
42

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

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

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

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

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

Примечание:

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

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

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

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

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

  • 3
    Примечание: если в конце файла есть несколько новых строк, эта команда удалит их все.
29

Вы можете сделать это с помощью 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
}
  • 3
    Лучшее решение из всех на данный момент. Использует стандартный инструмент, который есть в каждом дистрибутиве Linux, и является лаконичным и понятным, без каких-либо sed или perl wizardry.
  • 1
    Хорошее решение. Одним из изменений является то, что я думаю, что я бы использовал truncate --size=-1 вместо head -c -1 поскольку он просто изменяет размер входного файла, а не читает во входном файле, записывает его в другой файл и затем заменяет исходный с выходным файлом.
Показать ещё 2 комментария
15
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

  • 0
    Хороший оригинальный способ думать об этом. Спасибо Деннис.
  • 0
    версия awk также удаляет пустые строки
Показать ещё 9 комментариев
9

Gawk

   awk '{q=p;p=$0}NR>1{print q}END{ORS = ""; print p}' file
  • 0
    Все еще выглядит как много персонажей для меня ... учиться медленно :). Делает работу, хотя. Спасибо, призрак.
  • 1
    awk '{ prev_line = line; line = $0; } NR > 1 { print prev_line; } END { ORS = ""; print line; }' file должен быть легче для чтения.
Показать ещё 1 комментарий
8

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

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

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

  • 2
    но у этого есть недостаток - не сбрасывать права владения / разрешения для файла ... э-э, подождите ...
  • 0
    Подробный, но быстрый и надежный - кажется, единственный верный ответ для редактирования файла на месте (и поскольку он может быть не очевиден для всех: это скрипт на Perl ).
5

Вот хорошее, аккуратное решение 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"))
  • 0
    os.path.isfile () расскажет вам о наличии файла. Использование try / кроме может поймать много разных ошибок :)
4

Еще один perl WTDI:

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

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

/bin/echo -n $(cat $file)
  • 0
    Это достойный способ, если он не слишком дорогой (повторяющийся).
  • 0
    Я думаю, что это решение заслуживает большего количества голосов.
Показать ещё 3 комментария
3
  • 1
    Это убирает все новые строки. Эквивалент tr -d '\n'
  • 0
    @ Денис Уильямсон: отмечено и исправлено.
Показать ещё 4 комментария
2

В быстром решении используется утилита утилиты gnu:

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

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

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

  • 1
    любой способ заставить это работать с stdin?
  • 1
    усечение: отсутствует операнд файла
Показать ещё 1 комментарий
2
perl -pi -e 's/\n$// if(eof)' your_file
  • 0
    По сути, такой же, как принятый ответ, но, возможно, более понятный для пользователей, не являющихся пользователями Perl. Обратите внимание, что нет необходимости использовать g или круглые скобки вокруг eof : perl -pi -e 's/\n$// if eof' your_file .
2

Использование 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
2

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

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

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

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

  • 7
    Это работает, только если последняя строка пуста.
  • 0
    Вот решение sed которое работает даже для непустой последней строки: stackoverflow.com/a/52047796
1

Еще один ответ 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
    Я обнаружил комбо cat-printf случайно (пытался получить противоположное поведение). Обратите внимание, что это удалит ВСЕ завершающие новые строки, а не только последние.
0

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.
  • 0
    Я думаю, что это будет только удалить его, если последняя строка пуста. Он не удалит завершающий символ новой строки, если последняя строка не пуста. Например, echo -en 'a\nb\n' | sed '${/^$/d}' ничего не удалит. echo -en 'a\nb\n\n' | sed '${/^$/d}' удалит, так как вся последняя строка пуста.
0

рубин:

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

или

ruby -ane 'q=p;p=$_;puts q if $.>1;END{print p.strip!}'
0
sed -n "1 x;1 !H
$ {x;s/\n*$//p;}
" YourFile

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

0

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

sed 's/\r//g' orig | awk '{if (NR>1) printf("\r\n"); printf("%s",$0)}' > tweaked
0
sed ':a;/^\n*$/{$d;N;};/\n$/ba' file
  • 0
    Работает, но удаляет все завершающие символы новой строки.
0

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

  • 1
    Эй, теперь ... это работает.
  • 0
    Почти на месте; полный подход здесь .

Ещё вопросы

Сообщество Overcoder
Наверх
Меню