Tôi có nhiều tệp văn bản trong một thư mục và tôi muốn xóa dòng cuối cùng của mỗi tệp trong thư mục.
Tôi làm nó như thế nào?
Tôi có nhiều tệp văn bản trong một thư mục và tôi muốn xóa dòng cuối cùng của mỗi tệp trong thư mục.
Tôi làm nó như thế nào?
Câu trả lời:
Bạn có thể sử dụng oneliner đẹp này nếu bạn có GNU sed
.
sed -i '$ d' ./*
Nó sẽ xóa dòng cuối cùng của mỗi tệp không bị ẩn trong thư mục hiện tại. Switch -i
for GNU sed
có nghĩa là hoạt động tại chỗ và ra '$ d'
lệnh sed
xóa dòng cuối cùng ( $
nghĩa d
là xóa lần cuối và nghĩa là xóa).
-i
một GNUism, vì vậy đây là moot, nhưng tôi sẽ mất đi bộ râu cũ nếu tôi không chỉ ra rằng một số phiên bản cũ hơn sed
không cho phép bạn đặt bất kỳ khoảng trắng nào giữa $
và d
(hoặc, nói chung, giữa mẫu và lệnh).
*(.)
để toàn cầu cho các tệp thông thường, tôi không biết về các shell khác.
Các câu trả lời khác đều có vấn đề nếu thư mục chứa thứ gì đó không phải là tệp thông thường hoặc tệp có dấu cách / dòng mới trong tên tệp. Đây là một cái gì đó hoạt động bất kể:
find "$dir" -type f -exec sed -i '$d' '{}' '+'
find "$dir" -type f
: tìm các tập tin trong thư mục $dir
-type f
đó là những tập tin thường xuyên;-exec
thực hiện lệnh trên mỗi tập tin được tìm thấysed -i
: chỉnh sửa các tập tin tại chỗ;'$d'
: xóa ( d
) dòng cuối cùng ( $
).'+'
: Tell find để tiếp tục thêm đối số vào sed
(hiệu quả hơn một chút so với chạy lệnh cho từng tệp riêng biệt, nhờ @zwol).Nếu bạn không muốn đi vào thư mục con, thì bạn có thể thêm đối số -maxdepth 1
vào find
.
find
điều này được viết hiệu quả hơn find $dir -type f -exec sed -i '$d' '{}' '+'
.)
-print0
không có mặt trong lệnh đầy đủ, tại sao lại đưa nó vào phần giải thích?
-depth 0
không hoạt động (findutils 4.4.2), thay vào đó nên -maxdepth 1
.
-exec
.
Sử dụng GNU sed -i '$d'
có nghĩa là đọc toàn bộ tệp và tạo một bản sao của tệp mà không có dòng cuối cùng, trong khi việc cắt bớt tệp tại chỗ sẽ hiệu quả hơn (ít nhất là đối với các tệp lớn).
Với GNU truncate
, bạn có thể làm:
for file in ./*; do
[ -f "$file" ] &&
length=$(tail -n 1 "$file" | wc -c) &&
[ "$length" -gt 0 ] &&
truncate -s "-$length" "$file"
done
Nếu các tệp tương đối nhỏ, điều đó có thể sẽ kém hiệu quả hơn vì nó chạy một số lệnh trên mỗi tệp.
Lưu ý rằng đối với các tệp chứa byte bổ sung sau ký tự dòng mới cuối cùng (sau dòng cuối cùng) hoặc nói cách khác nếu chúng có dòng cuối không được phân tách , thì tùy thuộc vào việc tail
triển khai, tail -n 1
sẽ chỉ trả về các byte bổ sung đó (như GNU tail
), hoặc dòng cuối cùng (được phân cách chính xác) và các byte bổ sung đó.
|wc -c
trong các tail
cuộc gọi? (hoặc a ${#length}
)
${#length}
sẽ không hoạt động vì nó đếm các ký tự, không phải byte và $(...)
sẽ loại bỏ ký tự dòng mới, do đó ${#...}
sẽ bị tắt bởi một ký tự ngay cả khi tất cả các ký tự là một byte.
Một cách tiếp cận di động hơn:
for f in ./*
do
test -f "$f" && ed -s "$f" <<\IN
d
w
q
IN
done
Tôi không nghĩ rằng điều này cần bất kỳ lời giải thích ... ngoại trừ có lẽ đó trong trường hợp này d
cũng giống như $d
từ ed
bởi Selects mặc định dòng cuối cùng.
Điều này sẽ không tìm kiếm đệ quy và sẽ không xử lý các tệp ẩn (còn gọi là dotfiles).
Nếu bạn muốn chỉnh sửa chúng, hãy xem Cách khớp * với các tệp ẩn trong một thư mục
[[]]
để []
sau đó nó sẽ hoàn toàn POSIX tuân thủ. ( [[ ... ]]
là một chủ nghĩa Bashism.)
[[
không phải là bashism )
Một lớp lót tuân thủ POSIX cho tất cả các tệp đệ quy bắt đầu trong thư mục hiện tại, bao gồm các tệp dấu chấm:
find . -type f -exec sh -c 'for f; do printf "\$d\nx\n" | ex "$f"; done' sh {} +
Chỉ dành cho .txt
các tệp, không đệ quy:
find . -path '*/*/*' -prune -o -type f -name '*.txt' -exec sh -c 'for f; do printf "\$d\nx\n" | ex "$f"; done' sh {} +
Cũng thấy: