J'utilise beaucoup de tri grek awk dans mon shell Unix pour travailler avec des fichiers texte de colonne de taille moyenne (environ 10 à 100 millions de lignes) séparés par des tabulations. À cet égard, le shell Unix est ma feuille de calcul.
Mais j'ai un gros problème, c'est la sélection des enregistrements en fonction d'une liste d'ID.
Ayant un table.csv
fichier au format id\tfoo\tbar...
et un ids.csv
fichier avec la liste des identifiants, sélectionnez uniquement les enregistrements table.csv
dont l'identifiant est présent dans ids.csv
.
type de /programming/13732295/extract-all-lines-from-text-file-based-on-a-given-list-of-ids mais avec shell, pas perl.
grep -F
produit évidemment des faux positifs si les identifiants sont de largeur variable.
join
est un utilitaire que je ne pourrais jamais comprendre. Tout d'abord, cela nécessite un tri alphabétique (mes fichiers sont généralement triés numériquement), mais même dans ce cas, je ne peux pas le faire fonctionner sans me plaindre d'un ordre incorrect et sans sauter certains enregistrements. Donc je n'aime pas ça. grep -f par rapport au fichier avec ^id\t
-s est très lent lorsque le nombre d'identifiants est élevé.
awk
est encombrant.
Existe-t-il de bonnes solutions pour cela? Des outils spécifiques pour les fichiers séparés par des tabulations? Des fonctionnalités supplémentaires seront également les bienvenues.
UPD: corrigé sort
->join
grep -f
est trop lente, le maintien de cette stratégie semble plus difficile que cela en vaut la peine - les variations seront probablement la proie des mêmes problèmes de performances O (N * M). Peut-être que votre temps serait mieux consacré à apprendre à utiliser une base de données SQL normalisée ...awk
.sort
peut faire toutes sortes de tri, numérique, alphabétique et autres. Tu voisman sort
.Réponses:
Je suppose que vous
grep -f
ne vouliez pas dire cela,grep -F
mais vous avez en fait besoin d'une combinaison des deux et-w
:La raison pour laquelle vous obtenez des faux positifs est (je suppose, vous ne l'avez pas expliqué) car si un identifiant peut être contenu dans un autre, les deux seront imprimés.
-w
supprime ce problème et-F
s'assure que vos modèles sont traités comme des chaînes et non comme des expressions régulières. Deman grep
:Si vos faux positifs sont dus au fait qu'un ID peut être présent dans un champ non-ID, parcourez votre fichier à la place:
ou, plus rapidement:
Personnellement, je le ferais
perl
cependant:la source
^
avec -F, vous ne pouvez pas cibler spécifiquement la première colonne.^id\t
bit de l'OP impliqueid
peut se produire dans une autre colonne. Sinon, cela n'a pas d'importance.L'
join
utilitaire est ce que vous voulez. Il nécessite que les fichiers d'entrée soient triés lexicalement.En supposant que votre shell est bash ou ksh:
Sans avoir besoin de trier, la solution habituelle awk est
la source
join
n'est pas un coup de coude: vos mots étaient que vous ne pouviez pas le comprendre. Ouvrez votre esprit et apprenez. Quel résultat avez-vous obtenu et en quoi cela diffère-t-il de ce que vous attendez?join
.awk
solution ici est très rapide et efficace pour mes besoins (j'extraye des sous-ensembles de quelques centaines de fichiers avec ~ 100 millions de lignes)Les réponses à cette question SO m'ont aidé à contourner les problèmes avec join. Essentiellement, lorsque vous triez le fichier en vue de l'envoyer à la jointure, vous devez vous assurer que vous triez en fonction de la colonne sur laquelle vous vous joignez. Donc, si c'est le premier, vous devez lui dire quel est le caractère séparateur dans le fichier et que vous voulez qu'il trie sur le premier champ (et seulement le premier champ). Sinon, si le premier champ a des largeurs variables (par exemple), vos séparateurs et éventuellement d'autres champs peuvent commencer à affecter l'ordre de tri.
Donc, utilisez l'option -t de tri pour spécifier votre caractère de séparation, et utilisez l'option -k pour spécifier le champ (en vous rappelant que vous avez besoin d'un champ de début et de fin - même s'il est le même - ou il triera à partir de ce caractère à la fin de la ligne).
Donc, pour un fichier séparé par des tabulations comme dans cette question, ce qui suit devrait fonctionner (avec merci à la réponse de glenn pour la structure):
join -t$'\t' <(sort -d ids.csv) <(sort -d -t$'\t' -k1,1 table.csv) > output.csv
(Pour référence, l'indicateur -d signifie le tri par dictionnaire. Vous pouvez également utiliser l'indicateur -b pour ignorer les espaces principaux, voir
man sort
etman join
).À titre d'exemple plus général, supposons que vous joignez deux fichiers séparés par des virgules -
input1.csv
sur la troisième colonne etinput2.csv
sur la quatrième. Vous pourriez utiliserjoin -t, -1 3 -2 4 <(sort -d -t, -k3,3 input2.csv) <(sort -d -t, -k4,4 input2.csv) > output.csv
Ici, les options
-1
et-2
spécifient les champs à joindre respectivement dans les premier et deuxième fichiers d'entrée.la source
Vous pouvez également utiliser ruby pour faire quelque chose de similaire:
la source