cat một số lượng lớn các tập tin với nhau theo đúng thứ tự


23

Tôi có khoảng 15.000 tệp được đặt tên file_1.pdb, file_2.pdbv.v. Tôi có thể sắp xếp khoảng vài nghìn trong số này theo thứ tự:

cat file_{1..2000}.pdb >> file_all.pdb

Tuy nhiên, nếu tôi làm điều này trong 15.000 tệp, tôi sẽ gặp lỗi

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

Tôi đã thấy vấn đề này được giải quyết bằng cách thực hiện find . -name xx -exec xxnhưng điều này sẽ không bảo vệ thứ tự các tệp được nối. Làm thế nào tôi có thể đạt được điều này?


3
Tập tin thứ mười có tên là gì? (Hoặc bất kỳ tệp nào có nhiều hơn một chữ số được đặt hàng.)
roaima

Tôi (hiện tại) có 15.000 tệp này trong một thư mục và cat file_{1..15000}.pdbcấu trúc của bạn hoạt động tốt với tôi.
roaima

11
phụ thuộc vào hệ thống giới hạn là gì. getconf ARG_MAXnên nói.
ilkkachu

3
Cân nhắc thay đổi câu hỏi của bạn thành "hàng ngàn" hoặc "một số lượng rất lớn" tệp. Có thể làm cho câu hỏi dễ tìm hơn cho những người khác có vấn đề tương tự.
msouth

Câu trả lời:


49

Sử dụng find, sortxargs:

find . -maxdepth 1 -type f -name 'file_*.pdb' -print0 |
sort -zV |
xargs -0 cat >all.pdb

Các findlệnh tìm thấy tất cả các file liên quan, sau đó in tên đường dẫn của họ ra sortmà không một "phiên bản sắp xếp" để có được chúng theo thứ tự đúng (nếu những con số trong tên tập tin đã zero-điền để cố định chiều rộng chúng ta sẽ không cần thiết -V). xargslấy danh sách các tên đường dẫn được sắp xếp này và chạy cattrên chúng theo lô lớn nhất có thể.

Điều này sẽ hoạt động ngay cả khi tên tệp chứa các ký tự lạ như dòng mới và dấu cách. Chúng tôi sử dụng -print0với findđể cung cấp cho sorttên nul-chấm dứt để sắp xếp, và sortxử lý các sử dụng -z. xargsquá đọc tên kết thúc nul với -0cờ của nó .

Lưu ý rằng tôi đang viết kết quả vào một tệp có tên không khớp với mẫu file_*.pdb.


Giải pháp trên sử dụng một số cờ không chuẩn cho một số tiện ích. Chúng được hỗ trợ bởi việc triển khai GNU của các tiện ích này và ít nhất là bằng cách thực hiện OpenBSD và macOS.

Các cờ không chuẩn được sử dụng là

  • -maxdepth 1, để findchỉ nhập thư mục trên cùng nhưng không có thư mục con. POSIXly, sử dụngfind . ! -name . -prune ...
  • -print0, để tạo findcác tên đường dẫn kết thúc đầu ra (điều này đã được POSIX xem xét nhưng bị từ chối). Người ta có thể sử dụng -exec printf '%s\0' {} +thay thế.
  • -z, để thực sorthiện các hồ sơ chấm dứt nul. Không có tương đương POSIX.
  • -V, để sortsắp xếp, ví dụ như 200sau 3. Không có sự tương đương POSIX, nhưng có thể được thay thế bằng một loại số trên các phần cụ thể của tên tệp nếu tên tệp có tiền tố cố định.
  • -0, để xargsđọc hồ sơ chấm dứt nul. Không có tương đương POSIX. POSIXly, người ta sẽ cần trích dẫn tên tệp theo định dạng được công nhận bởi xargs.

Nếu tên đường dẫn được xử lý tốt và nếu cấu trúc thư mục phẳng (không có thư mục con), thì người ta có thể thực hiện mà không cần các cờ này, ngoại trừ -Vvới sort.


