Làm cách nào để xóa nhiều dòng mới tại EOF?


25

Tôi có các tệp kết thúc bằng một hoặc nhiều dòng mới và chỉ nên kết thúc bằng một dòng mới. Làm thế nào tôi có thể làm điều đó với các công cụ Bash / Unix / GNU?

Ví dụ tập tin xấu:

1\n
\n
2\n
\n
\n
3\n
\n
\n
\n

Ví dụ sửa tệp:

1\n
\n
2\n
\n
\n
3\n

Nói cách khác: Cần có chính xác một dòng mới giữa EOF và ký tự không phải dòng mới cuối cùng của tệp.

Thực hiện tham khảo

Đọc nội dung tập tin, cắt bỏ một dòng mới cho đến khi không còn hai dòng mới ở cuối, viết lại:

#! /bin/python

import sys

with open(sys.argv[1]) as infile:
    lines = infile.read()

while lines.endswith("\n\n"):
    lines = lines[:-1]

with open(sys.argv[2], 'w') as outfile:
    for line in lines:
        outfile.write(line)

Làm rõ: Tất nhiên, đường ống được cho phép, nếu điều đó thanh lịch hơn.

Câu trả lời:


16
awk '/^$/ {nlstack=nlstack "\n";next;} {printf "%s",nlstack; nlstack=""; print;}' file

2
+1: giải pháp của awk là (hầu như) luôn thanh lịch và dễ đọc!
Olivier Dulac

@OlivierDulac Thật vậy. Khi tôi thấy sedlời đề nghị, tôi chỉ nghĩ OMG ...
Hauke ​​Laging

1
điều này không hoạt động trên OSX Mavericks bằng cách sử dụng awk mới nhất có sẵn từ Homebrew. Nó lỗi với awk: illegal statement. brew install mawkvà thay đổi lệnh để mawklàm việc mặc dù.
tjmcewan

@noname Tôi thậm chí không hiểu câu hỏi ...
Hauke ​​Laging 16/10/18

Bất kỳ awk nào mà script không hoạt động là một awk bị hỏng nặng - hãy ngừng sử dụng nó và nhận một awk mới bởi vì nếu nó không thể làm điều này thì ai biết được nó có sự phá vỡ nào khác.
Ed Morton

21

Từ các kịch bản một dòng hữu ích cho sed .

# Delete all trailing blank lines at end of file (only).
sed -e :a -e '/^\n*$/{$d;N;};/\n$/ba' file

4
Cảm ơn, tôi đã sử dụng các cách sau để thực hiện tại chỗ cho nhiều tệp: find . -type f -name '*.js' -exec sed --in-place -e :a -e '/^\n*$/{$d;N;};/\n$/ba' {} \;
jakub.g

@ jakub.g tại chỗ và đệ quy là chính xác những gì tôi cần. cảm ơn bạn.
Butussy Butkus

Để thêm vào nhận xét xuất sắc từ @ jakub.g, bạn có thể gọi lệnh như thế này trên OS X:find . -type f -name '*.js' -exec sed -i '' -e :a -e '/^\n*$/{$d;N;};/\n$/ba' {} \;
davejagoda

18

Vì bạn đã có câu trả lời với các công cụ phù hợp hơn sed và awk; bạn có thể tận dụng thực tế là $(< file)loại bỏ các dòng trống.

a=$(<file); printf '%s\n' "$a" > file

Bản hack giá rẻ đó sẽ không hoạt động để xóa các dòng trống có thể chứa dấu cách hoặc các ký tự không in khác, chỉ để xóa các dòng trống ở cuối. Nó cũng sẽ không hoạt động nếu tệp chứa byte rỗng.

Trong shell khác với bash và zsh, sử dụng $(cat file)thay vì $(<file).


+1 để chỉ ra lỗi trông như thế nào đối với tôi: $ (<file) không thực sự đọc tệp? Tại sao nó loại bỏ các dòng mới? (đúng vậy, tôi vừa thử nghiệm, cảm ơn vì đã chỉ ra!)
Olivier Dulac

2
@OlivierDulac $()loại bỏ các dòng mới. Đó là một quyết định thiết kế. Tôi giả định rằng điều này sẽ làm cho việc tích hợp trong các chuỗi khác dễ dàng hơn: echo "On $(date ...) we will meet."sẽ là điều xấu với dòng mới mà gần như mọi lệnh shell xuất ra ở cuối.
Hauke ​​Laging

@HaukeLaging: điểm tốt, có lẽ đó là nguồn gốc của hành vi đó
Olivier Dulac

Tôi đã thêm một trường hợp đặc biệt để tránh thêm "\ n" vào các tệp trống : [[ $a == '' ]] || printf '%s\n' "$a" >"$file".
davidchambers

