Làm thế nào tôi có thể xóa một dòng mới trong bash?


10

Tôi đang tìm kiếm thứ gì đó giống như của Perl chomp. Tôi đang tìm kiếm một lệnh chỉ đơn giản là in đầu vào của nó, trừ ký tự cuối cùng nếu đó là một dòng mới:

$ printf "one\ntwo\n" | COMMAND_IM_LOOKING_FOR ; echo " done"
one
two done
$ printf "one\ntwo" | COMMAND_IM_LOOKING_FOR ; echo " done"
one
two done

(Thay thế lệnh trong Bash và Zsh xóa tất cả các dòng mới, nhưng tôi đang tìm kiếm một cái gì đó xóa nhiều nhất một dòng mới.)

Câu trả lời:


9

Điều này sẽ làm việc:

printf "one\ntwo\n" | awk 'NR>1{print PREV} {PREV=$0} END{printf("%s",$0)}' ; echo " done"

Kịch bản luôn in dòng trước thay vì hiện tại và dòng cuối cùng được xử lý khác nhau.

Những gì nó làm chi tiết hơn:

  1. NR>1{print PREV} In dòng trước (trừ lần đầu tiên).
  2. {PREV=$0}Lưu trữ dòng hiện tại trong PREVbiến.
  3. END{printf("%s",$0)} Cuối cùng, in dòng cuối cùng mà không ngắt dòng.

Cũng lưu ý rằng điều này sẽ loại bỏ tối đa một dòng trống ở cuối (không hỗ trợ để loại bỏ "one\ntwo\n\n\n").


15

Bạn có thể sử dụng perlmà không cần chomp:

$ printf "one\ntwo\n" | perl -0 -pe 's/\n\Z//'; echo " done"
one
two done

$ printf "one\ntwo" | perl -0 -pe 's/\n\Z//'; echo " done"
one
two done

Nhưng tại sao không sử dụng chompchính nó:

$ printf "one\ntwo\n" | perl -pe 'chomp if eof'; echo " done"

4

Nếu bạn muốn một chính xác tương đương chomp, phương pháp đầu tiên xuất hiện trong đầu tôi là giải pháp awk mà LatinSuD đã đăng . Tôi sẽ thêm một số phương pháp khác không triển khai chompnhưng thực hiện một số tác vụ phổ biến chompthường được sử dụng cho.

Khi bạn nhét một số văn bản vào một biến, tất cả các dòng mới ở cuối sẽ bị tước. Vì vậy, tất cả các lệnh này tạo ra cùng một đầu ra dòng đơn:

echo "$(printf 'one\ntwo') done"
echo "$(printf 'one\ntwo\n') done"
echo "$(printf 'one\ntwo\n\n') done"
echo "$(printf 'one\ntwo\n\n\n\n\n\n\n\n\n\n') done"

Nếu bạn muốn nối một số văn bản vào dòng cuối cùng của tệp hoặc đầu ra của lệnh, sedcó thể thuận tiện. Với GNU sed và hầu hết các triển khai hiện đại khác, điều này hoạt động ngay cả khi đầu vào không kết thúc trong một dòng mới¹; tuy nhiên, điều này sẽ không thêm một dòng mới nếu chưa có một dòng nào.

sed '$ s/$/ done/'

¹ Tuy nhiên điều này không làm việc với tất cả các triển khai sed: sed là một công cụ xử lý văn bản, và một tập tin mà không có sản phẩm nào và không kết thúc với một ký tự xuống dòng không phải là một tập tin văn bản.


Điều này không chính xác tương đương chomp, vì chompchỉ xóa tối đa một dòng mới.
Flimm

@Flimm Vâng, chính xác rõ ràng nhất tương đương với chompsẽ là giải pháp awk mà LatinSuD đã đăng. Nhưng trong nhiều trường hợp chompchỉ là một công cụ để thực hiện một công việc và tôi cung cấp các cách để thực hiện một số nhiệm vụ phổ biến. Hãy để tôi cập nhật câu trả lời của tôi để làm rõ điều này.
Gilles 'SO- ngừng trở nên xấu xa'

1

Một perlcách tiếp cận khác . Cái này đọc toàn bộ dữ liệu đầu vào vào bộ nhớ nên có thể không phải là ý tưởng hay cho số lượng lớn dữ liệu (sử dụng cuonglm hoặc awkcách tiếp cận cho điều đó):

$ printf "one\ntwo\n" | perl -0777pe 's/\n$//'; echo " done"
one
two done

Cảm ơn, @ StéphaneChazelas, đã sửa. Vì một số lý do, công tắc này luôn làm tôi bối rối !
terdon

0

Tôi đã lấy cái này từ một repo github ở đâu đó, nhưng không thể tìm thấy ở đâu

xóa dấu vết-trống-dòng-sed

