Hợp nhất hiệu quả / sắp xếp / số lượng lớn tệp văn bản


8

Tôi đang cố gắng ngây thơ:

$ cat * | sort -u > /tmp/bla.txt

thất bại với:

-bash: /bin/cat: Argument list too long

Vì vậy, để tránh một giải pháp ngớ ngẩn như (tạo một tệp tạm thời khổng lồ):

$ find . -type f -exec cat {} >> /tmp/unsorted.txt \;
$ cat /tmp/unsorted.txt | sort -u > /tmp/bla.txt

Mặc dù tôi có thể xử lý từng tệp một bằng cách sử dụng (điều này sẽ giảm mức tiêu thụ bộ nhớ và gần hơn với cơ chế truyền phát):

$ cat proc.sh
#!/bin/sh
old=/tmp/old.txt
tmp=/tmp/tmp.txt
cat $old "$1" | sort -u > $tmp
mv $tmp $old

Tiếp theo là:

$ touch /tmp/old.txt
$ find . -type f -exec /tmp/proc.sh {} \;

Có một sự thay thế kiểu unix đơn giản hơn cho: cat * | sort -ukhi số lượng tệp đạt tới MAX_ARG? Nó cảm thấy akward viết một kịch bản shell nhỏ cho một nhiệm vụ chung như vậy.


2
là cần thiết ở tất cả? sortnó tự động cho nhiều đầu vào tập tin .. nhưng sau đó sort -u *sẽ thất bại với Argument list too longnhư tôi cũng giả
Sundeep

Câu trả lời:


8

Với GNU sortvà shell printfđược tích hợp sẵn (tất cả các kiểu tương tự POSIX hiện nay ngoại trừ một số biến thể của pdksh):

printf '%s\0' * | sort -u --files0-from=- > output

Bây giờ, một vấn đề với điều đó là bởi vì hai thành phần của đường ống đó được chạy đồng thời và độc lập, vào thời điểm bên trái mở rộng toàn *cầu, bên phải có thể đã tạo outputtệp có thể gây ra sự cố (có thể không xảy ra -uở đây) như outputlà cả tệp đầu vào và đầu ra, vì vậy bạn có thể muốn đầu ra đi đến thư mục khác ( > ../outputví dụ) hoặc đảm bảo toàn cầu không khớp với tệp đầu ra.

Một cách khác để giải quyết nó trong trường hợp này là viết nó:

printf '%s\0' * | sort -u --files0-from=- -o output

Bằng cách đó, nó sortmở ra outputđể viết và (trong các thử nghiệm của tôi), nó sẽ không làm điều đó trước khi nó nhận được danh sách đầy đủ các tệp (rất lâu sau khi toàn cầu được mở rộng). Nó cũng sẽ tránh bị ghi đè outputnếu không có tệp đầu vào nào có thể đọc được.

Một cách khác để viết nó với zshhoặcbash

sort -u --files0-from=<(printf '%s\0' *) -o output

Đó là sử dụng thay thế quy trình (nơi <(...)được thay thế bằng đường dẫn tệp đề cập đến đầu đọc của ống printfđang ghi). Tính năng này xuất phát từ ksh, nhưng kshnhấn mạnh vào việc mở rộng <(...)một đối số riêng cho lệnh để bạn không thể sử dụng nó với --option=<(...)cú pháp. Nó sẽ làm việc với cú pháp này mặc dù:

sort -u --files0-from <(printf '%s\0' *) -o output

Lưu ý rằng bạn sẽ thấy sự khác biệt so với các cách tiếp cận cung cấp đầu ra catcho các tệp trong trường hợp có các tệp không kết thúc bằng ký tự dòng mới:

$ printf a > a
$ printf b > b
$ printf '%s\0' a b | sort -u --files0-from=-
a
b
$ printf '%s\0' a b | xargs -r0 cat | sort -u
ab

Cũng lưu ý rằng sortsắp xếp bằng thuật toán đối chiếu trong miền địa phương ( strcollate()) và sort -ubáo cáo một trong từng nhóm dòng sắp xếp giống nhau theo thuật toán đó, không phải là các dòng duy nhất ở mức byte. Nếu bạn chỉ quan tâm đến các dòng là duy nhất ở mức byte và không quan tâm nhiều đến thứ tự chúng được sắp xếp, bạn có thể muốn sửa miền địa phương thành C nơi sắp xếp dựa trên các giá trị byte ( memcmp(); điều đó có thể sẽ tăng tốc những điều đáng kể):

printf '%s\0' * | LC_ALL=C sort -u --files0-from=- -o output

Cảm thấy tự nhiên hơn để viết, điều này cũng tạo cơ hội sortđể giảm thiểu tiêu thụ bộ nhớ của nó. Tôi vẫn thấy printf '%s\0' *một chút phức tạp để gõ, mặc dù.
malat

Bạn có thể sử dụng find . -type f -maxdepth 1 -print0thay vì printf '%s\0' *, nhưng tôi không thể khẳng định nó dễ gõ hơn. Và sau này dễ xác định là bí danh hơn, tất nhiên!
Toby Speight

@TobySpeight echocó một -n, tôi sẽ thích một cái gì đó như thế printf -0 %snày có vẻ hơi thấp hơn so với'%s\0'
malat

