Comment puis-je mélanger les lignes d'un fichier texte sur la ligne de commande Unix ou dans un script shell?


285

Je souhaite mélanger les lignes d'un fichier texte de manière aléatoire et créer un nouveau fichier. Le fichier peut contenir plusieurs milliers de lignes.

Comment puis-je faire ça avec cat , awk, cut, etc?



Oui, il y a aussi d'autres bonnes réponses dans cette question originale.
Ruggiero Spearman

alors, faisiez-vous une liste de mots wpa? (juste une supposition aléatoire)
thahgr

Réponses:


361

Vous pouvez utiliser shuf . Sur certains systèmes au moins (ne semble pas être dans POSIX).

Comme l'a souligné jleedev: sort -Rpourrait également être une option. Sur certains systèmes au moins; eh bien, vous obtenez l'image. Il a été souligné quesort -R ne mélange pas vraiment, mais trie les éléments en fonction de leur valeur de hachage.

[Note de l'éditeur: sort -R presque aléatoire, sauf que les lignes / clés de tri en double finissent toujours côte à côte . En d'autres termes: c'est uniquement avec des lignes / touches d'entrée uniques que c'est un véritable shuffle. S'il est vrai que l'ordre de sortie est déterminé par les valeurs de hachage , le caractère aléatoire vient du choix d'une fonction de hachage aléatoire - voir le manuel .]


31
shufet sort -Rdiffèrent légèrement, car sort -Rordonne au hasard les éléments en fonction de leur hachage , ce sort -Rqui signifie que les éléments répétés seront assemblés, tout en shufmélangeant tous les éléments de manière aléatoire.
SeMeKh

146
Pour les utilisateurs d'OS X brew install coreutilsgshuf ...
:,

15
sort -Ret shufdoit être considéré comme complètement différent. sort -Rest déterministe. Si vous l'appelez deux fois à des moments différents sur la même entrée, vous obtiendrez la même réponse. shuf, d'autre part, produit une sortie aléatoire, il donnera donc très probablement une sortie différente sur la même entrée.
EfForEffort

18
Ce n'est pas correct. "sort -R" utilise une clé de hachage aléatoire différente chaque fois que vous l'appelez, donc elle produit une sortie différente à chaque fois.
Mark Pettit

3
Remarque sur le caractère aléatoire: selon les documents GNU, "Par défaut, ces commandes utilisent un générateur pseudo-aléatoire interne initialisé par une petite quantité d'entropie, mais peuvent être dirigées pour utiliser une source externe avec l'option --random-source = file."
Royce Williams

86

Perl one-liner serait une version simple de la solution de Maxim

perl -MList::Util=shuffle -e 'print shuffle(<STDIN>);' < myfile

6
J'ai alias ceci pour mélanger sous OS X. Merci!
The Unfun Cat

C'était le seul script de cette page qui renvoyait de VRAIES lignes aléatoires. D'autres solutions awk imprimaient souvent une sortie en double.
Felipe Alvarez

1
Mais soyez prudent car à la fin vous
perdrez

@JavaRunner: Je suppose que vous parlez d'entrée sans suivi \n; oui, ce \ndoit être présent - et généralement est - sinon vous obtiendrez ce que vous décrivez.
mklement0

1
Merveilleusement concis. Je suggère de le remplacer <STDIN>par <>, de sorte que la solution fonctionne également avec l'entrée des fichiers .
mklement0

60

Cette réponse complète les nombreuses excellentes réponses existantes des manières suivantes:

  • Les réponses existantes sont regroupées dans des fonctions shell flexibles :

    • Les fonctions prennent non seulement des stdinentrées, mais également des arguments de nom de fichier
    • Les fonctions prennent des mesures supplémentaires à gérer SIGPIPEde la manière habituelle (terminaison silencieuse avec code de sortie 141), par opposition à une interruption bruyante. Ceci est important lors de la canalisation de la sortie de fonction vers une canalisation fermée tôt, comme lors de la canalisation vers head.
  • Une comparaison des performances est effectuée.


  • Fonction compatible POSIX basée sur awk,, sortetcut , adaptée de la réponse du PO :
