Как я могу повторить содержимое файла n раз?

19

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

Вместо того, чтобы просто повторять тесты, я хотел бы дублировать входные данные несколько раз (например, 1000), чтобы 3-строчный файл превратился в 3000 строк, и я могу выполнить гораздо более эффективный тест.

Я передаю входные данные через имя файла:

mycommand input-data.txt
Оли
источник

Ответы:

21

Вам не нужно input-duplicated.txt.

Пытаться:

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

объяснение

  • 0777: -0sets устанавливает разделитель входной записи (специальная переменная perl, $/которая по умолчанию является новой строкой). Установка этого значения больше, чем 0400заставит Perl выплескивать весь входной файл в память.
  • pe: -pозначает «печатать каждую строку ввода после применения данного ей сценария -e».
  • $_=$_ x 1000: $_текущая строка ввода. Поскольку мы читаем весь файл одновременно -0700, это означает, что весь файл. В x 1000результате будет напечатано 1000 копий всего файла.
cuonglm
источник
Ницца. Это глупо-быстро. 0,785 с на 1000 xargs, 0,006 с для этого, так что да, вероятно, преодолевает проблемы с накладными расходами, которые я видел с другими циклами.
Оли
И увеличение этого значения в 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)  #*

Третий метод импровизирован из комментария Мару ниже и создает большой список имен входных файлов для cat. 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 раз. Разве не было бы более эффективно запустить cat один раз и выдать ей один аргумент N раз? Нечто подобное cat $(for i in {1..N}; do echo filename; done). Это имеет ограничение размера arg, но должно быть быстрее.
Муру
@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
terdon
источник
1
Справедливости ради, вы, вероятно, могли бы упростить это до awk '{for(i=0; i<1000; i++)print}' input-data.txtтакого, чтобы он просто выдавал 1000 копий каждой строки за раз. Не подходит для всех случаев, но даже быстрее, с меньшей задержкой и не требует хранения всего файла в оперативной памяти.
Оли
@ Действительно, я предполагал, что ты хотел сохранить порядок строк, так что это 123123123было хорошо, но 111222333это не так. Ваша версия явно быстрее, чем у Gnouc, в среднем она составляет 0,00297 секунды. РЕДАКТИРОВАТЬ: поцарапать это, я сделал ошибку, это на самом деле эквивалентно в 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).

evilsoup
источник
Да, но это все руководство, которое делает его на несколько порядков медленнее и сложнее, чем другие решения.
тердон
4

Вот простая однострочная, без сценариев:

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

объяснение

  • `yes input-data.txt | head -1000 | paste -s`выдает текст input-data.txt1000 раз, разделенный пробелом
  • Текст затем передается catв виде списка файлов
roeeb
источник
Это решение не похоже на работу. Вам нужно использовать 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 (ну, cat - стандартная команда).

Основываясь на функции printf внутри bash, мы можем сгенерировать повторяющуюся строку):

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

Затем мы можем отправить такой список из 1000 имен файлов (повторяется) и вызвать cat:

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

И, наконец, мы можем дать вывод команде для выполнения:

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

Или, если команда должна получить вход в стандартный ввод:

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 
SmallChess
источник