1
Bạn không cần chấm dứt null không chuẩn cho việc này. Những tên tệp này cực kỳ nhàm chán và các công cụ POSIX hoàn toàn có khả năng xử lý sau đó.
Kevin

6
Bạn cũng có thể viết điều này ngắn gọn hơn với đặc điểm kỹ thuật của người hỏi như printf ‘file_%d.pdb\0’ {1..15000} | xargs -0 cat, hoặc thậm chí với quan điểm của Kevin , echo file_{1..15000}.pdb | xargs cat. Các findgiải pháp có đáng kể chi phí hơn vì nó có để tìm kiếm trên hệ thống tập tin cho các tập tin, nhưng nó là hữu ích hơn khi một số các tập tin có thể không tồn tại.
kojiro

4
@Kevin trong khi những gì bạn đang nói là đúng, tốt hơn hết là nên có câu trả lời áp dụng trong những trường hợp chung hơn. Trong số hàng ngàn người tiếp theo có câu hỏi này, có khả năng một số người trong số họ sẽ có khoảng trắng hoặc bất cứ điều gì trong tên tệp của họ.
msouth

1
@chrylis Chuyển hướng không bao giờ là một phần của các đối số của lệnh và nó xargsthay vì catđược chuyển hướng (mỗi lệnh catgọi sẽ sử dụng xargsđầu ra tiêu chuẩn). Nếu chúng ta đã nói xargs -0 sh -c 'cat >all.pdb'thì nó sẽ có ý nghĩa để sử dụng >>thay vì >, nếu đó là những gì bạn đang gợi ý.
Kusalananda

1
Nó trông giống như sort -n -k1.6sẽ hoạt động (đối với bản gốc, file_nnntên tệp hoặc sort -n -k1.5cho những người không có dấu gạch dưới).
Scott

14

Với zsh(nơi mà {1..15000}toán tử đó đến từ):

autoload zargs # best in ~/.zshrc
zargs file_{1..15000}.pdb -- cat > file_all.pdb

Hoặc cho tất cả file_<digits>.pdbcác tệp theo thứ tự số:

zargs file_<->.pdb(n) -- cat > file_all.pdb

(trong đó <x-y>một toán tử toàn cục khớp với các số thập phân x đến y. Không có , nó xcũng không phải ylà số thập phân. Tương đương với extendedglob's [0-9]##hoặc kshglob' +([0-9])(một hoặc nhiều chữ số)).

Với ksh93, sử dụng catlệnh dựng sẵn của nó (để không bị ảnh hưởng bởi giới hạn của lệnh execve()gọi hệ thống do không thực hiện ):

command /opt/ast/bin/cat file_{1..15000}.pdb > file_all.pdb