shuf() { awk 'BEGIN {srand(); OFMT="%.17f"} {print rand(), $0}' "$@" |
               sort -k1,1n | cut -d ' ' -f2-; }
shuf() { perl -MList::Util=shuffle -e 'print shuffle(<>);' "$@"; }
shuf() { python -c '
import sys, random, fileinput; from signal import signal, SIGPIPE, SIG_DFL;    
signal(SIGPIPE, SIG_DFL); lines=[line for line in fileinput.input()];   
random.shuffle(lines); sys.stdout.write("".join(lines))
' "$@"; }

Voir la section inférieure pour une version Windows de cette fonction.

shuf() { ruby -e 'Signal.trap("SIGPIPE", "SYSTEM_DEFAULT");
                     puts ARGF.readlines.shuffle' "$@"; }

Comparaison des performances:

Remarque: Ces chiffres ont été obtenus sur un iMac fin 2012 avec Intel Core i5 à 3,2 GHz et un Fusion Drive, exécutant OSX 10.10.3. Bien que les délais varient en fonction du système d'exploitation utilisé, des spécifications de la machine et de l' awkimplémentation utilisée (par exemple, la awkversion BSD utilisée sur OSX est généralement plus lente que GNU awket surtout mawk), cela devrait fournir un sentiment général de performances relatives. .

Le fichier d' entrée est un fichier de 1 million de lignes produit avec seq -f 'line %.0f' 1000000.
Les temps sont répertoriés par ordre croissant (le plus rapide en premier):

  • shuf
    • 0.090s
  • Ruby 2.0.0
    • 0.289s
  • Perl 5.18.2
    • 0.589s
  • Python
    • 1.342savec Python 2.7.6; 2.407s(!) avec Python 3.4.2
  • awk+ sort+cut
    • 3.003savec BSD awk; 2.388savec GNU awk(4.1.1); 1.811savec mawk(1.3.4);

Pour plus de comparaison, les solutions non packagées comme les fonctions ci-dessus:

  • sort -R (pas un vrai shuffle s'il y a des lignes d'entrée en double)
    • 10.661s - allouer plus de mémoire ne semble pas faire de différence
  • Scala
    • 24.229s
  • bash boucles + sort
    • 32.593s

Conclusions :

  • Utilisez shuf, si vous le pouvez - c'est de loin le plus rapide.
  • Ruby s'en sort bien, suivi de Perl .
  • Python est sensiblement plus lent que Ruby et Perl, et, en comparant les versions de Python, 2.7.6 est un peu plus rapide que 3.4.1
  • Utilisez le combo + awk+ compatible POSIX en dernier recourssortcut ; l' awkimplémentation que vous utilisez mawkest plus rapide que GNU awk, BSDawk est la plus lente).
  • Restez à l'écart de sort -R, bashboucles et Scala.

Versions Windows de la solution Python (le code Python est identique, sauf pour les variations de citation et la suppression des instructions liées au signal, qui ne sont pas prises en charge sur Windows):

  • Pour PowerShell (dans Windows PowerShell, vous devrez ajuster $OutputEncodingsi vous souhaitez envoyer des caractères non ASCII via le pipeline):
# Call as `shuf someFile.txt` or `Get-Content someFile.txt | shuf`
function shuf {
  $Input | python -c @'
import sys, random, fileinput;
lines=[line for line in fileinput.input()];
random.shuffle(lines); sys.stdout.write(''.join(lines))
'@ $args  
}

Notez que PowerShell peut mélanger nativement via sa Get-Randomcmdlet (bien que les performances puissent être un problème); par exemple:
Get-Content someFile.txt | Get-Random -Count ([int]::MaxValue)

  • Pour cmd.exe(un fichier batch):

Enregistrer dans un fichier shuf.cmd, par exemple:

@echo off
python -c "import sys, random, fileinput; lines=[line for line in fileinput.input()]; random.shuffle(lines); sys.stdout.write(''.join(lines))" %*

SIGPIPE n'existe pas sur Windows, j'ai donc utilisé ce simple one-liner à la place:python -c "import sys, random; lines = [x for x in sys.stdin.read().splitlines()] ; random.shuffle(lines); print(\"\n\".join([line for line in lines]));"
elig

@elig: Merci, mais omettre from signal import signal, SIGPIPE, SIG_DFL; signal(SIGPIPE, SIG_DFL);de la solution d'origine est suffisant et conserve la flexibilité de pouvoir également passer des arguments de nom de fichier - pas besoin de changer quoi que ce soit d'autre (sauf pour les citations) - veuillez consulter la nouvelle section que j'ai ajoutée à la bas.
mklement0

27

J'utilise un petit script perl, que j'appelle "unsort":

#!/usr/bin/perl
use List::Util 'shuffle';
@list = <STDIN>;
print shuffle(@list);

J'ai également une version délimitée par NULL, appelée "unsort0" ... pratique pour une utilisation avec find -print0 et ainsi de suite.

PS: A également voté "shuf", je ne savais pas que c'était là dans coreutils ces jours-ci ... ce qui précède peut toujours être utile si vos systèmes n'ont pas "shuf".


gentil, RHEL 5.6 n'a pas de shuf (
Maxim Egorushkin

1
Bien fait; Je suggère de remplacer <STDIN>par <>afin de faire fonctionner la solution avec une entrée à partir de fichiers également.
mklement0

20

Voici un premier essai facile sur le codeur mais difficile sur le processeur qui ajoute un nombre aléatoire à chaque ligne, les trie puis supprime le nombre aléatoire de chaque ligne. En effet, les lignes sont triées aléatoirement:

cat myfile | awk 'BEGIN{srand();}{print rand()"\t"$0}' | sort -k1 -n | cut -f2- > myfile.shuffled

8
UUOC. passer le fichier pour se réveiller.
ghostdog74

1
Bon, je débogue avec head myfile | awk .... Ensuite, je le change juste en chat; c'est pourquoi il a été laissé là.
Ruggiero Spearman du

Pas besoin -k1 -nde tri, car la sortie de awk rand()est une décimale entre 0 et 1 et parce que tout ce qui compte, c'est qu'il soit réorganisé d'une manière ou d'une autre. -k1pourrait aider à l'accélérer en ignorant le reste de la ligne, bien que la sortie de rand () devrait être suffisamment unique pour court-circuiter la comparaison.
bonsaiviking

@ ghostdog74: La plupart des utilisations dites inutiles de cat sont en fait utiles pour être cohérentes entre les commandes canalisées et non. Mieux vaut garder le cat filename |(ou < filename |) que de se rappeler comment chaque programme prend l'entrée de fichier (ou non).
ShreevatsaR

2
shuf () {awk 'BEGIN {srand ()} {print rand () "\ t" $ 0}' "$ @" | trier | cut -f2-;}
Meow

16

voici un script awk

awk 'BEGIN{srand() }
{ lines[++d]=$0 }
END{
    while (1){
    if (e==d) {break}
        RANDOM = int(1 + rand() * d)
        if ( RANDOM in lines  ){
            print lines[RANDOM]
            delete lines[RANDOM]
            ++e
        }
    }
}' file

production

$ cat file
1
2
3
4
5
6
7
8
9
10

$ ./shell.sh
7
5
10
9
6
8
2
1
3
4

Bien joué, mais en pratique beaucoup plus lent que la réponse du PO , qui se combine awkavec sortet cut. Pour pas plus de plusieurs milliers de lignes, cela ne fait pas beaucoup de différence, mais avec un nombre de lignes plus élevé, cela importe (le seuil dépend de l' awkimplémentation utilisée). Une légère simplification serait de remplacer les lignes while (1){et if (e==d) {break}par while (e<d).
mklement0

11

Un one-liner pour python:

python -c "import random, sys; lines = open(sys.argv[1]).readlines(); random.shuffle(lines); print ''.join(lines)," myFile

Et pour imprimer une seule ligne aléatoire:

python -c "import random, sys; print random.choice(open(sys.argv[1]).readlines())," myFile

Mais voyez ce post pour les inconvénients de python random.shuffle(). Cela ne fonctionnera pas bien avec de nombreux éléments (plus de 2080).


2
"l'inconvénient" n'est pas spécifique à Python. Des périodes PRNG finies pourraient être contournées en réensemencant PRNG avec l'entropie du système comme le /dev/urandomfait. Pour l' utiliser à partir de Python: random.SystemRandom().shuffle(L).
jfs

la jointure () n'a-t-elle pas besoin d'être sur '\ n' pour que les lignes soient imprimées chacune à part?
elig

@elig: Non, car .readLines()renvoie les lignes avec une nouvelle ligne de fin.
mklement0

9

Une fonction simple basée sur awk fera le travail:

shuffle() { 
    awk 'BEGIN{srand();} {printf "%06d %s\n", rand()*1000000, $0;}' | sort -n | cut -c8-
}

usage:

any_command | shuffle

Cela devrait fonctionner sur presque tous les UNIX. Testé sur Linux, Solaris et HP-UX.

Mettre à jour:

Notez que le début des zéros ( %06d) et la rand()multiplication le font fonctionner correctement également sur les systèmes où sortne comprend pas les nombres. Il peut être trié par ordre lexicographique (alias comparaison de chaîne normale).


Bonne idée de regrouper la réponse du PO en tant que fonction; si vous ajoutez "$@", cela fonctionnera également avec les fichiers en entrée. Il n'y a aucune raison de multiplier rand(), car sort -nest capable de trier les fractions décimales. C'est, cependant, une bonne idée de contrôler awkle format de sortie de, car avec le format par défaut %.6g, rand()affichera le nombre occasionnel en notation exponentielle . Bien que le brassage jusqu'à 1 million de lignes soit sans doute suffisant dans la pratique, il est facile de prendre en charge plus de lignes sans payer beaucoup de pénalité de performance; par exemple %.17f.
mklement0

1
@ mklement0 Je n'ai pas remarqué de réponse OPs en écrivant la mienne. rand () est multiplié par 10e6 pour le faire fonctionner avec le tri solaris ou hpux pour autant que je m'en souvienne. Bonne idée avec "$ @"
Michał Šrajer

1
Je l'ai Merci; vous pourriez peut-être ajouter cette justification de la multiplication à la réponse elle-même; généralement, selon POSIX, sortdevrait être capable de gérer des fractions décimales (même avec des milliers de séparateurs, comme je viens de le remarquer).
mklement0

7

Ruby FTW:

ls | ruby -e 'puts STDIN.readlines.shuffle'

1
Grands trucs; Si vous utilisez puts ARGF.readlines.shuffle, vous pouvez le faire fonctionner avec les arguments stdin input et filename.
mklement0

Encore plus court ruby -e 'puts $<.sort_by{rand}'- ARGF est déjà un énumérable, nous pouvons donc mélanger les lignes en les triant par valeurs aléatoires.
akuhn

6

Un liner pour Python basé sur la réponse de scai , mais a) prend stdin, b) rend le résultat reproductible avec la graine, c) ne sélectionne que 200 de toutes les lignes.

