J'ai un fichier CSV qui ressemble à ceci
AS2345, ASDF1232, M. Plain Exemple, 110 avenue binaire, Atlantis, RI, 12345, (999) 123-5555,1.56 AS2345, ASDF1232, Mme. Plain Exemple, 1121110 st sternaire . 110 Avenue binaire, Atlantis, RI, 12345, (999) 123-5555,1,56 AS2345, ASDF1232, M. Plain Example, 110 avenue binaire, Liberty City, RI, 12345, (999) 123 -5555,1,56 AS2345, ASDF1232, M. Plain Exemple, 110 avenue Ternary, Une ville, RI, 12345, (999) 123-5555,1,56
Je dois le trier par longueur de ligne, espaces compris. La commande suivante n'inclut pas d'espaces. Y a-t-il un moyen de le modifier pour que cela fonctionne pour moi?
cat $@ | awk '{ print length, $0 }' | sort -n | awk '{$1=""; print $0}'
cat testfile | awk '{ print length, $0 }' | sort -n -s | cut -d" " -f2-
Ou, pour faire votre sous-tri initial (peut-être involontaire) de lignes égales:
cat testfile | awk '{ print length, $0 }' | sort -n | cut -d" " -f2-
Dans les deux cas, nous avons résolu votre problème en nous écartant de awk pour votre dernière coupe.
La question ne précisait pas si un tri supplémentaire était souhaité pour les lignes de longueur correspondante. J'ai supposé que c'était indésirable et ai suggéré l'utilisation de -s
(--stable
) pour éviter que ces lignes ne soient triées les unes contre les autres et conservez-les dans l'ordre relatif dans lequel elles apparaissent dans l'entrée.
(Ceux qui veulent plus de contrôle sur le tri de ces liens peuvent regarder le tri de --key
option.)
Il est intéressant de noter la différence entre:
echo "hello awk world" | awk '{print}'
echo "hello awk world" | awk '{$1="hello"; print}'
Ils cèdent respectivement
hello awk world
hello awk world
La section pertinente du manuel (de gawk) mentionne seulement de façon indirecte que awk reconstruira l'intégralité de $ 0 (en fonction du séparateur, etc.) lorsque vous modifiez un champ. Je suppose que ce n'est pas un comportement fou. Il a ceci:
"Enfin, il est parfois utile de forcer awk à reconstruire l'intégralité de l'enregistrement, en utilisant la valeur actuelle des champs et OFS. Pour ce faire, utilisez la tâche apparemment anodine:"
$1 = $1 # force record to be reconstituted
print $0 # or whatever else with $0
"Cela oblige awk à reconstruire le disque."
aa A line with MORE spaces
bb The very longest line in the file
ccb
9 dd equal len. Orig pos = 1
500 dd equal len. Orig pos = 2
ccz
cca
ee A line with some spaces
1 dd equal len. Orig pos = 3
ff
5 dd equal len. Orig pos = 4
g
La solution AWK de neillb est très bien si vous voulez vraiment utiliser awk
et explique pourquoi c'est compliqué, mais si ce que vous voulez, c'est que le travail soit fait rapidement et que vous ne le fassiez pas. Peu importe ce que vous faites, une solution consiste à utiliser la fonction sort()
de Perl avec une routine de paramétrage personnalisée pour effectuer une itération sur les lignes en entrée. Voici un one-liner:
Perl -e 'print sort { length($a) <=> length($b) } <>'
Vous pouvez mettre cela dans votre pipeline où vous en avez besoin, soit en recevant STDIN (de cat
ou une redirection de shell), soit en donnant le nom du fichier à Perl comme autre argument et en le laissant ouvrir le fichier.
Dans mon cas, j'avais besoin des lignes les plus longues en premier, alors j'ai échangé $a
et $b
dans la comparaison.
Essayez cette commande à la place:
awk '{print length, $0}' your-file | sort -n | cut -d " " -f2-
Vous trouverez ci-dessous les résultats d’une analyse comparative des solutions proposées ci-après.
Perl
de Caleb a pris 11,2 secondesPerl
a pris 11,6 secondesawk
de neillb # 1 a pris 20 secondesawk
de neillb # 2 a pris 23 secondesawk
d'Anubhava a pris 24 secondes.awk
de Jonathan a pris 25 secondesbash
de Fretz prend 400 fois plus longtemps que les solutions awk
(en utilisant un scénario de test tronqué de 100 000 lignes). Cela fonctionne bien, prend juste pour toujours.Perl
De plus, j'ai ajouté une autre solution Perl:
Perl -ne 'Push @a, $_; END{ print sort { length $a <=> length $b } @a }' file
Pure Bash:
declare -a sorted
while read line; do
if [ -z "${sorted[${#line}]}" ] ; then # does line length already exist?
sorted[${#line}]="$line" # element for new length
else
sorted[${#line}]="${sorted[${#line}]}\n$line" # append to lines with equal length
fi
done < data.csv
for key in ${!sorted[*]}; do # iterate over existing indices
echo -e "${sorted[$key]}" # echo lines with equal length
done
La fonction length()
inclut des espaces. Je ferais juste des ajustements mineurs à votre pipeline (y compris en évitant UUOC ).
awk '{ printf "%d:%s\n", length($0), $0;}' "$@" | sort -n | sed 's/^[0-9]*://'
La commande sed
supprime directement les chiffres et les points ajoutés par la commande awk
. Sinon, conservez votre mise en forme de awk
:
awk '{ print length($0), $0;}' "$@" | sort -n | sed 's/^[0-9]* //'
1) solution d'awk pure. Supposons que la longueur de la ligne ne puisse pas dépasser 1024 alors
cat nom du fichier | awk 'BEGIN {min = 1024; s = "";} {l = longueur ($ 0); si (l <min) {min = l; s = $ 0;}} END {print s} '
2) une solution de liner bash en supposant que toutes les lignes ont seulement 1 mot, mais peut être retravaillée pour tous les cas où toutes les lignes ont le même nombre de mots:
LINES = $ (cat filename); pour k dans $ LINES; faire printf "$ k"; echo $ k | wc -L; fait | trier -k2 | tête -n 1 | cut -d "" -f1
J'ai trouvé que ces solutions ne fonctionneraient pas si votre fichier contenait des lignes commençant par un nombre, car elles seraient triées numériquement avec toutes les lignes comptées. La solution consiste à donner à sort
l'indicateur -g
(Tri général-numérique) au lieu de -n
(Tri numérique):
awk '{ print length, $0 }' lines.txt | sort -g | cut -d" " -f2-
Voici une méthode compatible avec plusieurs octets pour trier les lignes par longueur. Cela demande:
wc -m
Est disponible pour vous (macOS l'a).LC_ALL=UTF-8
. Vous pouvez le définir soit dans votre profil .bash, ou simplement en le ajoutant avant la commande suivante.testfile
a un codage de caractères correspondant à votre langue (par exemple, UTF-8).Voici la commande complète:
cat testfile | awk '{l=$0; gsub(/\047/, "\047\"\047\"\047", l); cmd=sprintf("echo \047%s\047 | wc -m", l); cmd | getline c; close(cmd); sub(/ */, "", c); { print c, $0 }}' | sort -ns | cut -d" " -f2-
Expliquer partie par partie:
l=$0; gsub(/\047/, "\047\"\047\"\047", l);
← crée une copie de chaque ligne dans la variable awk l
et double-échappe tous les '
afin que la ligne puisse être répercutée en tant que une commande Shell (\047
est une simple citation en notation octale).cmd=sprintf("echo \047%s\047 | wc -m", l);
← C'est la commande à exécuter, qui renvoie la ligne échappée à wc -m
.cmd | getline c;
← exécute la commande et copie la valeur du nombre de caractères renvoyée dans la variable awk c
.close(cmd);
← ferme le canal de la commande Shell pour éviter de limiter le nombre de fichiers ouverts dans un processus.sub(/ */, "", c);
← élimine les espaces à partir du nombre de caractères renvoyé par wc
.{ print c, $0 }
← imprime la valeur du nombre de caractères de la ligne, un espace et la ligne d'origine.| sort -ns
← trie les lignes (par nombre de caractères ajoutés) numériquement (-n
) Et maintient l'ordre de tri stable (-s
).| cut -d" " -f2-
← supprime les valeurs de nombre de caractères ajoutées.C'est lent (seulement 160 lignes par seconde sur un Macbook Pro rapide) car il doit exécuter une sous-commande pour chaque ligne.
Vous pouvez également le faire uniquement avec gawk
(à partir de la version 3.1.5, gawk prend en charge plusieurs octets), ce qui serait nettement plus rapide. Il est très difficile de faire toutes les échappements et les guillemets pour passer les lignes en toute sécurité via une commande Shell de awk, mais c'est la seule méthode que je n'ai pas pu trouver et qui ne nécessite pas l'installation de logiciel supplémentaire (gawk n'est pas disponible par défaut sur macOS).