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


162

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

0013600   n   t  >  \n

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

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

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


4
символ новой строки - только один символ для символов новой строки unix. DOS переводы строк состоят из двух символов. Конечно, литерал "\ n" состоит из двух символов. Что вы на самом деле ищете?
Приостановлено до дальнейшего уведомления.

3
Хотя представление может быть \n, в Linux это один символ
Pavium

10
Можете ли вы уточнить, почему вы хотите это сделать? Текстовые файлы должны заканчиваться концом строки, если они не полностью пусты. Мне кажется странным, что вы хотели бы иметь такой усеченный файл?
Томас Падрон-Маккарти

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

9
@ ThomasPadron-McCarthy "В компьютерных технологиях для любой веской причины есть что-то, есть веская причина не делать этого и наоборот". -Иисус - «ты не должен этого делать» - ужасный ответ, независимо от вопроса. Правильный формат: [как это сделать], но [почему это может быть плохой идеей]. #sacrilege
Кори Моухортер

Ответы:


223
perl -pe 'chomp if eof' filename >filename2

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

perl -pi -e 'chomp if eof' filename

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

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

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


11
Вы можете сделать это безопаснее, используя chomp. И это лучше, чем хлебать файл.
Синан Юнюр

6
Богохульство, хотя это так, это работает очень хорошо. perl -i -pe 'chomp if eof' имя файла. Спасибо.
Тодд Партридж 'Gen2ly'

13
Самое смешное в богохульстве и ереси - это то, что обычно ненавидят, потому что это правильно. :)
Ether

8
Небольшое исправление: вы можете использовать perl -pi -e 'chomp if eof' filename, чтобы редактировать файл на месте вместо создания временного файла
Romuald Brunet

7
perl -pie 'chomp if eof' filename-> Не могу открыть Perl-скрипт "chomp if eof": такого файла или каталога нет; perl -pi -e 'chomp if eof' filename-> работает
aditsu уйти, потому что SE зла

56

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

Простая форма, которая работает в 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) в пустую строку.
  • Если вам нужно подробное, но быстрое и надежное решение, которое действительно редактирует на месте (в отличие от создания временного файла, который затем заменяет оригинальный), рассмотрите скрипт Perl jrockway .


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

47

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

Вы также можете использовать spongefrom 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
}

Обновить

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


3
Лучшее решение из всех на данный момент. Использует стандартный инструмент, который есть в каждом дистрибутиве Linux, и является лаконичным и понятным, без каких-либо sed или perl wizardry.
Даккарон

2
Хорошее решение. Одним из изменений является то, что я думаю, что я бы использовал truncate --size=-1вместо, head -c -1поскольку он просто изменяет размер входного файла, а не читает во входном файле, записывает его в другой файл, а затем заменяет оригинал выходным файлом.
Карл Уилбур

1
Обратите внимание, что head -c -1последний символ будет удален независимо от того, является ли он новой строкой или нет, поэтому перед его удалением необходимо проверить, является ли последний символ новой строкой.
wisbucky

К сожалению, не работает на Mac. Я подозреваю, что это не работает на любом варианте BSD.
Эдвард Фальк

16
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


Хороший оригинальный способ думать об этом. Спасибо Деннис.
Тодд Партридж 'Gen2ly'

Ты прав. Я полагаюсь на вашу awkверсию. Требуется два смещения (и другой тест), и я использовал только один. Тем не менее, вы можете использовать printfвместо ORS.
Приостановлено до дальнейшего уведомления.

вы можете сделать вывод конвейером с подстановкой процесса:head -n -1 abc | cat <(tail -n 1 abc | tr -d '\n') | ...
BCoates

2
Использование -c вместо -n для головы и хвоста должно быть еще быстрее.
rudimeier

1
Для меня head -n -1 abc удалил последнюю актуальную строку файла, оставив завершающий символ новой строки; head -c -1 abc, похоже, работал лучше
ChrisV

10

простак

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

Все еще выглядит как много персонажей для меня ... учиться медленно :). Делает работу, хотя. Спасибо, призрак.
Тодд Партридж 'Gen2ly'

1
awk '{ prev_line = line; line = $0; } NR > 1 { print prev_line; } END { ORS = ""; print line; }' fileэто должно быть легче читать.
Евгений Павлюк

Как насчет: awk 'NR>1 {print p} {p=$0} END {printf $0}' file.
Исаак

@sorontar Первым аргументом printfявляется аргумент формата . Таким образом, если во входном файле есть что-то, что можно интерпретировать как описатель формата %d, вы получите ошибку. Исправление было бы изменить его наprintf "%s" $0
Робин А. Мид

9

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

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

Это достойный способ, если он не слишком дорогой (повторяющийся).

Это имеет проблемы, когда \nприсутствует. Поскольку это преобразовано в новую линию.
Крис Стрычински

Также, кажется, работает для многострочных файлов $(...), которые цитируются
Thor

определенно нужно процитировать это ... /bin/echo -n "$(cat infile)" Кроме того, я не уверен, какой будет максимальный размер echoили оболочка в версиях / дистрибутивах os / shell (я просто гуглил это, и это была кроличья нора), так что я не уверен, насколько переносимым (или быстродействующим) он будет для чего-либо, кроме небольших файлов - но для маленьких файлов - отлично.
Майкл

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

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


2
но имеет тот недостаток , что не переустановка собственности / права доступа для файла ... ERR, подождите ...
ysth

1
Подробный, но быстрый и надежный - кажется, единственный верный ответ для редактирования файла на месте (и поскольку он может быть не очевиден для всех: это скрипт на Perl ).
mklement0

6

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

PS В духе "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"))

os.path.isfile () расскажет вам о наличии файла. Использование try / Кроме того, может поймать много разных ошибок :)
Денис Барменков

5

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

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

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

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


1
усечение: отсутствует операнд файла
Брайан Ханней

2
в этом примере просто отсутствует конечное имя файла, т. е. [ -z $(tail -c1 filename) ] && truncate -s -1 filename(также, в ответ на другой комментарий, truncateкоманда не работает с stdin, требуется имя файла)
майкл


3
$ perl -e 'local $ /; $ _ = <>; s / \ п $ //; print 'a-text-file.txt

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


1
Это убирает все новые строки. Эквивалентноtr -d '\n'
Приостановлено до дальнейшего уведомления.

Это тоже хорошо работает, вероятно, менее кощунственно, чем павиумы.
Тодд Партридж 'Gen2ly'

Синан, хотя в Linux и Unix текстовые файлы могут заканчиваться символом новой строки, Windows не предъявляет таких требований. Блокнот, например, будет писать только символы, которые вы вводите, не добавляя ничего в конце. Компиляторам Си может потребоваться, чтобы исходный файл заканчивался переносом строки, но исходные файлы Си - это не просто текстовые файлы, поэтому они могут иметь дополнительные требования.
Роб Кеннеди

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

@Rob Кеннеди и @ysth: Там есть интересный аргумент о том, почему такие файлы не являются текстовыми файлами и тому подобное.
Синан Юнюр

2

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

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
perl -pi -e 's/\n$// if(eof)' your_file

Фактически такой же, как принятый ответ, но, возможно, более понятный по концепции для пользователей, не являющихся пользователями Perl. Обратите внимание , что нет никакой необходимости в gили в круглых скобках eof: perl -pi -e 's/\n$// if eof' your_file.
mklement0

2

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

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

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

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


Вот sedрешение, которое работает даже для
непустой

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

1

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.

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

1

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

# 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вместо этого.


0

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


Почти на месте; полный подход здесь .
mklement0


0

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

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

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

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


Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.