$ cat file | python -c "import random, sys; 
  random.seed(100); print ''.join(random.sample(sys.stdin.readlines(), 200))," \
  > 200lines.txt

6

Une manière simple et intuitive serait d'utiliser shuf.

Exemple:

Supposons words.txtque:

the
an
linux
ubuntu
life
good
breeze

Pour mélanger les lignes, procédez comme suit:

$ shuf words.txt

ce qui jetterait les lignes mélangées à la sortie standard ; Donc, vous devez le diriger vers un fichier de sortie comme:

$ shuf words.txt > shuffled_words.txt

Un tel parcours aléatoire pourrait donner:

breeze
the
linux
an
ubuntu
good
life

4

Nous avons un package pour faire le travail même:

sudo apt-get install randomize-lines

Exemple:

Créez une liste ordonnée de numéros et enregistrez-la dans 1000.txt:

seq 1000 > 1000.txt

pour le mélanger, utilisez simplement

rl 1000.txt

3

Ceci est un script python que j'ai enregistré sous rand.py dans mon dossier personnel:

#!/bin/python

import sys
import random

if __name__ == '__main__':
  with open(sys.argv[1], 'r') as f:
    flist = f.readlines()
    random.shuffle(flist)

    for line in flist:
      print line.strip()

Sur Mac OSX sort -Ret shufne sont pas disponibles, vous pouvez donc les alias dans votre bash_profile comme:

alias shuf='python rand.py'

3

Si, comme moi, vous êtes venu ici pour chercher une alternative à shufmacOS, utilisez-le randomize-lines.

Installez le package randomize-lines(homebrew), qui a une rlcommande qui a des fonctionnalités similaires à shuf.

brew install randomize-lines

Usage: rl [OPTION]... [FILE]...
Randomize the lines of a file (or stdin).

  -c, --count=N  select N lines from the file
  -r, --reselect lines may be selected multiple times
  -o, --output=FILE
                 send output to file
  -d, --delimiter=DELIM
                 specify line delimiter (one character)
  -0, --null     set line delimiter to null character
                 (useful with find -print0)
  -n, --line-number
                 print line number with output lines
  -q, --quiet, --silent
                 do not output any errors or warnings
  -h, --help     display this help and exit
  -V, --version  output version information and exit

1
L'installation de Coreutils avec brew install coreutilsfournit le shufbinaire en tant que gshuf.
shadowtalker

2

Si vous avez installé Scala, voici une ligne pour mélanger l'entrée:

ls -1 | scala -e 'for (l <- util.Random.shuffle(io.Source.stdin.getLines.toList)) println(l)'

D'une simplicité séduisante, mais à moins que la machine virtuelle Java ne soit de toute façon démarrée, ce coût de démarrage est considérable; ne fonctionne pas bien avec de grands nombres de lignes non plus.
mklement0

