Como posso repetir o conteúdo de um arquivo n vezes?


19

Estou tentando fazer benchmark para comparar duas maneiras diferentes de processar um arquivo. Eu tenho uma pequena quantidade de dados de entrada, mas para obter boas comparações, preciso repetir os testes várias vezes.

Em vez de apenas repetir os testes, eu gostaria de duplicar os dados de entrada várias vezes (por exemplo, 1000), para que um arquivo de 3 linhas se torne 3000 linhas e eu possa executar um teste muito mais satisfatório.

Estou passando os dados de entrada através de um nome de arquivo:

mycommand input-data.txt

Respostas:


21

Você não precisa input-duplicated.txt.

Experimentar:

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

Explicação

  • 0777: -0sets define o separador de registro de entrada (variável especial perl, $/que é uma nova linha por padrão). Definir isso para um valor maior que 0400fará com que o Perl armazene todo o arquivo de entrada na memória.
  • pe: o -pmeio "imprime cada linha de entrada após aplicar o script fornecido por -eela".
  • $_=$_ x 1000: $_é a linha de entrada atual. Como estamos lendo o arquivo inteiro de uma só vez -0700, isso significa o arquivo inteiro. O x 1000resultará em 1000 cópias de todo o arquivo a ser impresso.

Agradável. Isso é estúpido, rápido. 0,785s para 1000 xargs, 0,006s para isso, então sim, provavelmente supera os problemas de sobrecarga que eu estava vendo com outros loops.
Oli

E aumentar para 100000 vezes apenas aumenta o tempo de execução em 0,002s. Isso é incrível.
Oli

@Oli: Com arquivos pequenos, e você tem memória suficiente, perlé tão eficiente que foi projetado para isso.
precisa saber é

11

Inicialmente, eu pensava que teria que gerar um arquivo secundário, mas poderia fazer o loop do arquivo original no Bash e usar algum redirecionamento para fazer com que ele apareça como um arquivo.

Provavelmente há uma dúzia de maneiras diferentes de fazer o loop, mas aqui estão quatro:

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

O terceiro método é improvisado a partir do comentário de maru abaixo e cria uma grande lista de nomes de arquivos de entrada para cat. xargsdividirá isso em tantos argumentos quanto o sistema permitir. É muito mais rápido que n gatos separados.

A awkmaneira (inspirada na resposta de Terdon ) é provavelmente a mais otimizada, mas duplica cada linha de cada vez. Isso pode ou não ser adequado a um aplicativo específico, mas é extremamente rápido e eficiente.


Mas isso está gerando rapidamente. É provável que a saída do Bash seja muito mais lenta do que algo possa ler, portanto você deve gerar um novo arquivo para teste. Felizmente, essa é apenas uma extensão muito simples:

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

3
Ambos os seus comandos têm gato executando N vezes. Não seria mais eficiente executar o gato uma vez e alimentá-lo um argumento N vezes? Algo como cat $(for i in {1..N}; do echo filename; done). Isso tem a limitação do tamanho do argumento, mas deve ser mais rápido.
muru 8/09/14

Boa idéia também. Precisava de algum trabalho, mas eu vou adicioná-lo. A implementação atual está executando 1000 iterações de um arquivo de 7 linhas em ~ 0,020s. Isso é realmente muito melhor do que minhas versões, mas não no nível Perl do Gnouc.
Oli

6

Aqui está uma awksolução:

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

É essencialmente tão rápido quanto o Perl do @ Gnuc (corri as 1000 vezes e obtive o tempo médio):

$ 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

11
Para ser justo, você provavelmente poderia simplificar isso para awk '{for(i=0; i<1000; i++)print}' input-data.txtque emita apenas 1000 cópias de cada linha por vez. Não serve para todas as ocasiões, mas ainda mais rápido, com menos atraso e não precisa reter o arquivo inteiro na RAM.
Oli

@ Aliás, eu tinha assumido que você queria manter a ordem das linhas, de modo que tudo 123123123estava bem, mas 111222333não estava. Sua versão é claramente mais rápida que a do Gnouc, e a média é de 0.00297 segundos. EDIT: risque isso, eu cometi um erro, é realmente equivalente a 0,004013 segundos.
terdon 8/09/14

5

Eu usaria apenas um editor de texto.

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)

Se você absolutamente precisar fazer isso pela linha de comando (isso requer que você tenha viminstalado, pois vinão possui o :normalcomando), você pode usar:

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

Aqui, -es(ou -e -s) faz com que o vim opere silenciosamente, para que ele não assuma a janela do terminal e o -u NONEimpeça de olhar para o vimrc, o que deve torná-lo um pouco mais rápido do que o normal (talvez muito mais rápido, se você usar muitos plugins do vim).


Sim, mas isso é tudo manual, o que torna várias ordens de magnitude mais lentas e mais complexas do que as outras soluções.
terdon 8/09/14

4

Aqui está uma linha única, sem scripts envolvidos:

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

Explicação

  • `yes input-data.txt | head -1000 | paste -s`produz o texto input-data.txt1000 vezes separado por espaço em branco
  • O texto é passado para catcomo uma lista de arquivos

Esta solução parece não funcionar. Você precisa usar xargs paste -s? Isso funciona, mas não preserva novas linhas no arquivo de entrada.
precisa saber é o seguinte

Verifique se você está usando o apóstrofo correto.
roeeb 6/06/16

2

Enquanto trabalhava em um script completamente diferente, aprendi que, com 29 milhões de linhas de texto, o uso seek()e a operação de dados bytewise geralmente são mais rápidos do que linha por linha. A mesma idéia é aplicada no script abaixo: abrimos o arquivo e, em vez de repetir a abertura e o fechamento do arquivo (o que pode adicionar sobrecarga, mesmo que não seja significativo), mantemos o arquivo aberto e procuramos o início.

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

O script em si é bastante simples de usar:

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

Para arquivos de texto de 3 linhas e 1000 iterações, tudo fica bem, cerca de 0,1 segundos:

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

O script em si não é muito elegante, provavelmente pode ser reduzido, mas faz o trabalho. Obviamente, adicionei alguns bits extras aqui e ali, como error_out()funções, o que não é necessário - é apenas um pequeno toque fácil de usar.


1

Podemos resolver isso sem um arquivo adicional, nem programas especiais, o Bash puro (bem, o gato é um comando padrão).

Com base em um recurso do printf dentro do bash, podemos gerar uma sequência repetida):

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

Em seguida, podemos enviar essa lista de 1000 nomes de arquivos (repetidos) e chamar cat:

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

E, finalmente, podemos dar a saída ao comando para executar:

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

Ou, se o comando precisar receber a entrada no stdin:

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

Sim, o dobro <é necessário.


0

Eu geraria um novo arquivo usando o Unix for loop:

content=$(cat Alex.pgn); for i in {1..900000}; do echo "$content" >> new_file; done 
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.