Làm thế nào tôi có thể lặp lại nội dung của một tập tin n lần?


19

Tôi đang cố gắng so sánh để so sánh hai cách xử lý tệp khác nhau. Tôi có một lượng nhỏ dữ liệu đầu vào nhưng để có được sự so sánh tốt, tôi cần lặp lại các bài kiểm tra một số lần.

Thay vì chỉ lặp lại các bài kiểm tra, tôi muốn sao chép dữ liệu đầu vào một số lần (ví dụ 1000) để tệp 3 dòng trở thành 3000 dòng và tôi có thể chạy thử nghiệm đầy đủ hơn nhiều.

Tôi đang truyền dữ liệu đầu vào qua tên tệp:

mycommand input-data.txt

Câu trả lời:


21

Bạn không cần input-duplicated.txt.

Thử:

mycommand <(perl -0777pe '$_=$_ x 1000' input-data.txt)

Giải trình

  • 0777: -0thiết lập bộ phân tách bản ghi đầu vào (biến đặc biệt perl $/là dòng mới theo mặc định). Đặt giá trị này thành giá trị lớn hơn 0400sẽ khiến Perl nhét toàn bộ tệp đầu vào vào bộ nhớ.
  • pe: -pcó nghĩa là "in từng dòng đầu vào sau khi áp dụng tập lệnh được cung cấp -echo nó".
  • $_=$_ x 1000: $_là dòng đầu vào hiện tại. Vì chúng tôi đang đọc toàn bộ tệp cùng một lúc vì -0700điều này có nghĩa là toàn bộ tệp. Điều x 1000này sẽ dẫn đến 1000 bản sao của toàn bộ tập tin được in.

Đẹp. Điều này thật ngu ngốc - nhanh chóng. 0,785 cho 1000 xargs, 0,006s cho điều này, vì vậy, có lẽ khắc phục được các vấn đề trên cao mà tôi đã thấy với các vòng lặp khác.
Oli

Và va chạm đến 100000 lần chỉ làm tăng thời gian chạy thêm 0,002s. Điều đó thật tuyệt vời.
Oli

@Oli: Với các tệp nhỏ và bạn có đủ bộ nhớ, perlrất hiệu quả, nó được thiết kế cho việc này.
cuonglm

11

Ban đầu tôi đã nghĩ rằng tôi sẽ phải tạo một tệp thứ cấp nhưng tôi chỉ có thể lặp lại tệp gốc trong Bash và sử dụng một số chuyển hướng để làm cho nó xuất hiện dưới dạng một tệp.

Có thể có hàng tá cách thực hiện vòng lặp khác nhau nhưng đây là bốn cách:

mycommand <( seq 1000 | xargs -i -- cat input-data.txt )
mycommand <( for _ in {1..1000}; do cat input-data.txt; done )
mycommand <((for _ in {1..1000}; do echo input-data.txt; done) | xargs cat )
mycommand <(awk '{for(i=0; i<1000; i++)print}' input-data.txt)  #*

Phương pháp thứ ba được ứng biến từ nhận xét của maru bên dưới và xây dựng một danh sách lớn các tên tệp đầu vào cho mèo. xargssẽ chia điều này thành nhiều đối số mà hệ thống sẽ cho phép. Đó là nhiều nhanh hơn n mèo riêng biệt.

Các awkcách (lấy cảm hứng từ câu trả lời của terdon ) có lẽ là tối ưu nhất nhưng nó trùng lặp mỗi dòng một lúc. Điều này có thể hoặc có thể không phù hợp với một ứng dụng cụ thể, nhưng nó nhanh và hiệu quả.


Nhưng điều này đang tạo ra trên bay. Bash xuất ra có thể sẽ chậm hơn rất nhiều so với những gì có thể đọc được vì vậy bạn nên tạo một tệp mới để thử nghiệm. Rất may đó chỉ là một phần mở rộng rất đơn giản:

