Як я можу видалити новий рядок, якщо він є останнім символом у файлі?


162

У мене є деякі файли, які я хотів би видалити останній новий рядок, якщо це останній символ у файлі. od -cпоказує мені, що команда, яку я запускаю, записує файл із зворотним новим рядком:

0013600   n   t  >  \n

Я спробував кілька трюків з sed, але найкраще, що я міг придумати, - це не робити трюк:

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

Будь-які ідеї, як це зробити?


4
newline - це лише один символ для unix new рядків. Нові рядки DOS - це два символи. Звичайно, буквальне "\ n" - це два символи. Кого ви насправді шукаєте?
Призупинено до подальшого повідомлення.

3
Хоча представництво може бути \n, у Linux є один символ
pavium

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

Звичайна причина робити що - то на кшталт цього, щоб видалити Кома з останнього рядка файлу CSV. Sed працює добре, але до нових рядків доводиться ставитися по-різному.
pavium

9
@ ThomasPadron-McCarthy "В обчислювальній справі, для кожної вагомої причини є щось робити, є вагома причина, щоб цього не робити і робити візу навпаки". -Ісус - "ти цього не повинен робити" - жахлива відповідь незалежно від питання. Правильний формат такий: [як це зробити], але [чому це може бути поганою ідеєю]. #sacrilege
Cory Mawhorter

Відповіді:


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

або, щоб відредагувати файл на місці:

perl -pi -e 'chomp if eof' filename

[Примітка редактора: -pi -eспочатку -pie, але, як зазначили кілька коментаторів і пояснив @hvd, остання не працює.]

На веб-сайті awk, яке я бачив, це було описано як "кощунство".

Але, в тесті, це спрацювало.


11
Ви можете зробити це більш безпечним, використовуючи chomp. І це б'ється промацуючи файл.
Sinan Ünür

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

13
Найцікавіше в богохульстві та єресі - це, як правило, ненависть, оскільки це правильно. :)
Ефір

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 - EVIL

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 доступний, перейдіть до прийнятої відповіді - вона проста та ефективна для пам'яті (не читає одразу весь вхідний файл).

  • В іншому випадку врахуйте відповідь Awk ghostdog74 - це малозрозуміло, але також ефективно для пам'яті ; більше читають еквівалент (POSIX-сумісний) є:

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


3
Зверніть увагу: якщо в кінці файлу є кілька нових рядків, ця команда видалить їх усі.
Sparhawk

47

Це можна зробити за 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 в коментарях і використовується в Sorentar в відповідь , truncate --size=-1може замінити head -c-1і опори в місці редагування.


3
Найкраще рішення з усіх поки що. Використовує стандартний інструмент, який має справді кожен дистрибутив Linux, і є чітким і зрозумілим, без будь-якого майстра sed або perl.
Даккарон

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 = $ 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 / оболонки версій / дистрибутивів (я просто гуляв це & це була кроляча нора), тож я не впевнений, наскільки портативним (або виконавським) він би був насправді для будь-якого іншого, крім невеликих файлів - але для невеликих файлів - чудово.
Майкл

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
але це має недолік: не скидати права власності / дозволи на файл ...
Помилка

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 () розповість про наявність файлу. Використовуючи спробувати / крім, можливо,
вдасться

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, потрібне ім'я файлу)
michael


3
$ perl -e 'локальний $ /; $ _ = <>; s / \ n $ //; print 'a-text-file.txt

Див. Також Збіг будь-якого символу (включаючи нові рядки) в sed .


1
Це витягує всі нові рядки. Еквівалентноtr -d '\n'
Призупинено до подальшого повідомлення.

Це також добре, мабуть, менш богохульне, ніж павіум.
Тодд Партридж 'Gen2ly'

Сінан, хоча Linux і Unix можуть визначати текстові файли, що закінчуються новим рядком, Windows не вимагає такої вимоги. Наприклад, Блокнот запише лише ті символи, які ви вводите, не додаючи в кінці нічого зайвого. Для компіляторів C може знадобитися вихідний файл, щоб закінчився розрив рядка, але текстові файли C не є "просто" текстовими файлами, тому вони можуть мати додаткові вимоги.
Роб Кеннеді

У цьому ключі більшість міні-фільмів javascript / css видалить останні рядки та ще й створить текстові файли.
ysth

@Rob Кеннеді та @ysth: Є цікавий аргумент щодо того, чому такі файли насправді не є текстовими файлами.
Sinan Ünür

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
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рішення , яке працює навіть для непробельний останнього рядка: stackoverflow.com/a/52047796
wisbucky

1

Ще одна відповідь FTR (і моя улюблена!): Відлуння / кот річ, яку ви хочете зняти і зафіксувати вихід за допомогою зворотних посилань. Заключний новий рядок буде знято. Наприклад:

# 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 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.

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

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)


0

рубін:

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

або:

ruby -ane 'q=p;p=$_;puts q if $.>1;END{print p.strip!}'
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.