Làm cách nào để xóa một dòng mới nếu đó là ký tự cuối cùng trong một tệp?


162

Tôi có một số tệp mà tôi muốn xóa dòng mới nhất nếu đó là ký tự cuối cùng trong một tệp. od -ccho tôi thấy rằng lệnh tôi chạy sẽ ghi tệp với một dòng mới:

0013600   n   t  >  \n

Tôi đã thử một vài mẹo với sed nhưng cách tốt nhất tôi có thể nghĩ là không làm trò đó:

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

Bất kỳ ý tưởng làm thế nào để làm điều này?


4
dòng mới chỉ là một ký tự cho dòng mới unix. Dòng mới của DOS là hai ký tự. Tất nhiên, nghĩa đen "\ n" là hai ký tự. Mà bạn đang thực sự tìm kiếm?
Tạm dừng cho đến khi có thông báo mới.

3
Mặc dù đại diện có thể là \n, trong linux là một ký tự
pavium

10
Bạn có thể giải thích lý do tại sao bạn muốn làm điều này? Các tệp văn bản được cho là kết thúc bằng một dòng cuối, trừ khi chúng hoàn toàn trống. Nó có vẻ lạ đối với tôi rằng bạn muốn có một tập tin bị cắt ngắn như vậy?
Thomas Padron-McCarthy

Lý do thông thường để làm một cái gì đó như thế này là xóa dấu phẩy từ dòng cuối cùng của tệp CSV. Sed hoạt động tốt, nhưng các dòng mới phải được đối xử khác nhau.
pavium

9
@ ThomasPadron-McCarthy "Trong điện toán, vì mọi lý do chính đáng đều phải làm một cái gì đó tồn tại một lý do chính đáng để không làm điều đó và ngược lại." -Jesus - "bạn không nên làm điều đó" là một câu trả lời khủng khiếp cho dù câu hỏi là gì. Định dạng đúng là: [cách thực hiện] nhưng [tại sao nó có thể là ý tưởng tồi]. #sacrilege
Cory Mawhorter 30/03/2015

Câu trả lời:


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

hoặc, để chỉnh sửa tệp tại chỗ:

perl -pi -e 'chomp if eof' filename

[Ghi chú của biên tập viên: -pi -eban đầu -pie, nhưng, như được lưu ý bởi một số người bình luận và được giải thích bởi @hvd, cái sau không hoạt động.]

Điều này đã được mô tả như là một 'báng bổ perl' trên trang web awk mà tôi thấy.

Nhưng, trong một thử nghiệm, nó đã làm việc.


11
Bạn có thể làm cho nó an toàn hơn bằng cách sử dụng chomp. Và nó đập mạnh tập tin.
Sinan Ünür

6
Blasphemy mặc dù nó là, nó hoạt động rất tốt. perl -i -pe 'chomp if eof' tên tệp. Cảm ơn bạn.
Todd Partridge 'Gen2ly'

13
Điều buồn cười về sự báng bổ và dị giáo là nó thường bị ghét vì nó đúng. :)
Ether

8
Chỉnh sửa nhỏ: bạn có thể sử dụng perl -pi -e 'chomp if eof' filename, để chỉnh sửa một tệp tại chỗ thay vì tạo một tệp tạm thời
Romuald Brunet

7
perl -pie 'chomp if eof' filename-> Không thể mở tập lệnh perl "chomp if eof": Không có tệp hoặc thư mục như vậy; perl -pi -e 'chomp if eof' filename-> hoạt động
aditsu bỏ vì SE là EVIL

56

Bạn có thể tận dụng thực tế là các thay thế lệnh shell sẽ loại bỏ các ký tự dòng mới :

Hình thức đơn giản hoạt động trong bash, ksh, zsh:

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

Thay thế di động (tuân thủ POSIX) (hơi kém hiệu quả):

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