(for _ in {1..1000}; do echo input-data.txt; done) | xargs cat > input-duplicated.txt
mycommand input-duplicated.txt

3
Cả hai lệnh của bạn có con mèo chạy N lần. Sẽ không hiệu quả hơn khi chạy mèo một lần và cho nó ăn một lần N lần? Một cái gì đó như cat $(for i in {1..N}; do echo filename; done). Điều này có giới hạn về kích thước arg, nhưng nên nhanh hơn.
muru

@muru Ý kiến ​​hay quá. Cần một số công việc nhưng tôi sẽ thêm nó. Việc triển khai hiện tại đang thực hiện 1000 lần lặp của tệp 7 dòng trong ~ 0,020 giây. Điều đó thực sự tốt hơn nhiều so với các phiên bản của tôi, nhưng không phải ở cấp độ Perl của Gnouc.
Oli

6

Đây là một awkgiải pháp:

awk '{a[NR]=$0}END{for (i=0; i<1000; i++){for(k in a){print a[k]}}}' file 

Nó cơ bản nhanh như Perl của @ Gnuc (Tôi đã chạy cả 1000 lần và có thời gian trung bình):

$ for i in {1..1000}; do 
 (time awk '{a[NR]=$0}END{for (i=0;i<1000;i++){for(k in a){print a[k]}}}' file > a) 2>&1 | 
    grep -oP 'real.*?m\K[\d\.]+'; done | awk '{k+=$1}END{print k/1000}'; 
0.00426

$ for i in {1..1000}; do 
  (time perl -0777pe '$_=$_ x 1000' file > a ) 2>&1 | 
    grep -oP 'real.*?m\K[\d\.]+'; done | awk '{k+=$1}END{print k/1000}'; 
0.004076

1
Nói một cách công bằng, có lẽ bạn có thể đơn giản hóa việc này xuống để awk '{for(i=0; i<1000; i++)print}' input-data.txtnó chỉ phát hành 1000 bản sao của mỗi dòng tại một thời điểm. Không phù hợp với tất cả các dịp nhưng thậm chí nhanh hơn, ít trễ hơn và không cần giữ toàn bộ tệp trong RAM.
Oli

@Oli thực sự, tôi đã giả sử bạn muốn giữ trật tự dòng để 123123123ổn nhưng 111222333không được. Phiên bản của bạn rõ ràng nhanh hơn Gnouc, trung bình ở mức 0,00297 giây. EDIT: cào đó, tôi đã phạm sai lầm, nó thực sự tương đương ở 0,004013 giây.
terdon

5

Tôi sẽ chỉ sử dụng một trình soạn thảo văn bản.

vi input-data.txt
gg (move cursor to the beginning of the file)
yG (yank til the end of the file)
G (move the cursor to the last line of the file)
999p (paste the yanked text 999 times)
:wq (save the file and exit)

Nếu bạn thực sự cần phải thực hiện thông qua dòng lệnh (điều này đòi hỏi bạn phải vimcài đặt, vì vikhông có :normallệnh), bạn có thể sử dụng:

vim -es -u NONE "+normal ggyGG999p" +wq input-data.txt

Ở đây, -es(hoặc -e -s) làm cho vim hoạt động âm thầm, vì vậy nó không nên chiếm cửa sổ đầu cuối của bạn và -u NONEngăn nó nhìn vào vimrc của bạn, điều này sẽ khiến nó chạy nhanh hơn một chút so với nếu không (có thể nhanh hơn nhiều, nếu bạn sử dụng rất nhiều plugin vim).


Có, nhưng đây là tất cả thủ công làm cho nó có một số đơn đặt hàng cường độ chậm hơn và phức tạp hơn các giải pháp khác.
terdon

4

Đây là một lót đơn giản, không có kịch bản liên quan:

mycommand <(cat `yes input-data.txt | head -1000 | paste -s`)

