Как я могу удалить новую строку, если это последний символ в файле?
У меня есть несколько файлов, которые я хотел бы удалить последним символом новой строки, если это последний символ в файле. od -c
показывает, что команда, которую я запускаю, записывает файл с новой строкой:
0013600 n t > \n
Я попробовал несколько трюков с sed, но лучшее, что я мог придумать, это не делать трюк:
sed -e '$s/\(.*\)\n$/\1/' abc
Есть идеи, как это сделать?
23 ответа
perl -pe 'chomp if eof' filename >filename2
или, чтобы отредактировать файл на месте:
perl -pi -e 'chomp if eof' filename
[Примечание редактора: -pi -e
был изначально -pie
, но, как отметили несколько комментаторов и объяснил @hvd, последний не работает.]
На веб-сайте awk это было описано как "богохульство perl".
Но в тесте это сработало.
Вы можете воспользоваться тем, что подстановки команд оболочки удаляют завершающие символы новой строки:
Простая форма, которая работает в 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.
Вы можете сделать это с 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
и поддерживает редактирование на месте.
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
Простофиля
awk '{q=p;p=$0}NR>1{print q}END{ORS = ""; print p}' file
Быстрое решение использует утилиту усечения gnu:
[ -z $(tail -c1 file) ] && truncate -s-1
Тест будет верным, если в файле есть завершающая новая строка.
Удаление выполняется очень быстро, действительно на месте, новый файл не требуется, и поиск также читает с конца только один байт (tail -c1).
Очень простой метод для однострочных файлов, требующий эхо GNU от coreutils:
/bin/echo -n $(cat $file)
Если вы хотите сделать это правильно, вам нужно что-то вроде этого:
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
, Мы используем это число для поиска одного символа, а затем читаем этот один символ. Если это новая строка, мы усекаем файл до символа перед новой строкой, в противном случае мы ничего не делаем.
Это выполняется в постоянном времени и постоянном пространстве для любого ввода и не требует больше дискового пространства.
Вот хорошее, аккуратное решение 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"))
$ perl -e 'local $/; $_ = <>; s/\ п $//; print' a-text-file.txt
Смотрите также Подберите любой символ (включая символы новой строки) в sed.
Предполагая Unix тип файла, и вы хотите только последний перевод строки, это работает.
sed -e '${/^$/d}'
Он не будет работать на нескольких новых строках...
* Работает, только если последняя строка является пустой строкой.
Это хорошее решение, если вам нужно работать с конвейерами / перенаправлениями вместо чтения / вывода из или в файл. Это работает с одной или несколькими строками. Это работает, есть ли завершающий перевод строки или нет.
# 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
вместо.
Используя дд:
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
Рубин:
ruby -ne 'print $stdin.eof ? $_.strip : $_'
или же:
ruby -ane 'q=p;p=$_;puts q if $.>1;END{print p.strip!}'
Еще один ответ 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
POSIX САС:
$ - 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.
sed -n "1 x;1 !H
$ {x;s/\n*$//p;}
" YourFile
Следует удалить все последние вхождения \n в файле. Не работает с огромным файлом (из-за ограничения sed-буфера)
Вот простое решение, использующее sed. Ваши версии sed должны поддерживать
-z
вариант.
-z, --null-data
separate lines by NUL characters
Его можно использовать в конвейере или использовать для редактирования файла на месте с помощью
-i
вариант
sed -ze 's/\n$//' file
Единственный раз, когда я хотел сделать это, для кода гольф, а затем я просто скопировал свой код из файла и вставил его в echo -n 'content'>file
заявление.
У меня была похожая проблема, но я работал с файлом Windows, и мне нужно сохранить эти CRLF - мое решение для Linux:
sed 's/\r//g' orig | awk '{if (NR>1) printf("\r\n"); printf("%s",$0)}' > tweaked