Ghi chú:

  • Nếu in.txtkết thúc bằng nhiều ký tự dòng mới, việc thay thế lệnh sẽ loại bỏ tất cả chúng - cảm ơn, @Sparhawk. (Nó không xóa các ký tự khoảng trắng ngoài các dòng mới.)
  • Vì cách tiếp cận này đọc toàn bộ tệp đầu vào vào bộ nhớ , chỉ nên dùng cho các tệp nhỏ hơn.
  • printf %sđảm bảo rằng không có dòng mới nào được thêm vào đầu ra (đó là thay thế tuân thủ POSIX cho tiêu chuẩn không theo tiêu chuẩn echo -n; xem http://pub.opengroup.org/onlinepub/009696799/utilities/echo.htmlhttps: //unix.stackexchange. com / a / 65819 )

Một hướng dẫn để các câu trả lời khác :

  • Nếu Perl khả dụng, hãy tìm câu trả lời được chấp nhận - nó đơn giản và tiết kiệm bộ nhớ (không đọc toàn bộ tệp đầu vào cùng một lúc).

  • Mặt khác, hãy xem xét câu trả lời Awk của ghostdog74 - nó tối nghĩa, nhưng cũng hiệu quả về bộ nhớ ; một tương đương dễ đọc hơn (tuân thủ POSIX) là:

    • awk 'NR > 1 { print prev } { prev=$0 } END { ORS=""; print }' in.txt
    • Việc in bị trì hoãn bởi một dòng để dòng cuối cùng có thể được xử lý trong ENDkhối, nơi nó được in mà không có dấu vết \ndo đặt dấu tách bản ghi đầu ra ( OFS) thành một chuỗi trống.
  • Nếu bạn muốn một giải pháp dài dòng, nhưng nhanh chóng và mạnh mẽ thực sự chỉnh sửa tại chỗ (trái ngược với việc tạo tệp tạm thời sau đó thay thế bản gốc), hãy xem xét tập lệnh Perl của jrockway .


3
NB nếu có nhiều dòng mới ở cuối tệp, lệnh này sẽ xóa tất cả chúng.
Sparhawk

47

Bạn có thể làm điều này với headtừ coreutils GNU, nó hỗ trợ các đối số có liên quan đến phần cuối của tệp. Vì vậy, để loại bỏ việc sử dụng byte cuối cùng:

head -c -1

Để kiểm tra một dòng mới kết thúc, bạn có thể sử dụng tailwc. Ví dụ sau lưu kết quả vào một tệp tạm thời và sau đó ghi đè lên bản gốc:

if [[ $(tail -c1 file | wc -l) == 1 ]]; then
  head -c -1 file > file.tmp
  mv file.tmp file
fi

Bạn cũng có thể sử dụng spongetừ moreutilsđể thực hiện chỉnh sửa "tại chỗ":

[[ $(tail -c1 file | wc -l) == 1 ]] && head -c -1 file | sponge file

Bạn cũng có thể tạo một chức năng có thể tái sử dụng chung bằng cách nhét chức năng này vào .bashrctệp của mình :

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

Cập nhật

Theo ghi nhận của KarlWilbur trong các bình luận và được sử dụng trong câu trả lời của Sorentar , truncate --size=-1có thể thay thế head -c-1và hỗ trợ chỉnh sửa tại chỗ.


3
Giải pháp tốt nhất cho đến nay. Sử dụng một công cụ tiêu chuẩn mà thực sự mọi bản phân phối Linux đều có, và ngắn gọn và rõ ràng, không có bất kỳ thuật sĩ sed hay perl nào.
Dakkaron

2
Giải pháp tốt đẹp. Một thay đổi là tôi nghĩ rằng tôi sẽ sử dụng truncate --size=-1thay head -c -1vì nó chỉ thay đổi kích thước tệp đầu vào thay vì đọc trong tệp đầu vào, ghi nó ra tệp khác, sau đó thay thế tệp gốc bằng tệp đầu ra.
Karl Wilbur

1
Lưu ý rằng head -c -1sẽ xóa ký tự cuối cùng bất kể đó có phải là dòng mới hay không, đó là lý do tại sao bạn phải kiểm tra xem ký tự cuối cùng có phải là dòng mới hay không trước khi bạn xóa nó.
wvducky

Thật không may, không hoạt động trên Mac. Tôi nghi ngờ nó không hoạt động trên bất kỳ biến thể BSD nào.
Edward Falk

16
head -n -1 abc > newfile
tail -n 1 abc | tr -d '\n' >> newfile

Chỉnh sửa 2:

Đây là một awkphiên bản (đã sửa) không tích lũy một mảng rất lớn:

dòng in awk '{if (line); dòng = $ 0} END {printf $ 0} 'abc


Cách tốt để suy nghĩ về nó. Cảm ơn Dennis.
Todd Partridge 'Gen2ly'

Bạn nói đúng. Tôi trì hoãn awkphiên bản của bạn . Phải mất hai lần bù (và một bài kiểm tra khác nhau) và tôi chỉ sử dụng một lần. Tuy nhiên, bạn có thể sử dụng printfthay vì ORS.
Tạm dừng cho đến khi có thông báo mới.

bạn có thể biến đầu ra thành một đường ống với quá trình thay thế:head -n -1 abc | cat <(tail -n 1 abc | tr -d '\n') | ...
BCoates

2
Sử dụng -c thay vì -n cho đầu và đuôi sẽ nhanh hơn nữa.
rudimeier

1
Đối với tôi, head -n -1 abc đã xóa dòng thực tế cuối cùng của tệp, để lại một dòng mới; head -c -1 abc dường như hoạt động tốt hơn
ChrisV

10

chim ưng

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

Vẫn giống như rất nhiều nhân vật đối với tôi ... học từ từ :). Có công việc mặc dù. Cảm ơn ma.
Todd Partridge 'Gen2ly'

