Sử dụng các hệ thống kiểm soát phiên bản tôi cảm thấy khó chịu với tiếng ồn khi diff nói No newline at end of file
.
Vì vậy, tôi đã tự hỏi: Làm thế nào để thêm một dòng mới vào cuối một tập tin để loại bỏ những tin nhắn?
Sử dụng các hệ thống kiểm soát phiên bản tôi cảm thấy khó chịu với tiếng ồn khi diff nói No newline at end of file
.
Vì vậy, tôi đã tự hỏi: Làm thế nào để thêm một dòng mới vào cuối một tập tin để loại bỏ những tin nhắn?
Câu trả lời:
Để vệ sinh đệ quy một dự án tôi sử dụng oneliner này:
git ls-files -z | while IFS= read -rd '' f; do tail -c1 < "$f" | read -r _ || echo >> "$f"; done
Giải trình:
git ls-files -z
liệt kê các tập tin trong kho lưu trữ. Nó lấy một mẫu tùy chọn làm tham số bổ sung có thể hữu ích trong một số trường hợp nếu bạn muốn hạn chế thao tác đối với các tệp / thư mục nhất định. Thay vào đó, bạn có thể sử dụng find -print0 ...
hoặc các chương trình tương tự để liệt kê các tệp bị ảnh hưởng - chỉ cần đảm bảo rằng nó phát ra NUL
các mục được phân tách.
while IFS= read -rd '' f; do ... done
Lặp lại thông qua các mục, xử lý tên tệp một cách an toàn bao gồm khoảng trắng và / hoặc dòng mới.
tail -c1 < "$f"
đọc char cuối cùng từ một tập tin.
read -r _
thoát với trạng thái thoát khác nếu thiếu một dòng mới.
|| echo >> "$f"
nối thêm một dòng mới vào tệp nếu trạng thái thoát của lệnh trước đó là khác không.
find -name \*.java | while read f; do tail -n1 $f | read -r _ || echo >> $f; done
git ls-files
mà vẫn sẽ cứu bạn khỏi việc chỉnh sửa các tệp không được theo dõi trong kiểm soát phiên bản.
IFS=
để bỏ đặt dấu phân cách là tốt để duy trì khoảng trắng xung quanh. Các mục bị chấm dứt null chỉ có liên quan nếu bạn có tệp hoặc thư mục có dòng mới trong tên của chúng, có vẻ như rất xa, nhưng là cách chính xác hơn để xử lý trường hợp chung, tôi đồng ý. Cũng như một cảnh báo nhỏ: -d
tùy chọn read
không khả dụng trong POSIX sh.
tail -n1 < "$f"
để tránh các vấn đề với tên tệp bắt đầu -
( tail -n1 -- "$f"
không hoạt động đối với tệp được gọi -
). Bạn có thể muốn làm rõ rằng câu trả lời bây giờ là zsh / bash cụ thể.
sed -i -e '$a\' file
Và thay thế cho OS X sed
:
sed -i '' -e '$a\' file
Điều này thêm \n
vào cuối của tập tin duy nhất nếu nó chưa kết thúc với một dòng mới. Vì vậy, nếu bạn chạy nó hai lần, nó sẽ không thêm một dòng mới:
$ cd "$(mktemp -d)"
$ printf foo > test.txt
$ sed -e '$a\' test.txt > test-with-eol.txt
$ diff test*
1c1
< foo
\ No newline at end of file
---
> foo
$ echo $?
1
$ sed -e '$a\' test-with-eol.txt > test-still-with-one-eol.txt
$ diff test-with-eol.txt test-still-with-one-eol.txt
$ echo $?
0
man sed
: $ Match the last line.
Nhưng có lẽ nó chỉ hoạt động một cách tình cờ. Giải pháp của bạn cũng hoạt động.
$
. Bên trong một biểu thức chính, chẳng hạn như với biểu mẫu /<regex>/
, nó có nghĩa "kết thúc dòng phù hợp" thông thường. Mặt khác, được sử dụng như một địa chỉ, sed cung cấp cho nó ý nghĩa "dòng cuối cùng trong tệp" đặc biệt. Mã này hoạt động vì sed theo mặc định sẽ thêm một dòng mới vào đầu ra của nó nếu nó chưa có ở đó. Mã "$ a \" chỉ nói "khớp với dòng cuối cùng của tệp và không thêm gì vào nó." Nhưng mặc nhiên, sed thêm dòng mới vào mỗi dòng mà nó xử lý (chẳng hạn như $
dòng này ) nếu nó chưa có ở đó.
/regex/
mang lại cho nó một ý nghĩa khác. Các trang web của FreeBSD có nhiều thông tin hơn, tôi nghĩ: freebsd.org/cgi/man.cgi?query=sed
Có một cái nhìn:
$ echo -n foo > foo
$ cat foo
foo$
$ echo "" >> foo
$ cat foo
foo
vì thế echo "" >> noeol-file
nên làm các trick. (Hoặc bạn có nghĩa là yêu cầu xác định các tệp này và sửa chúng?)
chỉnh sửa đã xóa ""
từ echo "" >> foo
(xem bình luận của @ yuyichao) edit2 đã thêm ""
lại ( nhưng xem bình luận của @Keith
Thompson)
""
là không cần thiết (ít nhất là cho bash) và tail -1 | wc -l
có thể được sử dụng để tìm ra các tập tin mà không có một dòng mới vào cuối
""
Không cần thiết cho bash, nhưng tôi đã thấy các echo
triển khai không in gì khi được gọi mà không có đối số (mặc dù không có cái nào tôi có thể tìm thấy bây giờ làm điều này). echo "" >> noeol-file
có lẽ là mạnh mẽ hơn một chút. printf "\n" >> noeol-file
thậm chí còn hơn thế
csh
's echo
là một trong những được biết đến đầu ra gì khi không thông qua bất kỳ cuộc tranh cãi. Nhưng sau đó nếu chúng ta sẽ hỗ trợ vỏ phi Bourne-như thế nào, chúng ta nên làm cho nó echo ''
thay vì echo ""
như echo ""
sẽ ouput ""<newline>
với rc
hoặc es
ví dụ.
tcsh
, không giống như csh
, in một dòng mới khi được gọi mà không có đối số - bất kể cài đặt của $echo_style
.
Một giải pháp khác sử dụng ed
. Giải pháp này chỉ ảnh hưởng đến dòng cuối cùng và chỉ khi \n
bị thiếu:
ed -s file <<< w
Về cơ bản, nó hoạt động mở tệp để chỉnh sửa thông qua một tập lệnh, tập lệnh là w
lệnh đơn , ghi tập tin trở lại đĩa. Nó được dựa trên câu này được tìm thấy trong ed(1)
trang người đàn ông:
GIỚI HẠN (...) Nếu một tệp văn bản (không nhị phân) không bị chấm dứt bởi một ký tự dòng mới, sau đó ed nối thêm một vào việc đọc / viết nó. Trong trường hợp nhị phân tập tin, ed không nối thêm một dòng mới về đọc / viết.
Một cách đơn giản, di động, tuân thủ POSIX để thêm một dòng mới, vắng mặt cuối cùng vào một tệp văn bản:
[ -n "$(tail -c1 file)" ] && echo >> file
Cách tiếp cận này không cần phải đọc toàn bộ tập tin; nó chỉ đơn giản là có thể tìm kiếm EOF và làm việc từ đó.
Cách tiếp cận này cũng không cần tạo các tệp tạm thời phía sau lưng (ví dụ: sed -i), vì vậy các liên kết cứng không bị ảnh hưởng.
echo chỉ nối một dòng mới vào tệp khi kết quả của lệnh thay thế là một chuỗi không trống. Lưu ý rằng điều này chỉ có thể xảy ra nếu tệp không trống và byte cuối cùng không phải là dòng mới.
Nếu byte cuối cùng của tệp là một dòng mới, đuôi sẽ trả về nó, sau đó lệnh thay thế sẽ loại bỏ nó; kết quả là một chuỗi rỗng. Thử nghiệm -n thất bại và echo không chạy.
Nếu tệp trống, kết quả của việc thay thế lệnh cũng là một chuỗi trống và tiếng vang lại không chạy. Điều này là mong muốn, bởi vì một tệp trống không phải là tệp văn bản không hợp lệ, cũng không tương đương với tệp văn bản không trống với một dòng trống.
yash
nếu ký tự cuối cùng trong tệp là ký tự nhiều byte (ví dụ, trong ngôn ngữ UTF-8) hoặc nếu miền địa phương là C và byte cuối cùng trong tệp có tập bit thứ 8. Với các shell khác (trừ zsh), nó sẽ không thêm dòng mới nếu tệp kết thúc bằng byte NUL (nhưng sau đó, điều đó có nghĩa là đầu vào sẽ không phải là văn bản ngay cả sau khi thêm một dòng mới).
Thêm dòng mới bất kể:
echo >> filename
Đây là một cách để kiểm tra xem một dòng mới có tồn tại ở cuối trước khi thêm một dòng hay không, bằng cách sử dụng Python:
f=filename; python -c "import sys; sys.exit(open(\"$f\").read().endswith('\n'))" && echo >> $f
echo ""
dường như trở nên mạnh mẽ hơn echo -n '\n'
. Hoặc bạn có thể sử dụngprintf '\n'
Giải pháp nhanh nhất là:
[ -n "$(tail -c1 file)" ] && printf '\n' >>file
Thực sự rất nhanh.
Trên một tệp kích thước trung bình, seq 99999999 >file
việc này mất vài giây.
Các giải pháp khác mất nhiều thời gian:
[ -n "$(tail -c1 file)" ] && printf '\n' >>file 0.013 sec
vi -ecwq file 2.544 sec
paste file 1<> file 31.943 sec
ed -s file <<< w 1m 4.422 sec
sed -i -e '$a\' file 3m 20.931 sec
Hoạt động trong tro, bash, lksh, mksh, ksh93, attsh và zsh nhưng không yash.
Nếu bạn cần một giải pháp di động để yash (và tất cả các shell khác được liệt kê ở trên), nó có thể phức tạp hơn một chút:
f=file
if [ "$(tail -c1 "$f"; echo x)" != "$(printf '\nx')" ]
then printf '\n' >>"$f"
fi
Cách nhanh nhất để kiểm tra nếu byte cuối cùng của tệp là một dòng mới là chỉ đọc byte cuối cùng đó. Điều đó có thể được thực hiện với tail -c1 file
. Tuy nhiên, cách đơn giản để kiểm tra xem giá trị byte có phải là một dòng mới hay không, tùy thuộc vào lớp vỏ thông thường loại bỏ một dòng mới bên trong mở rộng lệnh không thành công (ví dụ) trong yash, khi ký tự cuối cùng trong tệp là UTF- 8 giá trị.
Cách chính xác, tuân thủ POSIX, tất cả (hợp lý) để tìm nếu byte cuối cùng của tệp là một dòng mới là sử dụng xxd hoặc hexdump:
tail -c1 file | xxd -u -p
tail -c1 file | hexdump -v -e '/1 "%02X"'
Sau đó, so sánh đầu ra ở trên 0A
sẽ cung cấp một bài kiểm tra mạnh mẽ.
Nó rất hữu ích để tránh thêm một dòng mới vào một tập tin trống.
Tất nhiên, tệp sẽ không cung cấp ký tự cuối cùng 0A
của:
f=file
a=$(tail -c1 "$f" | hexdump -v -e '/1 "%02X"')
[ -s "$f" -a "$a" != "0A" ] && echo >> "$f"
Ngắn và ngọt. Điều này mất rất ít thời gian vì nó chỉ đọc byte cuối cùng (tìm đến EOF). Nó không quan trọng nếu tập tin lớn. Sau đó chỉ thêm một byte nếu cần.
Không có tập tin tạm thời cần thiết cũng không được sử dụng. Không có liên kết cứng bị ảnh hưởng.
Nếu thử nghiệm này được chạy hai lần, nó sẽ không thêm một dòng mới.
xxd
cũng không hexdump
. Trong công cụ POSIX, có od -An -tx1
giá trị hex của một byte.
Tốt hơn hết là bạn nên sửa trình soạn thảo của người dùng đã chỉnh sửa tệp lần cuối. Nếu bạn là người cuối cùng đã chỉnh sửa tệp - bạn đang sử dụng trình chỉnh sửa nào, tôi đoán là bạn đồng hành ..?
emacs
không thêm một dòng mới vào cuối tập tin.
(setq require-final-newline 'ask)
trong.emacs
Nếu bạn chỉ muốn nhanh chóng thêm một dòng mới khi xử lý một số đường ống, hãy sử dụng điều này:
outputting_program | { cat ; echo ; }
nó cũng tuân thủ POSIX.
Sau đó, tất nhiên, bạn có thể chuyển hướng nó đến một tập tin.
cat file.csv | tr "\r" "\n" | { cat; echo; } | sed "/^[[:space:]]*$/d" | tail -n +2 | wc -l
Miễn là không có null trong đầu vào:
paste - <>infile >&0
... Sẽ đủ để luôn luôn nối một dòng mới vào phần cuối của một kẻ lưu manh nếu nó chưa có. Và nó chỉ cần đọc tệp đầu vào trong một lần để làm cho đúng.
paste infile 1<> infile
thay thế.
Mặc dù nó không trả lời trực tiếp câu hỏi, nhưng đây là một đoạn script liên quan tôi đã viết để phát hiện các tệp không kết thúc trong dòng mới. Nó rất nhanh.
find . -type f | # sort | # sort file names if you like
/usr/bin/perl -lne '
open FH, "<", $_ or do { print " error: $_"; next };
$pos = sysseek FH, 0, 2; # seek to EOF
if (!defined $pos) { print " error: $_"; next }
if ($pos == 0) { print " empty: $_"; next }
$pos = sysseek FH, -1, 1; # seek to last char
if (!defined $pos) { print " error: $_"; next }
$cnt = sysread FH, $c, 1;
if (!$cnt) { print " error: $_"; next }
if ($c eq "\n") { print " EOL: $_"; next }
else { print "no EOL: $_"; next }
'
Kịch bản perl đọc danh sách các tên tệp (được sắp xếp tùy chọn) từ stdin và với mỗi tệp, nó đọc byte cuối cùng để xác định xem tệp có kết thúc trong một dòng mới hay không. Nó rất nhanh vì nó tránh đọc toàn bộ nội dung của mỗi tệp. Nó xuất ra một dòng cho mỗi tệp mà nó đọc, có tiền tố là "error:" nếu xảy ra một số lỗi, "trống:" nếu tệp trống (không kết thúc bằng dòng mới!), "EOL:" ("kết thúc dòng ") nếu tệp kết thúc bằng dòng mới và" không EOL: "nếu tệp không kết thúc bằng dòng mới.
Lưu ý: tập lệnh không xử lý tên tệp có chứa dòng mới. Nếu bạn đang sử dụng hệ thống GNU hoặc BSD, bạn có thể xử lý tất cả các tên tệp có thể bằng cách thêm -print0 để tìm, -z để sắp xếp và -0 thành perl, như thế này:
find . -type f -print0 | sort -z |
/usr/bin/perl -ln0e '
open FH, "<", $_ or do { print " error: $_"; next };
$pos = sysseek FH, 0, 2; # seek to EOF
if (!defined $pos) { print " error: $_"; next }
if ($pos == 0) { print " empty: $_"; next }
$pos = sysseek FH, -1, 1; # seek to last char
if (!defined $pos) { print " error: $_"; next }
$cnt = sysread FH, $c, 1;
if (!$cnt) { print " error: $_"; next }
if ($c eq "\n") { print " EOL: $_"; next }
else { print "no EOL: $_"; next }
'
Tất nhiên, bạn vẫn phải nghĩ ra cách mã hóa tên tệp với dòng mới trong đầu ra (còn lại là một bài tập cho người đọc).
Đầu ra có thể được lọc, nếu muốn, để nối thêm một dòng mới vào những tệp không có, đơn giản nhất là với
echo >> "$filename"
Thiếu một dòng mới cuối cùng có thể gây ra lỗi trong các tập lệnh vì một số phiên bản shell và các tiện ích khác sẽ không xử lý đúng một dòng mới cuối cùng bị thiếu khi đọc một tệp như vậy.
Theo kinh nghiệm của tôi, việc thiếu một dòng mới cuối cùng là do sử dụng các tiện ích Windows khác nhau để chỉnh sửa các tệp. Tôi chưa bao giờ thấy vim gây ra một dòng mới cuối cùng bị thiếu khi chỉnh sửa một tệp, mặc dù nó sẽ báo cáo về các tệp đó.
Cuối cùng, có các tập lệnh ngắn hơn (nhưng chậm hơn) có thể lặp qua các đầu vào tên tệp của chúng để in các tệp không kết thúc trong dòng mới, chẳng hạn như:
/usr/bin/perl -ne 'print "$ARGV\n" if /.\z/' -- FILE1 FILE2 ...
Các vi
/ vim
/ ex
biên tập viên tự động thêm <EOL>
vào EOF trừ khi tập tin đã có nó.
Vì vậy, hãy thử một trong hai:
vi -ecwq foo.txt
tương đương với:
ex -cwq foo.txt
Kiểm tra:
$ printf foo > foo.txt && wc foo.txt
0 1 3 foo.txt
$ ex -scwq foo.txt && wc foo.txt
1 1 4 foo.txt
Để sửa nhiều tệp, hãy kiểm tra: Cách khắc phục 'Không có dòng mới ở cuối tệp' cho nhiều tệp? tại SO
Tại sao điều này rất quan trọng? Để giữ cho các tập tin của chúng tôi tương thích POSIX .
Để áp dụng câu trả lời được chấp nhận cho tất cả các tệp trong thư mục hiện tại (cộng với thư mục con):
$ find . -type f -exec sed -i -e '$a\' {} \;
Điều này hoạt động trên Linux (Ubuntu). Trên OS X có thể bạn phải sử dụng -i ''
(chưa được kiểm tra).
find .
liệt kê tất cả các tệp, bao gồm các tệp trong .git
. Để loại trừ:find . -type f -not -path './.git/*' -exec sed -i -e '$a\' {} \;
Ít nhất là trong các phiên bản GNU, chỉ cần grep ''
hoặcawk 1
hợp thức hóa đầu vào của nó, thêm một dòng mới cuối cùng nếu chưa có. Họ sao chép tệp trong quá trình, việc này sẽ mất thời gian nếu lớn (nhưng nguồn không nên quá lớn để đọc?) Và cập nhật thời gian sửa đổi trừ khi bạn làm điều gì đó như
mv file old; grep '' <old >file; touch -r old file
(mặc dù điều đó có thể ổn đối với một tệp bạn đang đăng ký vì bạn đã sửa đổi nó) và nó bị mất các liên kết cứng, quyền không thích hợp và ACL, v.v. trừ khi bạn thậm chí còn cẩn thận hơn.
grep '' file 1<> file
, mặc dù điều đó vẫn sẽ đọc và viết các tập tin đầy đủ.
Điều này hoạt động trong AIX ksh:
lastchar=`tail -c 1 *filename*`
if [ `echo "$lastchar" | wc -c` -gt "1" ]
then
echo "/n" >> *filename*
fi
Trong trường hợp của tôi, nếu tệp bị thiếu dòng mới, wc
lệnh sẽ trả về giá trị 2
và chúng tôi viết một dòng mới.
Thêm vào câu trả lời của Patrick Oscarity , nếu bạn chỉ muốn áp dụng nó vào một thư mục cụ thể, bạn cũng có thể sử dụng:
find -type f | while read f; do tail -n1 $f | read -r _ || echo >> $f; done
Chạy này trong thư mục bạn muốn thêm dòng mới vào.
echo $'' >> <FILE_NAME>
sẽ thêm một dòng trống vào cuối tập tin.
echo $'\n\n' >> <FILE_NAME>
sẽ thêm 3 dòng trống vào cuối tập tin.
Nếu tệp của bạn bị chấm dứt với các kết thúc dòng Windows\r\n
và bạn đang ở trong Linux, bạn có thể sử dụng sed
lệnh này . Nó chỉ thêm \r\n
vào dòng cuối cùng nếu nó chưa có ở đó:
sed -i -e '$s/\([^\r]\)$/\1\r\n/'
Giải trình:
-i replace in place
-e script to run
$ matches last line of a file
s substitute
\([^\r]\)$ search the last character in the line which is not a \r
\1\r\n replace it with itself and add \r\n
Nếu dòng cuối cùng đã chứa \r\n
thì regrec tìm kiếm sẽ không khớp, do đó sẽ không có gì xảy ra.
Bạn có thể viết một fix-non-delimited-line
kịch bản như:
#! /bin/zsh -
zmodload zsh/system || exit
ret=0
for file do
if sysopen -rwu0 -- "$file"; then
if sysseek -w end -1; then
read -r x || print -u0
else
syserror -p "Can't seek in $file before the last byte: "
ret=1
fi
else
ret=1
fi
done
exit $ret
Trái với một số giải pháp được đưa ra ở đây, nó
Bạn có thể sử dụng nó như:
that-script *.txt
hoặc là:
git ls-files -z | xargs -0 that-script
POSIXly, bạn có thể làm một cái gì đó có chức năng tương đương với
export LC_ALL=C
ret=0
for file do
[ -s "$file" ] || continue
{
c=$(tail -c 1 | od -An -vtc)
case $c in
(*'\n'*) ;;
(*[![:space:]]*) printf '\n' >&0 || ret=$?;;
(*) ret=1;; # tail likely failed
esac
} 0<> "$file" || ret=$? # record failure to open
done