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?

Ruggiero Spearman
la source
4
Duplicate de stackoverflow.com/questions/886237/…
pause jusqu'à nouvel ordre.
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 .]

Joey
la source
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
Moonyoung Kang
la source
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))" %*
mklement0
la source
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".

NickZoic
la source
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
Ruggiero Spearman
la source
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
ghostdog74
la source
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).

scai
la source
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).

Michał Šrajer
la source
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'
hoffmanc
la source
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
dfrankow
la source
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
kmario23
la source
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
navigaid
la source
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'
Jeff Wu
la source
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
Ahmad Awais
la source
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)'
swartzrock
la source
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
}
Miaou
la source
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.

Ayfan
la source
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
agc
la source
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];
  }
}
biziclop
la source