Làm thế nào để chạy sed trên hơn 10 triệu tệp trong một thư mục?


16

Tôi có một thư mục có 10144911 tập tin trong đó. Cho đến nay tôi đã thử như sau:

  • for f in ls; do sed -i -e 's/blah/blee/g' $f; done

Đập vỡ vỏ của tôi, lstrong một tilda nhưng tôi không thể tìm ra cách tạo ra nó.

  • ls | xargs -0 sed -i -e 's/blah/blee/g'

Quá nhiều tranh luận cho sed

  • find . -name "*.txt" -exec sed -i -e 's/blah/blee/g' {} \;

Không thể fork thêm nữa không còn bộ nhớ

Bất kỳ ý tưởng khác về cách tạo ra loại lệnh này? Các tập tin không cần phải giao tiếp với nhau. ls | wc -ldường như làm việc (rất chậm) vì vậy nó phải có thể


1
Sẽ nhanh hơn nếu bạn có thể tránh việc gọi sedcho mỗi tệp. Tôi không chắc có cách nào để mở, chỉnh sửa, lưu và đóng một loạt các tệp trong đó không sed; nếu tốc độ là điều cần thiết, bạn có thể muốn sử dụng một chương trình khác, có thể là perl hoặc python.
trực giác

@intuited: sẽ còn nhanh hơn nữa nếu không làm gì với các tập tin ... nghiêm túc chứ? nếu bạn muốn thay đổi một mẫu trong một tập hợp các tệp bạn phải xem xét từng tệp để xem, nếu có mẫu đó. nếu bạn biết trước rằng bạn có thể bỏ qua các tệp 'một số', thì rõ ràng sẽ nhanh hơn để không chạm vào các tệp. và thời gian khởi động sedcó lẽ nhanh hơn khởi chạy pythonhoặc perltốt, ngoại trừ nếu bạn làm mọi thứ trong trình thông dịch đó.
akira

@akira: Bạn đang nói rằng khởi chạy perl hoặc python một lần cho càng nhiều tệp sẽ phù hợp với một dòng lệnh sẽ tốn kém hơn so với khởi chạy sed một lần cho mỗi tệp đó? Tôi sẽ thực sự ngạc nhiên nếu đó là trường hợp. Tôi nghĩ bạn không hiểu rằng đề nghị của tôi là gọi (bắt đầu) chương trình chỉnh sửa một lần (hoặc ít nhất là ít lần hơn - xem câu trả lời của tôi) và mở, sửa đổi và lưu lại từng tệp lần lượt, thay vì gọi chương trình chỉnh sửa riêng cho từng tệp đó.
trực giác

bình luận đầu tiên của bạn không phản ánh những gì bạn thực sự muốn nói: "thay thế sed bằng python / perl" .. chỉ bằng cách thực hiện và tìm kiếm @ dòng lệnh OP đã đưa ra, một độc giả vô tội có thể cho rằng "find. -exec python" là nhanh hơn "find. -exec sed" .. điều đó rõ ràng không phải là trường hợp. trong câu trả lời của riêng bạn, bạn gọi python thường xuyên hơn nhiều so với thực sự cần thiết.
akira

Tôi nghĩ rằng akira giải thích sai đề nghị (trực giác) của bạn. Tôi tin rằng bạn đã đề xuất để bó các tập tin với nhau. Tôi đã thử điều đó với nỗ lực xargs của mình, đã đến lúc thử lại :)
Sandro

Câu trả lời:


19

Hãy thử xem:

find -name '*.txt' -print0 | xargs -0 -I {} -P 0 sed -i -e 's/blah/blee/g' {}

Nó sẽ chỉ cung cấp một tên tệp cho mỗi lần gọi sed. Điều đó sẽ giải quyết vấn đề "quá nhiều tranh luận cho sed". Các -Ptùy chọn nên cho phép nhiều quy trình để được chia hai cùng một lúc. Nếu 0 không hoạt động (đáng lẽ phải chạy càng nhiều càng tốt), hãy thử các số khác (10? 100? Số lõi bạn có?) Để giới hạn số lượng.


3
Có lẽ, sẽ cần phải find . -name \*.txt -print0tránh việc shell mở rộng toàn cầu và cố gắng phân bổ không gian cho 10 triệu đối số cần tìm .
Chris Johnsen

@ChrisJohnsen: Vâng, đúng rồi. Tôi vội vàng đăng câu trả lời của tôi và bỏ lỡ bao gồm cả những phần thiết yếu. Tôi đã chỉnh sửa câu trả lời của mình với những sửa chữa đó. Cảm ơn.
Tạm dừng cho đến khi có thông báo mới.