@Toby -maxdepth-print0là các phần mở rộng GNU (mặc dù được hỗ trợ rộng rãi trong những ngày này). Với các finds khác (mặc dù nếu bạn có GNU sort, bạn cũng có thể tìm thấy GNU), bạn vẫn có thể làm LC_ALL=C find . ! -name . -prune -type f ! -name '.*' -exec printf '%s\0' {} +( LC_ALL=Cđể loại trừ các tệp ẩn có chứa các ký tự không hợp lệ, ngay cả với GNU find), nhưng nói chung hơi quá mức khi bạn nói chung đã printfdựng sẵn.
Stéphane Chazelas

2
@malat, bạn luôn có thể xác định print0chức năng print0() { [ "$#" -eq 0 ] || printf '%s\0' "$@";}và sau đóprint0 * | sort...
Stéphane Chazelas

11

Một sửa chữa đơn giản, hoạt động ít nhất trong Bash, vì printfđược dựng sẵn và giới hạn đối số dòng lệnh không áp dụng cho nó:

printf "%s\0" * | xargs -0 cat | sort -u > /tmp/bla.txt

( echo * | xargscũng sẽ hoạt động, ngoại trừ việc xử lý tên tệp có khoảng trắng, v.v.)


Đây có vẻ như là một câu trả lời tốt hơn câu trả lời được chấp nhận, vì nó không yêu cầu sinh ra một catquy trình riêng cho mỗi tệp.
LarsH

4
@LarsH, find -exec {} +tập hợp nhiều tệp cho mỗi lần thực hiện. Với find -exec \;nó sẽ là một con mèo cho mỗi tập tin.
ilkkachu

Ah, tốt để biết. (Đệm)
LarsH

9
find . -maxdepth 1 -type f ! -name ".*" -exec cat {} + | sort -u -o /path/to/sorted.txt

Điều này sẽ nối tất cả các tệp thông thường không bị ẩn trong thư mục hiện tại và sắp xếp nội dung kết hợp của chúng (trong khi loại bỏ các dòng trùng lặp) vào tệp /path/to/sorted.txt.


Tôi đã cố gắng chỉ sử dụng hai tệp cùng một lúc để tránh tiêu tốn nhiều bộ nhớ (số lượng tệp của tôi khá lớn). Bạn có tin rằng |sẽ hoạt động đúng chuỗi để hạn chế sử dụng bộ nhớ?
malat

2
@malat sortsẽ thực hiện sắp xếp ngoài lõi nếu yêu cầu bộ nhớ yêu cầu. Phía bên trái của đường ống sẽ tiêu thụ rất ít bộ nhớ so sánh.
Kusalananda

1

Hiệu quả là một thuật ngữ tương đối để bạn thực sự phải xác định yếu tố nào bạn muốn giảm thiểu; cpu, bộ nhớ, đĩa, thời gian, v.v ... Để tranh luận, tôi sẽ giả định rằng bạn muốn giảm thiểu việc sử dụng bộ nhớ và sẵn sàng dành nhiều chu kỳ cpu hơn để đạt được điều đó. Các giải pháp như được đưa ra bởi Stéphane Chazelas hoạt động tốt

sort -u --files0-from <(printf '%s\0' *) > ../output

nhưng họ cho rằng các tệp văn bản riêng lẻ có mức độ duy nhất cao để bắt đầu. Nếu họ không, tức là nếu sau

sort -u < sample.txt > sample.srt

sample.srt nhỏ hơn 10% sau đó sample.txt, sau đó bạn sẽ tiết kiệm bộ nhớ đáng kể bằng cách xóa các bản sao trong các tệp trước khi bạn hợp nhất. Bạn cũng sẽ tiết kiệm được nhiều bộ nhớ hơn bằng cách không xâu chuỗi các lệnh, điều đó có nghĩa là kết quả từ các quá trình khác nhau không cần phải có trong bộ nhớ cùng một lúc.

find /somedir -maxdepth 1 type f -exec sort -u -o {} {} \;
sort -u --files0-from <(printf '%s\0' *) > ../output

1
Việc sử dụng bộ nhớ hiếm khi được quan tâm sortkhi sortsử dụng các tệp tạm thời khi việc sử dụng bộ nhớ vượt quá ngưỡng (thường là tương đối nhỏ). base64 /dev/urandom | sort -usẽ lấp đầy đĩa của bạn nhưng không sử dụng nhiều bộ nhớ.
Stéphane Chazelas

Chà, ít nhất đó là trường hợp của hầu hết các sorttriển khai, bao gồm cả bản gốc trong Unix v3 năm 1972, nhưng dường như không phải vậy busybox sort. Có lẽ bởi vì người ta dự định chạy trên các hệ thống nhỏ không có bộ nhớ vĩnh viễn.
Stéphane Chazelas

Lưu ý rằng yes | sort -u(tất cả dữ liệu trùng lặp) không phải sử dụng nhiều hơn một vài byte bộ nhớ. Nhưng với GNU và Solaris sortít nhất, chúng ta thấy nó ghi rất nhiều tệp lớn 2 byte vào /tmp( y\ncứ sau vài megabyte đầu vào) nên cuối cùng nó sẽ lấp đầy đĩa.
Stéphane Chazelas

0

Giống như @ilkkachu, nhưng con mèo (1) là không cần thiết:

printf "%s\0" * | xargs -0 sort -u

Ngoài ra, nếu dữ liệu quá dài, có thể bạn muốn sử dụng tùy chọn sort (1) - vô song = N

Khi N là số lượng CPU mà máy tính của bạn có

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.