Giải trình

  • `yes input-data.txt | head -1000 | paste -s`tạo văn bản input-data.txt1000 lần tách biệt bởi khoảng trắng
  • Văn bản sau đó được chuyển đến catdưới dạng danh sách tập tin

Giải pháp này dường như không hoạt động. Bạn có cần sử dụng xargs paste -s? Điều này hoạt động, nhưng không bảo tồn các dòng mới trong tệp đầu vào.
JeremyKun

Hãy chắc chắn rằng bạn đang sử dụng dấu nháy đơn chính xác.
roeeb

2

Khi làm việc trên một tập lệnh hoàn toàn khác, tôi đã học được rằng với 29 triệu dòng văn bản, việc sử dụng seek()và vận hành trên dữ liệu tạm thời thường nhanh hơn trên cơ sở từng dòng. Ý tưởng tương tự được áp dụng trong kịch bản dưới đây: chúng tôi mở tệp và thay vì lặp qua mở và đóng tệp (có thể thêm chi phí, ngay cả khi không đáng kể), chúng tôi giữ tệp mở và tìm lại từ đầu.

#!/usr/bin/env python3
from __future__ import print_function
import sys,os

def error_out(string):
    sys.stderr.write(string+"\n")
    sys.exit(1)

def read_bytewise(fp):
    data = fp.read(1024)
    print(data.decode(),end="",flush=True)
    while data:
        data = fp.read(1024)
        print(data.decode(),end="",flush=True)
    #fp.seek(0,1)

def main():
    howmany = int(sys.argv[1]) + 1
    if not os.path.isfile(sys.argv[2]):
       error_out("Needs a valid file") 

    fp = open(sys.argv[2],'rb')
    for i in range(1,howmany):
        #print(i)
        fp.seek(0)
        read_bytewise(fp)
    fp.close()

if __name__ == '__main__': main()

Bản thân kịch bản sử dụng khá đơn giản:

./repeat_text.py <INT> <TEXT.txt>

Đối với tệp văn bản 3 dòng và 1000 lần lặp, nó khá ổn, khoảng 0,1 giây:

$ /usr/bin/time ./repeat_text.py 1000 input.txt  > /dev/null                                                             
0.10user 0.00system 0:00.23elapsed 45%CPU (0avgtext+0avgdata 9172maxresident)k
0inputs+0outputs (0major+1033minor)pagefaults 0swaps

Bản thân kịch bản không thanh lịch nhất, có thể rút ngắn, nhưng thực hiện công việc. Tất nhiên, tôi đã thêm một vài bit bổ sung ở đây và ở đó, như error_out()chức năng, không cần thiết - đó chỉ là một liên lạc nhỏ thân thiện với người dùng.


1

Chúng ta có thể giải quyết vấn đề này mà không cần một tệp bổ sung, cũng như các chương trình đặc biệt, thuần Bash (tốt, mèo là một lệnh tiêu chuẩn).

Dựa trên một tính năng của printf bên trong bash, chúng ta có thể tạo một chuỗi lặp lại):

printf "test.file.txt %.0s\n" {1..1000}

Sau đó, chúng tôi có thể gửi danh sách 1000 tên tệp đó (lặp đi lặp lại) và gọi mèo:

printf "test.file.txt %.0s" {1..1000} | xargs cat 

Và cuối cùng, chúng ta có thể cung cấp đầu ra cho lệnh để thực thi:

mycommand "$( printf "%.0sinput.txt\n" {1..1000} | xargs cat )"

Hoặc, nếu lệnh cần nhận đầu vào trong stdin:

mycommand < <( printf "%.0sinput.txt\n" {1..1000} | xargs cat )

Có, gấp đôi <là cần thiết.


0

Tôi sẽ tạo một tệp mới bằng vòng lặp Unix:

content=$(cat Alex.pgn); for i in {1..900000}; do echo "$content" >> new_file; done 
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.