Thử ngay bây giờ ... bắt chéo ngón tay
Sandro

7

Tôi đã thử nghiệm phương pháp này (và tất cả các phương pháp khác) trên 10 triệu tệp (trống), được đặt tên là "xin chào 00000001" thành "xin chào 10000000" (14 byte mỗi tên).

CẬP NHẬT: Bây giờ tôi đã bao gồm một lõi chạy trên 'find |xargs'phương thức (vẫn không có 'sed'; chỉ có tiếng vang> / dev / null) ..

# Step 1. Build an array for 10 million files
#   * RAM usage approx:  1.5 GiB 
#   * Elapsed Time:  2 min 29 sec 
  names=( hello\ * )

# Step 2. Process the array.
#   * Elapsed Time:  7 min 43 sec
  for (( ix=0, cnt=${#names[@]} ; ix<$cnt; ix++ )) ; do echo "${names[ix]}" >/dev/null ; done  

Dưới đây là một bản tóm tắt về cách các câu trả lời được cung cấp đã chạy khi chạy với dữ liệu thử nghiệm được đề cập ở trên. Những kết quả này chỉ liên quan đến các chi phí cơ bản; tức là 'sed' không được gọi. Quá trình sed gần như chắc chắn sẽ tốn nhiều thời gian nhất, nhưng tôi nghĩ sẽ rất thú vị khi xem các phương pháp trần so sánh như thế nào.

'find |xargs'Phương pháp của Dennis , sử dụng một lõi đơn, mất hơn 4 giờ 21 phút ** so với bash arrayphương pháp đang no sedchạy ... Tuy nhiên, lợi thế đa lõi được cung cấp bởi 'find' sẽ vượt xa sự khác biệt về thời gian được hiển thị khi sed được yêu cầu xử lý tập tin ...

           | Time    | RAM GiB | Per loop action(s). / The command line. / Notes
-----------+---------+---------+----------------------------------------------------- 
Dennis     | 271 min | 1.7 GiB | * echo FILENAME >/dev/null
Williamson   cores: 1x2.66 MHz | $ time find -name 'hello *' -print0 | xargs -0 -I {} echo >/dev/null {}
                               | Note: I'm very surprised at how long this took to run the 10 million file gauntlet
                               |       It started processing almost immediately (because of xargs I suppose),  
                               |       but it runs **significantly slower** than the only other working answer  
                               |       (again, probably because of xargs) , but if the multi-core feature works  
                               |       and I would think that it does, then it could make up the defecit in a 'sed' run.   
           |  76 min | 1.7 GiB | * echo FILENAME >/dev/null
             cores: 4x2.66 MHz | $ time find -name 'hello *' -print0 | xargs -0 -I {} -P 0 echo >/dev/null {}
                               |  
-----------+---------+---------+----------------------------------------------------- 
fred.bear  | 10m 12s | 1.5 GiB | * echo FILENAME >/dev/null
                               | $ time names=( hello\ * ) ; time for (( ix=0, cnt=${#names[@]} ; ix<$cnt; ix++ )) ; do echo "${names[ix]}" >/dev/null ; done
-----------+---------+---------+----------------------------------------------------- 
l0b0       | ?@#!!#  | 1.7 GiB | * echo FILENAME >/dev/null 
                               | $ time  while IFS= read -rd $'\0' path ; do echo "$path" >/dev/null ; done < <( find "$HOME/junkd" -type f -print0 )
                               | Note: It started processing filenames after 7 minutes.. at this point it  
                               |       started lots of disk thrashing.  'find' was using a lot of memory, 
                               |       but in its basic form, there was no obvious advantage... 
                               |       I pulled the plug after 20 minutes.. (my poor disk drive :(
-----------+---------+---------+----------------------------------------------------- 
intuited   | ?@#!!#  |         | * print line (to see when it actually starts processing, but it never got there!)
                               | $ ls -f hello * | xargs python -c '
                               |   import fileinput
                               |   for line in fileinput.input(inplace=True):
                               |       print line ' 
                               | Note: It failed at 11 min and approx 0.9 Gib
                               |       ERROR message: bash: /bin/ls: Argument list too long  
-----------+---------+---------+----------------------------------------------------- 
Reuben L.  | ?@#!!#  |         | * One var assignment per file
                               | $ ls | while read file; do x="$file" ; done 
                               | Note: It bombed out after 6min 44sec and approx 0.8 GiB
                               |       ERROR message: ls: memory exhausted
-----------+---------+---------+----------------------------------------------------- 

2

Một cơ hội khác cho việc tìm kiếm hoàn toàn an toàn :

while IFS= read -rd $'\0' path
do
    file_path="$(readlink -fn -- "$path"; echo x)"
    file_path="${file_path%x}"
    sed -i -e 's/blah/blee/g' -- "$file_path"
done < <( find "$absolute_dir_path" -type f -print0 )

1

Điều này chủ yếu là ngoài chủ đề, nhưng bạn có thể sử dụng

find -maxdepth 1 -type f -name '*.txt' | xargs python -c '
import fileinput
for line in fileinput.input(inplace=True):
    print line.replace("blah", "blee"),
'

Lợi ích chính ở đây (hơn ... xargs ... -I {} ... sed ...) là tốc độ: bạn tránh gọi sed10 triệu lần. Sẽ nhanh hơn nếu bạn có thể tránh sử dụng Python (vì python là loại chậm, tương đối), vì vậy perl có thể là lựa chọn tốt hơn cho nhiệm vụ này. Tôi không chắc làm thế nào để làm tương đương thuận tiện với perl.

Cách thức hoạt động này là xargssẽ gọi Python với nhiều đối số nhất có thể phù hợp với một dòng lệnh duy nhất và tiếp tục làm điều đó cho đến khi hết đối số (được cung cấp bởi ls -f *.txt). Số lượng đối số cho mỗi lần gọi sẽ phụ thuộc vào độ dài của tên tệp và, ừm, một số nội dung khác. Các fileinput.inputchức năng mang lại dòng liên tiếp từ các tập tin có tên trong lập luận của mỗi invocation, và các inplacetùy chọn cho nó để kỳ diệu "bắt" đầu ra và sử dụng nó để thay thế mỗi dòng.

Lưu ý rằng replacephương thức chuỗi của Python không sử dụng regexps; nếu bạn cần những thứ đó, bạn phải import revà sử dụng print re.sub(line, "blah", "blee"). Chúng là các RegExps tương thích Perl, là loại phiên bản được củng cố mạnh mẽ của những phiên bản bạn có sed -r.

biên tập

Như akira đã đề cập trong các bình luận, phiên bản gốc sử dụng lệnh global ( ls -f *.txt) thay cho findlệnh sẽ không hoạt động vì các khối được xử lý bởi chính shell ( bash). Điều này có nghĩa là trước khi lệnh thậm chí được chạy, 10 triệu tên tệp sẽ được thay thế vào dòng lệnh. Điều này được đảm bảo khá nhiều để vượt quá kích thước tối đa của danh sách đối số của lệnh. Bạn có thể sử dụng xargs --show-limitscho thông tin cụ thể hệ thống về điều này.

Kích thước tối đa của danh sách đối số cũng được tính đến xargs, điều này giới hạn số lượng đối số mà nó truyền cho mỗi lần gọi của python theo giới hạn đó. Vì xargsvẫn sẽ phải gọi python khá nhiều lần, nên sử dụng đề xuất của akira os.path.walkđể lấy danh sách tập tin có thể sẽ giúp bạn tiết kiệm thời gian.


1
điểm sử dụng toán tử toàn cầu là gì (dù sao cũng sẽ thất bại với nhiều tệp đó) ... và sau đó cung cấp các tệp cho python có os.path.walk()?
akira

@akira: nhà điều hành toàn cầu là để tránh cố gắng thay thế nội dung của .... Chắc chắn có nhiều cách khác để làm điều đó (tức là find) nhưng tôi đang cố gắng bám sát nhất có thể vào những gì OP hiểu. Đây cũng là lý do không sử dụng os.path.walk.
trực giác

@akira: Mặc dù vậy, đề xuất tốt có lẽ sẽ nhanh hơn đáng kể.
trực giác

Tôi nghĩ rằng OP sẽ hiểu os.path.walkkhá dễ dàng.
akira

0

Thử:

ls | while read file; do (something to $file); done

2
ls -fsẽ tốt hơn; Bạn có thực sự muốn đợi nó stat()và sắp xếp nhiều tập tin không?
geekizard

ngay bây giờ tôi đang cố gắng: cho f trong * .txt; làm blah; làm xong. Tôi sẽ đánh đòn đó nếu thất bại. Cảm ơn bạn!
Sandro
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.