Comment puis-je répéter le contenu d'un fichier n fois?

19

J'essaie de comparer pour comparer deux façons différentes de traiter un fichier. J'ai une petite quantité de données d'entrée, mais pour obtenir de bonnes comparaisons, je dois répéter les tests plusieurs fois.

Plutôt que de simplement répéter les tests, je voudrais dupliquer les données d'entrée un certain nombre de fois (par exemple 1000) pour qu'un fichier de 3 lignes devienne 3000 lignes et je puisse exécuter un test beaucoup plus satisfaisant.

Je passe les données d'entrée via un nom de fichier:

mycommand input-data.txt
Oli
la source

Réponses:

21

Tu n'as pas besoin input-duplicated.txt.

Essayer:

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

Explication

  • 0777: -0sets définit le séparateur d'enregistrement en entrée (variable spéciale perl $/qui est une nouvelle ligne par défaut). 0400Si vous définissez cette valeur sur une valeur supérieure à Perl, Perl perdra tout le fichier d'entrée en mémoire.
  • pe: le -pmoyen "imprime chaque ligne d'entrée après avoir appliqué le script qui lui est donné -e".
  • $_=$_ x 1000: $_est la ligne d'entrée actuelle. Étant donné que nous lisons le fichier entier à la fois à cause de -0700, cela signifie le fichier entier. Le x 1000résulteront en 1000 exemplaires du dossier complet en cours d' impression.
cuonglm
la source
Agréable. C'est stupide-rapide. 0.785s pour 1000 xargs, 0.006s pour cela, donc oui, surmonte probablement les problèmes de surcharge que je voyais avec d'autres boucles.
Oli
Et le faire passer à 100 000 fois n'augmente le temps d'exécution que de 0,002 s. C'est assez étonnant.
Oli
@Oli: Avec de petits fichiers, et vous avez suffisamment de mémoire, perlc'est tellement efficace qu'il est conçu pour ça.
cuonglm
11

Je pensais à l'origine que je devrais générer un fichier secondaire mais je pourrais simplement boucler le fichier d'origine dans Bash et utiliser une redirection pour le faire apparaître comme un fichier.

Il existe probablement une douzaine de façons différentes de faire la boucle, mais voici quatre:

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

La troisième méthode est improvisée à partir du commentaire de maru ci-dessous et construit une grande liste de noms de fichiers d'entrée pour cat. xargsdivisera cela en autant d'arguments que le système le permettra. C'est beaucoup plus rapide que n chats séparés.

La awkvoie (inspirée de la réponse de terdon ) est probablement la plus optimisée mais elle duplique chaque ligne à la fois. Cela peut convenir ou non à une application particulière, mais c'est rapide et efficace.


Mais cela génère à la volée. La sortie Bash est probablement beaucoup plus lente que ce que quelque chose peut lire, vous devez donc générer un nouveau fichier pour le test. Heureusement, ce n'est qu'une extension très simple:

(for _ in {1..1000}; do echo input-data.txt; done) | xargs cat > input-duplicated.txt
mycommand input-duplicated.txt
Oli
la source
3
Vos deux commandes ont un chat exécuté N fois. Ne serait-il pas plus efficace d'exécuter cat une fois et de le nourrir un argument N fois? Quelque chose comme cat $(for i in {1..N}; do echo filename; done). Cela a la limitation de la taille d'argument, mais devrait être plus rapide.
muru
@muru Belle idée aussi. Besoin d'un peu de travail mais je vais l'ajouter. L'implémentation actuelle fait 1000 itérations d'un fichier de 7 lignes en ~ 0,020 s. C'est vraiment beaucoup mieux que mes versions, mais pas au niveau Perl de Gnouc.
Oli
6

Voici une awksolution:

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

C'est essentiellement aussi rapide que @ Gnuc's Perl (j'ai couru à la fois 1000 fois et j'ai obtenu le temps moyen):

