Как удалить новую строку, если она является последним символом в файле?
У меня есть несколько файлов, которые я бы хотел удалить последней новой строкой, если это последний символ в файле. 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
).
Ответ 11
$ perl -e 'local $/; $_ = <>; s/\n$//; print' a-text-file.txt
См. также Соответствует любому символу (включая символы перевода строки) в sed.
Ответ 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
.