Як я можу повторити вміст файлу n разів?


19

Я намагаюся порівняти два різних способи обробки файлу. У мене є невелика кількість вхідних даних, але для того, щоб отримати хороші порівняння, мені потрібно повторити тести кілька разів.

Замість того, щоб просто повторювати тести, я хотів би кілька разів повторювати вхідні дані (наприклад, 1000), тому 3-рядковий файл стає 3000 рядків, і я можу виконати набагато більш тестуючу перевірку.

Я передаю вхідні дані через ім'я файлу:

mycommand input-data.txt

Відповіді:


21

Вам не потрібно input-duplicated.txt.

Спробуйте:

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

Пояснення

  • 0777: -0set встановлює роздільник запису вхідних даних (спеціальна змінна perl, $/яка за замовчуванням є новою лінією). Якщо встановити це значення, що перевищує значення, це 0400призведе до того, що Perl перенесе весь вхідний файл в пам'ять.
  • pe: -pозначає "надрукувати кожен рядок введення після застосування сценарію, заданого -eним".
  • $_=$_ x 1000: $_- поточний рядок введення. Оскільки ми читаємо весь файл одразу через -0700це, це означає весь файл. У x 1000результаті буде надруковано 1000 копій всього файлу.

Приємно. Це нерозумно-швидко. 0.785s для 1000 xargs, 0.006s для цього, так так, так, ймовірно, долає накладні проблеми, які я бачив з іншими циклами.
Олі

І наткнувшись на це, що в 100000 разів лише збільшує час виконання на 0,002 секунди. Це досить дивовижно.
Олі

@Oli: З невеликими файлами, і у вас достатньо пам’яті, perlнастільки ефективно, він призначений для цього.
cuonglm

11

Спочатку я думав, що мені доведеться генерувати вторинний файл, але я міг просто зациклювати оригінальний файл у Bash і використати певне перенаправлення, щоб він відобразився як файл.

Напевно, існує десяток різних способів робити цикл, але ось чотири:

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)  #*

Третій метод, імпровізований з коментаря Мару нижче, створює великий список вхідних імен файлів для кішки. xargsрозділить це на стільки аргументів, скільки дозволить система. Це набагато швидше, ніж п. Окремих котів.

awkСпосіб (натхненний відповідь terdon в ), ймовірно, найбільш оптимізований , але він дублює кожен рядок в той час. Це може не відповідати конкретній програмі, але це блискавично швидко та ефективно.


Але це генерується на льоту. Виведення Bash, ймовірно, буде набагато повільніше, ніж щось можна прочитати, тому вам слід створити новий файл для тестування. На щастя, це лише дуже просте розширення:

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

3
В обох ваших командах кішка працює N разів. Хіба не було б більш ефективно запустити кота один раз і нагодувати його одним аргументом N разів? Щось подібне cat $(for i in {1..N}; do echo filename; done). Це має обмеження розміру аргументу, але має бути швидшим.
муру

@muru Приємна ідея теж. Потрібна була якась робота, але я додам її. Поточна реалізація робить 1000 ітерацій 7-рядкового файлу за ~ 0,020 секунд. Це дійсно набагато краще, ніж мої версії, але не на рівні Perl Gnouc.
Олі

6

Ось таке awkрішення:

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

Це по суті так само швидко, як Perl @ Gnuc (я бігав і 1000 разів, і отримав середній час):

$ 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
Справедливо кажучи, ви могли, ймовірно, спростити це вниз, щоб awk '{for(i=0; i<1000; i++)print}' input-data.txtвоно просто видавало 1000 копій кожного рядка одночасно. Не підходить для всіх випадків, але навіть швидше, менше затримок і не потрібно зберігати весь файл в оперативній пам’яті.
Олі

@Oli дійсно, я припускав, що ви хочете зберегти порядок лінії, щоб це 123123123було добре, але 111222333не було. Ваша версія явно швидша, ніж версія Gnouc, вона в середньому становить 0,00297 секунди. EDIT: подряпини це, я помилився, це фактично еквівалентно 0,004013 секунд.
тердон

5

Я б просто скористався текстовим редактором.

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)

Якщо вам абсолютно потрібно зробити це за допомогою командного рядка (для цього потрібно vimвстановити, як viнемає :normalкоманди), ви можете використовувати:

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

Тут -es(або -e -s) змушує vim працювати безшумно, тому він не повинен перебирати вікно вашого терміналу, і -u NONEне дозволяє йому дивитися на ваш vimrc, який повинен змусити його працювати трохи швидше, ніж це було б інакше (можливо, набагато швидше, якщо ви використовуєте багато плагінів vim).


Так, але це все вручну, що робить його на кілька порядків повільнішими і складнішими, ніж інші рішення.
тердон

4

Ось простий однолінійний, без сценаріїв:

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

Пояснення

  • `yes input-data.txt | head -1000 | paste -s`створює текст у input-data.txt1000 разів відокремлений пробілом
  • Потім текст передається у catсписок файлів

Це рішення, схоже, не працює. Чи потрібно використовувати xargs paste -s? Це працює, але не зберігає нові рядки у вхідному файлі.
JeremyKun

Переконайтесь, що ви використовуєте правильний апостроф.
roeeb

2

Працюючи над зовсім іншим сценарієм, я дізнався, що за допомогою 29 мільйонів рядків тексту використання seek()та оперування даними побіжно часто буває швидше, ніж за рядковим принципом. Ця ж ідея застосовується і в нижченаведеному сценарії: ми відкриваємо файл, і замість того, щоб прокручувати через відкриття та закриття файлу (що може додавати накладні витрати, навіть якщо вони не значні), ми тримаємо файл відкритим і прагнемо повернутися до початку.

#!/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()

Сам сценарій у використанні досить простий:

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

Для текстового файлу з 3 рядками та 1000 ітерацій він іде повністю добре, приблизно за 0,1 секунди:

$ /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

Сам сценарій не найелегантніший, напевно, його можна скоротити, але це робить роботу. Звичайно, я додав туди-сюди кілька зайвих бітів, як error_out()функція, яка не потрібна - це лише невеликий зручний для користувачів штрих.


1

Ми можемо вирішити це без додаткового файлу, а також спеціальних програм, чистого Bash (ну, кішка - це стандартна команда).

Виходячи з функції printf всередині bash, ми можемо створити повторний рядок):

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

Тоді ми можемо надіслати такий список з 1000 імен файлів (повторно) та зателефонувати коту:

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

І нарешті, ми можемо дати вихід команді для виконання:

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

Або, якщо команді потрібно отримати вхід в stdin:

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

Так, потрібен подвійний <.


0

Я б створив новий файл за допомогою Unix для циклу:

content=$(cat Alex.pgn); for i in {1..900000}; do echo "$content" >> new_file; done 
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.