$ 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
la source
1
En toute honnêteté, vous pourriez probablement simplifier cela awk '{for(i=0; i<1000; i++)print}' input-data.txtpour qu'il émette seulement 1000 copies de chaque ligne à la fois. Ne conviendra pas à toutes les occasions mais encore plus rapide, moins de retard et n'a pas besoin de conserver le fichier entier en RAM.
Oli
@Oli en effet, j'avais supposé que vous vouliez conserver l'ordre des lignes, ce qui 123123123était bien, mais ce 111222333n'était pas le cas. Votre version est nettement plus rapide que celle de Gnouc, elle s'établit en moyenne à 0.00297 secondes. EDIT: grattez ça, j'ai fait une erreur, c'est en fait équivalent à 0.004013 secondes.
terdon
5

Je voudrais simplement utiliser un éditeur de texte.

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)

Si vous devez absolument le faire via la ligne de commande (cela nécessite que vous ayez viminstallé, car il vin'a pas la :normalcommande), vous pouvez utiliser:

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

Ici, -es(ou -e -s) fait fonctionner vim silencieusement, donc il ne devrait pas prendre le contrôle de votre fenêtre de terminal, et l' -u NONEempêche de regarder votre vimrc, ce qui devrait le faire fonctionner un peu plus vite qu'il ne le ferait autrement (peut-être beaucoup plus vite, si vous utilisez beaucoup de plugins vim).

evilsoup
la source
Oui, mais tout cela est manuel, ce qui rend plusieurs ordres de grandeur plus lents et plus complexes que les autres solutions.
terdon
4

Voici une simple ligne, sans script impliqué:

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

Explication

  • `yes input-data.txt | head -1000 | paste -s`produit le texte input-data.txt1000 fois séparé par un espace blanc
  • Le texte est ensuite transmis à catune liste de fichiers
roeeb
la source
Cette solution ne semble pas fonctionner. Avez-vous besoin d'utiliser xargs paste -s? Cela fonctionne, mais ne préserve pas les sauts de ligne dans le fichier d'entrée.
JeremyKun
Assurez-vous que vous utilisez l'apostrophe correcte.
roeeb
2

Tout en travaillant sur un script complètement différent, j'ai appris qu'avec 29 millions de lignes de texte, l'utilisation seek()et l'exploitation des données par octets sont souvent plus rapides que ligne par ligne. La même idée est appliquée dans le script ci-dessous: nous ouvrons le fichier, et au lieu de parcourir en boucle l'ouverture et la fermeture du fichier (ce qui peut ajouter des frais généraux, même si cela n'est pas significatif), nous gardons le fichier ouvert et cherchons au début.

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

Le script lui-même est assez simple à utiliser:

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

Pour un fichier texte de 3 lignes et 1000 itérations, cela se passe très bien, environ 0,1 seconde:

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

Le script lui-même n'est pas le plus élégant, pourrait probablement être raccourci, mais fait le travail. Bien sûr, j'ai ajouté quelques bits supplémentaires ici et là, comme la error_out()fonction, ce qui n'est pas nécessaire - c'est juste une petite touche conviviale.

Sergiy Kolodyazhnyy
la source
1

Nous pouvons résoudre ce problème sans fichier supplémentaire, ni programmes spéciaux, pur Bash (enfin, cat est une commande standard).

Sur la base d'une fonctionnalité de printf dans bash, nous pouvons générer une chaîne répétée):

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

Ensuite, nous pouvons envoyer une telle liste de 1000 noms de fichiers (répétés) et appeler cat:

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

Et enfin, nous pouvons donner la sortie à la commande à exécuter:

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

Ou, si la commande doit recevoir l'entrée dans le stdin:

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

Oui, le double <est nécessaire.


la source
0

Je générerais un nouveau fichier en utilisant Unix pour la boucle:

content=$(cat Alex.pgn); for i in {1..900000}; do echo "$content" >> new_file; done 
SmallChess
la source