#!/bin/bash
#
# Delete all trailing blank lines.
# From http://sed.sourceforge.net/sed1line.txt
#
# Version: 1.3.0
# Created: 2011-01-02
# Updated: 2015-01-25
# Contact: Joel Parker Henderson (joel@joelparkerhenderson.com)
# License: GPL
##
set -euf
sed -e :a -e '/^\n*$/{$d;N;ba' -e '}'

0

trừu tượng

In các dòng không có dòng mới, chỉ thêm một dòng mới nếu có một dòng khác để in.

$ printf 'one\ntwo\n' | 

     awk '{ printf( "%s%s" , NR>1?"\n":"" , $0 ) }';   echo " done"

one
two done

Các giải pháp khác

Nếu chúng tôi đang làm việc với một tệp, chúng tôi có thể cắt bớt một ký tự từ nó (nếu nó kết thúc trên một dòng mới):

removeTrailNewline () {[[$ (đuôi -c 1 "$ 1")]] || cắt ngắn -s-1 "$ 1"; }

Đó là một giải pháp nhanh vì nó chỉ cần đọc một ký tự từ tệp và sau đó xóa trực tiếp ( truncate) mà không cần đọc toàn bộ tệp.

Tuy nhiên, trong khi làm việc với dữ liệu từ stdin (một luồng), dữ liệu phải được đọc, tất cả dữ liệu đó. Và, nó được "tiêu thụ" ngay khi đọc. Không có backtrack (như với cắt ngắn). Để tìm điểm cuối của luồng chúng ta cần đọc đến cuối luồng. Tại thời điểm đó, không có cách nào để quay trở lại luồng đầu vào, dữ liệu đã được "tiêu thụ". Điều này có nghĩa là dữ liệu phải được lưu trữ trong một số dạng bộ đệm cho đến khi chúng ta khớp với cuối luồng và sau đó làm một cái gì đó với dữ liệu trong bộ đệm.

Rõ ràng nhất của các giải pháp là chuyển đổi luồng thành một tệp và xử lý tệp đó. Nhưng câu hỏi yêu cầu một số loại bộ lọc của luồng. Không phải về việc sử dụng các tập tin bổ sung.

Biến đổi

Giải pháp ngây thơ sẽ là bắt toàn bộ đầu vào thành một biến:

FilterOne(){ filecontents=$(cat; echo "x");        # capture the whole input
             filecontents=${filecontents%x};       # Remove the "x" added above.
             nl=$'\n';                             # use a variable for newline.
             printf '%s' "${filecontents%"$nl"}";  # Remove newline (if it exists).
       }

printf 'one\ntwo'     | FilterOne ; echo 1done
printf 'one\ntwo\n'   | FilterOne ; echo 2done
printf 'one\ntwo\n\n' | FilterOne ; echo 3done

ký ức

Có thể tải toàn bộ tập tin trong bộ nhớ bằng sed. Trong sed không thể tránh được dòng mới trên dòng cuối cùng. GNU sed có thể tránh in một dòng mới, nhưng chỉ khi tệp nguồn đã bị thiếu. Vì vậy, không, sed đơn giản không thể giúp đỡ.

Ngoại trừ trên GNU awk với -ztùy chọn:

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

Với awk (bất kỳ awk), hãy nhét toàn bộ luồng và printfnó không có dòng mới.

awk '    { content = content $0 RS } 
     END { gsub( "\n$", "", content ); printf( "%s", content ) }
    '

Tải toàn bộ tập tin vào bộ nhớ có thể không phải là một ý tưởng hay, nó có thể tiêu tốn rất nhiều bộ nhớ.

Hai dòng trong bộ nhớ

Trong awk, chúng ta có thể xử lý hai dòng trên mỗi vòng lặp bằng cách lưu trữ dòng trước đó trong một biến và in dòng hiện tại:

awk 'NR>1{print previous} {previous=$0} END {printf("%s",$0)}'

Xử lý trực tiếp

Nhưng chúng ta có thể làm tốt hơn.

Nếu chúng tôi in dòng hiện tại mà không có dòng mới và chỉ in dòng mới khi dòng tiếp theo tồn tại, chúng tôi sẽ xử lý một dòng tại một dòng và dòng cuối cùng sẽ không có dòng mới:

awk 'NR == 1 {printf ("% s", $ 0); tiếp theo}; {printf ("\ n% s", $ 0)} '

Hoặc, được viết theo một cách khác:

awk 'NR>1{ print "" }; { printf( "%s", $0 ) }'

Hoặc là:

awk '{ printf( "%s%s" , NR>1?"\n":"" , $0 ) }'

Vì thế:

$ printf 'one\ntwo\n' | awk '{ printf( "%s%s" , NR>1?"\n":"" , $0 ) }'; echo " done"
one
two done
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.