1

Cette fonction bash a la dépendance minimale (uniquement sort et bash):

shuf() {
while read -r x;do
    echo $RANDOM$'\x1f'$x
done | sort |
while IFS=$'\x1f' read -r x y;do
    echo $y
done
}

Belle solution bash qui est parallèle à la awksolution assistée par l'OP , mais les performances seront un problème avec une entrée plus importante; votre utilisation d'une seule $RANDOMvaleur ne mélange correctement que jusqu'à 32 768 lignes d'entrée; bien que vous puissiez étendre cette plage, cela ne vaut probablement pas la peine: par exemple, sur ma machine, l'exécution de votre script sur 32 768 lignes d'entrée courtes prend environ 1 seconde, ce qui représente environ 150 fois la durée d'exécution shufet environ 10-15 fois aussi longtemps que awkdure la solution assistée par le PO . Si vous pouvez compter sur votre sortprésence, vous awkdevriez également être là.
mklement0

0

Dans Windows, vous pouvez essayer ce fichier de commandes pour vous aider à mélanger votre data.txt, l'utilisation du code de commandes est

C:\> type list.txt | shuffle.bat > maclist_temp.txt

Après avoir émis cette commande, maclist_temp.txt contiendra une liste aléatoire de lignes.

J'espère que cela t'aides.


Ne fonctionne pas pour les fichiers volumineux. J'ai abandonné après 2 heures pour un fichier de 1 million de lignes +
Stefan Haberl

0

Pas encore mentionné:

  1. L' unsortutilité. Syntaxe (quelque peu orientée playlist):

    unsort [-hvrpncmMsz0l] [--help] [--version] [--random] [--heuristic]
           [--identity] [--filenames[=profile]] [--separator sep] [--concatenate] 
           [--merge] [--merge-random] [--seed integer] [--zero-terminated] [--null] 
           [--linefeed] [file ...]
  2. msort peut être mélangé par ligne, mais c'est généralement exagéré:

    seq 10 | msort -jq -b -l -n 1 -c r

0

Une autre awkvariante:

#!/usr/bin/awk -f
# usage:
# awk -f randomize_lines.awk lines.txt
# usage after "chmod +x randomize_lines.awk":
# randomize_lines.awk lines.txt

BEGIN {
  FS = "\n";
  srand();
}

{
  lines[ rand()] = $0;
}

END {
  for( k in lines ){
    print lines[k];
  }
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.