Với bash/ zsh/ ksh93(mà hỗ trợ zsh's {x..y}và có printfBUILTIN):

printf '%s\n' file_{1..15000}.pdb | xargs cat > file_all.pdb

Trên hệ thống GNU hoặc tương thích, bạn cũng có thể sử dụng seq:

seq -f 'file_%.17g.pdb' 15000 | xargs cat > file_all.pdb

Đối với các xargsgiải pháp dựa trên cơ sở, cần phải có sự quan tâm đặc biệt đối với các tên tệp có khoảng trắng, dấu ngoặc đơn hoặc dấu ngoặc kép hoặc dấu gạch chéo ngược.

Thích cho -It's a trickier filename - 12.pdb, sử dụng:

seq -f "\"./-It's a trickier filename - %.17g.pdb\"" 15000 |
  xargs cat > file_all.pdb

Đây seq -f | xarg cat > là giải pháp thanh lịch và hiệu quả nhất. (IMHO).
Hastur

Kiểm tra tên tập tin phức tạp hơn ... có thể '"./-It'\''s a trickier filename - %.17g.pdb"'?
Hastur

@Hastur, ôi! Vâng, cảm ơn, tôi đã thay đổi nó thành một cú pháp trích dẫn thay thế. Bạn cũng sẽ làm việc như vậy.
Stéphane Chazelas

11

Một vòng lặp for là có thể, và rất đơn giản.

for i in file_{1..15000}.pdb; do cat $i >> file_all.pdb; done

Nhược điểm là bạn gọi catmột địa ngục rất nhiều lần. Nhưng nếu bạn không thể nhớ chính xác làm thế nào để thực hiện công việc findvà chi phí cầu nguyện không quá tệ trong tình huống của bạn, thì điều đó đáng để ghi nhớ.


Tôi thường thêm phần thân echo $i;trong vòng lặp làm "chỉ báo tiến trình"
Rolf

3
seq 1 15000 | awk '{print "file_"$0".dat"}' | xargs cat > file_all.pdb

1
awk có thể làm công việc của seq ở đây và seq có thể làm công việc của awk : seq -f file_%.10g.pdb 15000. Lưu ý rằng đó seqkhông phải là một lệnh tiêu chuẩn.
Stéphane Chazelas

Cảm ơn Stéphane - Tôi nghĩ seq -f là một cách tuyệt vời để làm điều này; sẽ nhớ điều đó
LarryC

2

Tiền đề

Bạn không nên gặp phải lỗi đó chỉ với 15k tệp có định dạng tên cụ thể đó [ 1 , 2 ] .

Nếu bạn đang chạy bản mở rộng đó từ một thư mục khác và bạn phải thêm đường dẫn vào mỗi tệp, kích thước của lệnh của bạn sẽ lớn hơn và tất nhiên điều đó có thể xảy ra.

Giải pháp chạy lệnh từ thư mục đó.

(cd That/Directory ; cat file_{1..2000}.pdb >> file_all.pdb )

Giải pháp tốt nhất Nếu thay vào đó tôi đoán là xấu và bạn chạy nó từ thư mục chứa các tệp ...
IMHO giải pháp tốt nhất là của Stéphane Chazelas :

seq -f 'file_%.17g.pdb' 15000 | xargs cat > file_all.pdb

với printf hoặc seq; đã thử nghiệm trên các tệp 15k chỉ có số của chúng trong bộ nhớ đệm trước, nó thậm chí còn nhanh hơn (hiện tại và ngoại trừ tệp OP từ cùng thư mục chứa các tệp).

Một số từ nữa

Bạn sẽ có thể chuyển đến dòng lệnh shell của bạn lâu hơn.
Dòng lệnh của bạn dài 213914 ký tự và chứa 15003 từ
cat file_{1..15000}.pdb " > file_all.pdb" | wc

... thậm chí thêm 8 byte cho mỗi từ là 333 938 byte (0,3M) thấp hơn nhiều so với 2097142 (2.1M) được báo cáo bởi ARG_MAXkernel 3.13.0 hoặc 2088 232 nhỏ hơn một chút được báo cáo là "Độ dài tối đa của lệnh chúng ta thực sự có thể sử dụng " bởixargs --show-limits

Cung cấp cho nó một cái nhìn trên hệ thống của bạn với đầu ra của

getconf ARG_MAX
xargs --show-limits

Giải pháp hướng dẫn lười biếng

Trong những trường hợp như thế này, tôi thích làm việc với các khối hơn vì thường đưa ra giải pháp hiệu quả về thời gian.
Logic (nếu có) là tôi quá lười để viết 1 ... 1000 1001..2000, v.v ...
Vì vậy, tôi yêu cầu một kịch bản làm điều đó cho tôi.
Chỉ sau khi tôi kiểm tra đầu ra là chính xác, tôi mới chuyển hướng nó sang một tập lệnh.

... Nhưng Lười là một trạng thái của tâm trí .
Vì tôi dị ứng với xargs(tôi thực sự nên sử dụng xargsở đây) và tôi không muốn kiểm tra cách sử dụng nó, tôi đã hoàn thành đúng giờ để phát minh lại bánh xe như trong các ví dụ dưới đây (tl; dr).

Lưu ý rằng vì tên tệp được kiểm soát (không có dấu cách, dòng mới ...), bạn có thể dễ dàng đi với một cái gì đó giống như tập lệnh bên dưới.

tl; dr

Phiên bản 1: vượt qua dưới dạng tham số tùy chọn số tệp 1, cuối cùng, kích thước khối, tệp đầu ra

#!/bin/bash
StartN=${1:-1}          # First file number
EndN=${2:-15000}        # Last file number
BlockN=${3:-100}        # files in a Block 
OutFile=${4:-"all.pdb"} # Output file name

CurrentStart=$StartN 
for i in $(seq $StartN $BlockN $EndN)
do 
  CurrentEnd=$i ;  
    cat $(seq -f file_%.17g.pdb $CurrentStart $CurrentEnd)  >> $OutFile;
  CurrentStart=$(( CurrentEnd + 1 )) 
done
# Here you may need to do a last iteration for the part cut from seq
[[ $EndN -ge $CurrentStart ]] && 
    cat $(seq -f file_%.17g.pdb $CurrentStart $EndN)  >> $OutFile;

Phiên bản 2

Gọi bash cho việc mở rộng (chậm hơn một chút trong các thử nghiệm của tôi ~ 20%).

#!/bin/bash
StartN=${1:-1}          # First file number
EndN=${2:-15000}        # Last file number
BlockN=${3:-100}        # files in a Block 
OutFile=${4:-"all.pdb"} # Output file name

CurrentStart=$StartN 
for i in $(seq $StartN $BlockN $EndN)
do 
  CurrentEnd=$i ;
    echo  cat file_{$CurrentStart..$CurrentEnd}.pdb | /bin/bash  >> $OutFile;
  CurrentStart=$(( CurrentEnd + 1 )) 
done
# Here you may need to do a last iteration for the part cut from seq
[[ $EndN -ge $CurrentStart ]] && 
    echo  cat file_{$CurrentStart..$EndN}.pdb | /bin/bash  >> $OutFile;

Tất nhiên, bạn có thể tiếp tục và loại bỏ hoàn toàn seq [ 3 ] (từ coreutils) và làm việc trực tiếp với các biến trong bash, hoặc sử dụng python hoặc biên dịch chương trình ac để làm điều đó [ 4 ] ...


Lưu ý đó %glà viết tắt của %.6g. Nó sẽ đại diện cho 1.000.000 như 1e + 06 chẳng hạn.
Stéphane Chazelas

Những người thực sự lười biếng sử dụng các công cụ được thiết kế cho nhiệm vụ làm việc xung quanh giới hạn đó của E2BIG như xargs, zsh zargshoặc ksh93's command -x.
Stéphane Chazelas

seqkhông phải là một bash dựng sẵn, nó là một lệnh từ GNU coreutils. seq -f %g 1000000 1000000xuất ra 1e + 06 ngay cả trong phiên bản mới nhất của coreutils.
Stéphane Chazelas

@ StéphaneChazelas Lười là một trạng thái của tâm trí. Thật kỳ lạ để nói nhưng tôi cảm thấy ấm cúng hơn khi tôi có thể nhìn thấy (và kiểm tra trực quan đầu ra của một lệnh nối tiếp) và chỉ sau đó chuyển hướng đến thực thi. Công trình đó cho tôi suy nghĩ ít hơn xarg... nhưng tôi hiểu đó là vấn đề cá nhân và có lẽ chỉ liên quan đến tôi.
Hastur

@ StéphaneChazelas Gotcha, phải ... Đã sửa. Cảm ơn. Tôi chỉ thử nghiệm với các tệp 15k do OP cung cấp, thật tệ.
Hastur

0

Một cách khác để làm điều đó có thể là

(cat file_{1..499}.pdb; cat file_{500..999}.pdb; cat file_{1000..1499}.pdb; cat file_{1500..2000}.pdb) >> file_all.pdb
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.