1
awk '{ prev_line = line; line = $0; } NR > 1 { print prev_line; } END { ORS = ""; print line; }' fileĐiều này sẽ dễ đọc hơn.
Yevhen Pavliuk

Làm thế nào về : awk 'NR>1 {print p} {p=$0} END {printf $0}' file.
Isaac

@sorontar Đối số đầu tiên printflà đối số định dạng . Do đó, nếu tệp đầu vào có một cái gì đó có thể được hiểu là một công cụ xác định định dạng như thế nào %d, bạn sẽ gặp lỗi. Một sửa chữa sẽ là thay đổi nó thànhprintf "%s" $0
Robin A. Meade

9

Một phương pháp rất đơn giản cho các tệp một dòng, yêu cầu tiếng vang GNU từ coreutils:

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

Đây là một cách tốt nếu nó không quá đắt (lặp đi lặp lại).

Điều này có vấn đề khi \ncó mặt. Khi nó được chuyển đổi sang một dòng mới.
Chris Stryczynski

Dường như cũng hoạt động đối với các tệp đa dòng, nó $(...)được trích dẫn
Thor

chắc chắn cần phải trích dẫn điều đó ... /bin/echo -n "$(cat infile)" Ngoài ra, tôi không chắc chắn mức tối đa của len echohoặc vỏ sẽ là gì trên các phiên bản os / shell / phiên bản (tôi chỉ làm việc này và đó là một lỗ thỏ), vì vậy tôi không chắc chắn mức độ di động (hoặc hiệu năng) thực sự sẽ như thế nào đối với mọi thứ ngoài các tệp nhỏ - nhưng đối với các tệp nhỏ, thật tuyệt.
michael

8

Nếu bạn muốn làm đúng, bạn cần một cái gì đó như thế này:

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;
}

Chúng tôi mở tập tin để đọc và nối thêm; mở để nối thêm có nghĩa là chúng tôi đã seeksửa đến cuối tập tin. Sau đó chúng tôi nhận được vị trí số của phần cuối của tệp với tell. Chúng tôi sử dụng số đó để tìm lại một ký tự, và sau đó chúng tôi đọc một ký tự đó. Nếu đó là một dòng mới, chúng tôi cắt ngắn tệp cho ký tự trước dòng mới đó, nếu không, chúng tôi không làm gì cả.

Điều này chạy trong thời gian liên tục và không gian liên tục cho bất kỳ đầu vào nào, và cũng không yêu cầu thêm không gian đĩa.


