Tri groupé des paragraphes continus (séparés par une ligne vierge)?

8

Je pense que je suis assez expérimenté maintenant dans le tri par colonnes ; cependant, je n'ai jusqu'à présent rien trouvé pour trier les lignes continues .

Supposons que nous ayons un fichier texte qui ressemble à ceci: (très simplifié, bien sûr)

Echo
Alpha
Delta
Charlie

Golf
Bravo
Hotel
Foxtrot

Maintenant, est-il possible de trier les lignes de manière alphanumérique pour chaque bloc séparément ? Je veux dire, pour que le résultat ressemble à ceci:

Alpha
Charlie
Delta
Echo

Bravo
Foxtrot
Golf
Hotel

D'après ce que j'ai trouvé dans la sortpage de manuel, cela pourrait ne pas être possible avec la sortcommande UNIX intégrée . Ou peut-on même le faire sans avoir recours à des outils externes / tiers?

erreur de syntaxe
la source

Réponses:

9

La awksolution de Drav est bonne, mais cela signifie exécuter une sortcommande par paragraphe. Pour éviter cela, vous pouvez faire:

< file awk -v n=0 '!NF{n++};{print n,$0}' | sort -k1n -k2 | cut -d' ' -f2-

Ou vous pouvez tout faire en perl:

perl -ne 'if (/\S/){push@l,$_}else{print sort@l if@l;@l=();print}
          END{print sort @l if @l}' < file

Notez que ci-dessus, les séparateurs sont des lignes vides (pour l' awkun, des lignes avec uniquement des espaces ou des tabulations, pour l' perlun, n'importe quel caractère d'espacement horizontal ou vertical) au lieu de lignes vides. Si vous voulez des lignes vides, vous pouvez les remplacer !NFpar !lengthou $0=="", et /\S/par /./.

Stéphane Chazelas
la source
Merci à vous aussi, surtout pour la awksolution qui évite les sortfrais généraux! Sournois!
syntaxerror
9
awk -v RS= -v cmd=sort '{print | cmd; close(cmd); print ""}' file

La définition du séparateur d'enregistrement RSsur une chaîne vide fait avancer awk dans les paragraphes à la fois. Pour chaque paragraphe, canalisez le paragraphe (in $0) vers cmd (qui est défini sur sort) et imprimez la sortie. Imprimez une ligne vierge pour séparer les paragraphes de sortie par un print "".

Si nous donnons des exemples de Perl, je présente une approche alternative à celle de Stéphane:

perl -e 'undef $/; print join "\n", sort (split /\n/), "\n" 
    foreach(split(/\n\n/, <>))' < file

Désactiver le séparateur de champ ( undef $/), cela nous permet d'utiliser <>et d'obtenir l'ensemble de STDIN. Nous avons ensuite splitcela autour \n\n(paragraphes). foreach"paragraphe", sortles lignes en splittintant autour des sauts de ligne, sortpuis joinles réintégrant ensemble et clouant sur une queue \n.

Cependant, cela a un effet secondaire d'ajouter un séparateur "paragraphe de fin" sur le dernier paragraphe (s'il n'en avait pas auparavant). Vous pouvez contourner cela avec le moins moins joli:

perl -e 'undef $/; print join "\n", sort (split /\n/) , (\$_ == \$list[-1] ? "" : "\n")
    foreach(@list = split(/\n\n/, <>))' < file

Ceci affecte les paragraphes à @list, puis il y a une "opération ternaire" pour vérifier s'il s'agit du dernier élément de foreach(la \$_ == \$list[-1]vérification). affiche ""s'il s'agit de ( ? ...), sinon ( : ...) affiche "\n"pour tous les autres "paragraphes" (éléments de @list).

Drav Sloan
la source
C'est bien! Je vous remercie. Invoquez-vous réellement /usr/bin/sortavec cette ligne ou s'agit-il d'une commande awkintégrée de "tri"?
erreur de syntaxe
Invoquer la commande sort, d'où l'exigence de fermer (cmd) sur chaque boucle :)
Drav Sloan
5

J'ai écrit un outil en haskell qui vous permet d'utiliser sort, shuf, tac ou toute autre commande sur des paragraphes de texte.

https://gist.github.com/siers/01306a361c22f2de0122
EDIT: l'outil est également inclus dans ce dépôt: https://github.com/siers/haskell-import-sort

Il divise le texte en blocs, joint les sous-blocs avec \0char, dirige la commande et fait finalement la même chose en sens inverse.

28-08-2015 : J'ai trouvé une autre utilisation personnelle de cet outil - sélectionner N paragraphes après une ligne.

paramap grep -aA2 '^reddit usernames' < ~/my-username-file
reddit usernames

foo
bar
baz

a couple
more of these
Raitis Veinbahs
la source
4

Si GNU awk est disponible, vous pouvez trier chaque bloc à l'aide de la asort()fonction intégrée. Quelque chose comme ça:

blocksort.awk

function sort_n_print(array) {
  asort(array)
  for(i=1; i<=length(array); i++)
    print array[i]
  delete array
}

NF { a[++x] = $0 }

!NF { sort_n_print(a); print }

END { sort_n_print(a) }

Exécutez-le comme ceci:

awk -f blocksort.awk infile
Thor
la source
1

TXR Lisp étape par étape:

$ cat data
Echo
Alpha
Delta
Charlie

Golf
Bravo
Hotel
Foxtrot

$ txr -p '(get-lines)' < data
("Echo" "Alpha" "Delta" "Charlie" "" "Golf" "Bravo" "Hotel" "Foxtrot")

$ txr -t '(get-lines)' < data
Echo
Alpha
Delta
Charlie

Golf
Bravo
Hotel
Foxtrot

$ txr -p '(partition* (get-lines) (op where [chain length zerop]))' < data
(("Echo" "Alpha" "Delta" "Charlie") ("Golf" "Bravo" "Hotel" "Foxtrot"))

$ txr -p '[mapcar sort (partition* (get-lines) (op where [chain length zerop]))]' < data
(("Alpha" "Charlie" "Delta" "Echo") ("Bravo" "Foxtrot" "Golf" "Hotel"))

$ txr -p '(interpose (list "") [mapcar sort (partition* (get-lines) (op where [chain length zerop]))])' < data
(("Alpha" "Charlie" "Delta" "Echo") ("") ("Bravo" "Foxtrot" "Golf" "Hotel"))

$ txr -t '(interpose (list "") [mapcar sort (partition* (get-lines) (op where [chain length zerop]))])' < data
Alpha
Charlie
Delta
Echo

Bravo
Foxtrot
Golf
Hotel

Références: get-lines , partition * , op , where , chain , length , zerop , mapcar , interpose .

Kaz
la source
Notez que dans le [mapcar sort ...]nous pourrions remplacer sortpar une fonction qui redirige les chaînes à travers un processus externe. On peut alors se retrouver avec un outil de distribution d'une commande externe de traitement de texte sur des paragraphes.
Kaz