Để loại bỏ nhiều dòng mới khỏi phần bắt đầu của một tệp, hãy chèn tac vào quy trình (Tôi sử dụng gnu coreutils trên Mac, vì vậy gtac cho tôi):a=$(gtac file.txt); printf '%s\n' "$a" | gtac > file.txt
r_alex_hall

5

Bạn có thể sử dụng thủ thuật này với cat& printf:

$ printf '%s\n' "`cat file`"

Ví dụ

$ printf '%s\n' "`cat ifile`" > ofile
$ cat -e ofile
1$
$
2$
$
$
3$

Các $ký hiệu kết thúc của một dòng.

Tài liệu tham khảo


4

Câu hỏi này được gắn thẻ với , nhưng không ai đề xuất một edgiải pháp.

Đây là một:

ed -s file <<'ED_END'
a

.
?^..*?+1,.d
w
ED_END

hoặc, tương đương,

printf '%s\n' a '' . '?^..*?+1,.d' w | ed -s file

ed sẽ đặt bạn ở dòng cuối cùng của bộ đệm chỉnh sửa theo mặc định khi khởi động.

Lệnh đầu tiên ( a) thêm một dòng trống vào cuối bộ đệm (dòng trống trong tập lệnh chỉnh sửa là dòng này và dấu chấm ( .) chỉ để quay lại chế độ lệnh).

Lệnh thứ hai ( ?) tìm kiếm dòng trước gần nhất có chứa một cái gì đó (ngay cả các ký tự khoảng trắng), sau đó xóa mọi thứ đến cuối bộ đệm từ dòng tiếp theo trên.

Lệnh thứ ba ( w) ghi tệp trở lại đĩa.

Dòng trống được thêm vào sẽ bảo vệ phần còn lại của tệp khỏi bị xóa trong trường hợp không có bất kỳ dòng trống nào ở cuối tệp gốc.


3

Đây là giải pháp Perl không yêu cầu đọc nhiều dòng vào bộ nhớ cùng một lúc:

my $n = 0;
while (<>) {
    if (/./) {
        print "\n" x $n, $_;
        $n = 0;
    } else {
        $n++;
    }
}

hoặc, như một lớp lót:

perl -ne 'if (/./) { print "\n" x $n, $_; $n = 0 } else { $n++ }'

Điều này đọc tệp một dòng tại một thời điểm và kiểm tra từng dòng để xem nếu có chứa một ký tự không phải dòng mới. Nếu không, nó sẽ tăng một bộ đếm; nếu có, nó sẽ in số dòng mới được chỉ định bởi bộ đếm, theo sau là chính dòng đó, sau đó đặt lại bộ đếm.

Về mặt kỹ thuật, thậm chí đệm một dòng duy nhất trong bộ nhớ là không cần thiết; có thể giải quyết vấn đề này bằng cách sử dụng một lượng bộ nhớ không đổi bằng cách đọc tệp trong các đoạn có độ dài cố định và xử lý ký tự theo từng ký tự bằng máy trạng thái. Tuy nhiên, tôi nghi ngờ rằng nó sẽ phức tạp không cần thiết cho trường hợp sử dụng điển hình.


1

Nếu tệp của bạn đủ nhỏ để nhét vào bộ nhớ, bạn có thể sử dụng tệp này

perl -e 'local($/);$f=<>; $f=~s/\n*$/\n/;print $f;' file

0

Trong python (tôi biết đó không phải là điều bạn muốn, nhưng nó tốt hơn nhiều vì nó được tối ưu hóa và mở đầu cho phiên bản bash) mà không cần viết lại tệp và không đọc tất cả tệp (đó là một điều tốt nếu tệp là rất lớn):

#!/bin/python
import sys
infile = open(sys.argv[1], 'r+')
infile.seek(-1, 2)
while infile.read(1) == '\n':
  infile.seek(-2, 1)
infile.seek(1, 1)
infile.truncate()
infile.close()

Lưu ý rằng nó không hoạt động trên các tệp mà ký tự EOL không phải là '\ n'.


0

Một phiên bản bash, thực hiện thuật toán python, nhưng kém hiệu quả hơn vì nó cần nhiều quy trình:

#!/bin/bash
n=1
while test "$(tail -n $n "$1")" == ""; do
  ((n++))
done
((n--))
truncate -s $(($(stat -c "%s" "$1") - $n)) "$1"

0

Đây là một cách nhanh chóng để gõ, và, nếu bạn biết sed, dễ nhớ:

tac < file | sed '/[^[:blank:]]/,$!d' | tac

Nó sử dụng tập lệnh sed để xóa các dòng trống hàng đầu khỏi các tập lệnh một dòng hữu ích cho sed , được tham chiếu bởi Alexey, ở trên và tac (mèo ngược).

Trong một thử nghiệm nhanh, trên tệp dòng 18 MB, 64.000, cách tiếp cận của Alexey nhanh hơn, (0,036 so với 0,046 giây).

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.