2
nhưng điều đó có nhược điểm là không thiết lập lại quyền sở hữu / quyền cho tệp ... err, chờ đợi ...
ysth

1
Verbose, nhưng cả nhanh và mạnh - dường như là câu trả lời chỉnh sửa tệp tại chỗ thực sự duy nhất ở đây (và vì nó có thể không rõ ràng với mọi người: đây là tập lệnh Perl ).
mkuity0

6

Đây là một giải pháp Python đẹp, gọn gàng. Tôi đã không cố gắng để được terse ở đây.

Điều này sửa đổi tệp tại chỗ, thay vì tạo một bản sao của tệp và tước dòng mới từ dòng cuối cùng của bản sao. Nếu tệp lớn, điều này sẽ nhanh hơn nhiều so với giải pháp Perl được chọn là câu trả lời tốt nhất.

Nó cắt một tệp bằng hai byte nếu hai byte cuối là CR / LF hoặc một byte nếu byte cuối cùng là LF. Nó không cố gắng sửa đổi tệp nếu (các) byte cuối không phải là (CR) LF. Nó xử lý lỗi. Đã thử nghiệm trong Python 2.6.

Đặt cái này trong một tập tin gọi là "thoát y" và 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 Theo tinh thần "Perl golf", đây là giải pháp Python ngắn nhất của tôi. Nó đưa toàn bộ tập tin từ đầu vào tiêu chuẩn vào bộ nhớ, loại bỏ tất cả các dòng mới ở cuối và ghi kết quả vào đầu ra tiêu chuẩn. Không ngắn gọn như Perl; bạn không thể đánh bại Perl vì những thứ nhanh nhẹn như thế này.

Xóa "\ n" khỏi cuộc gọi đến .rstrip()và nó sẽ xóa toàn bộ khoảng trắng khỏi phần cuối của tệp, bao gồm nhiều dòng trống.

Đặt cái này vào "slurp_and_chomp.py" và sau đó chạy python slurp_and_chomp.py < inputfile > outputfile.

import sys

sys.stdout.write(sys.stdin.read().rstrip("\n"))

os.path.isfile () sẽ cho bạn biết về sự hiện diện của tệp. Sử dụng thử / ngoại trừ có thể bắt được rất nhiều lỗi khác nhau :)
Denis Barmenkov

5

Một giải pháp nhanh là sử dụng tiện ích gnu truncate:

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

Thử nghiệm sẽ là đúng nếu tập tin có một dòng mới.

Việc xóa rất nhanh, thực sự tại chỗ, không cần tệp mới và tìm kiếm cũng đang đọc từ cuối chỉ một byte ( tail -c1).


1
cắt ngắn: toán hạng tập tin bị thiếu
Brian Hannay

2
nó chỉ thiếu tên tệp đuôi trong ví dụ, nghĩa là [ -z $(tail -c1 filename) ] && truncate -s -1 filename(cũng vậy, để trả lời cho nhận xét khác, truncatelệnh không hoạt động với stdin, bắt buộc phải có tên tệp)
michael

4

Một WTDI khác:

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

3
$ perl -e 'địa phương $ /; $ _ = <>; s / \ n $ //; in 'a-text-file.txt

Xem thêm Phù hợp với bất kỳ nhân vật (bao gồm cả dòng mới) trong sed .


1
Điều đó đưa ra tất cả các dòng mới. Tương đương vớitr -d '\n'
Tạm dừng cho đến khi có thông báo mới.

Điều này cũng hoạt động tốt, có lẽ ít báng bổ hơn so với paviums.
Todd Partridge 'Gen2ly'

Sinan, mặc dù Linux và Unix có thể định nghĩa các tệp văn bản để kết thúc bằng một dòng mới, Windows không đặt ra yêu cầu như vậy. Notepad, ví dụ, sẽ chỉ viết các ký tự bạn nhập mà không thêm bất cứ điều gì thêm vào cuối. Trình biên dịch C có thể yêu cầu tệp nguồn kết thúc bằng ngắt dòng, nhưng tệp nguồn C không phải là tệp văn bản "chỉ", do đó chúng có thể có các yêu cầu bổ sung.
Rob Kennedy

trong đó, hầu hết các công cụ khai thác javascript / css sẽ xóa các dòng mới và vẫn tạo ra các tệp văn bản.
ysth

@Rob Kennedy và @ysth: Có một lập luận thú vị về lý do tại sao các tệp đó không thực sự là tệp văn bản và như vậy.
Sinan Ünür

2

Sử dụng 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

Hiệu quả tương tự như câu trả lời được chấp nhận, nhưng được cho là rõ ràng hơn trong khái niệm cho người dùng không phải là Perl. Lưu ý rằng không cần có gdấu ngoặc đơn xung quanh eof: perl -pi -e 's/\n$// if eof' your_file.
mkuity0

2

Giả sử loại tệp Unix và bạn chỉ muốn dòng mới nhất này hoạt động.

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

Nó sẽ không hoạt động trên nhiều dòng mới ...

* Chỉ hoạt động nếu dòng cuối cùng là một dòng trống.


Đây là một sedgiải pháp hoạt động ngay cả đối với dòng cuối cùng không trống: stackoverflow.com/a/52047796
wvducky

1

Một câu trả lời khác FTR (và yêu thích của tôi!): Echo / cat điều bạn muốn tước và nắm bắt đầu ra thông qua backticks. Dòng mới cuối cùng sẽ bị tước. Ví dụ:

# 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
Tôi tình cờ tìm thấy combo cat-printf (đang cố gắng để có hành vi ngược lại). Lưu ý rằng điều này sẽ loại bỏ TẤT CẢ các dòng mới, không chỉ cuối cùng.
Technosaurus

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.

Tôi nghĩ rằng điều này sẽ chỉ loại bỏ nó nếu dòng cuối cùng là trống. Nó sẽ không xóa dòng mới nếu dòng cuối cùng không trống. Ví dụ, echo -en 'a\nb\n' | sed '${/^$/d}'sẽ không loại bỏ bất cứ điều gì. echo -en 'a\nb\n\n' | sed '${/^$/d}'sẽ loại bỏ vì toàn bộ dòng cuối cùng là trống.
wvducky

1

Đây là một giải pháp tốt nếu bạn cần nó để làm việc với các đường ống / chuyển hướng thay vì đọc / xuất từ ​​hoặc vào một tệp. Điều này hoạt động với một hoặc nhiều dòng. Nó hoạt động cho dù có một dòng mới hay không.

# 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

Chi tiết:

  • head -c -1cắt ngắn ký tự cuối cùng của chuỗi, bất kể ký tự đó là gì. Vì vậy, nếu chuỗi không kết thúc bằng một dòng mới, thì bạn sẽ mất một ký tự.
  • Vì vậy, để giải quyết vấn đề đó, chúng tôi thêm một lệnh khác sẽ thêm một dòng mới nếu không có : sed '$s/$//'. Nghĩa đầu tiên $chỉ áp dụng lệnh cho dòng cuối cùng. s/$//có nghĩa là thay thế "cuối dòng" bằng "không có gì", về cơ bản là không làm gì cả. Nhưng nó có một tác dụng phụ của việc thêm một dòng mới là không có.

Lưu ý: Mặc định của Mac headkhông hỗ trợ -ctùy chọn. Bạn có thể làm brew install coreutilsvà sử dụng gheadthay thế.


0

Lần duy nhất tôi muốn làm điều này là cho mã golf, và sau đó tôi chỉ sao chép mã của mình ra khỏi tệp và dán nó vào một echo -n 'content'>filetuyên bố.


Nửa đường; hoàn thành cách tiếp cận ở đây .
mkuity0


0

Tôi gặp vấn đề tương tự, nhưng đang làm việc với tệp windows và cần giữ CRLF đó - giải pháp của tôi trên 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ên xóa mọi sự xuất hiện cuối cùng của \ n trong tệp. Không hoạt động trên tệp lớn (do giới hạn bộ đệm sed)


0

hồng ngọc:

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

hoặc là:

ruby -ane 'q=p;p=$_;puts q if $.>1;END{print p.strip!}'
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.