Comment puis-je utiliser bash pour fractionner uniquement certains éléments d’un fichier texte?

J’essaie de comprendre comment créer un fichier .txt (myGeneFile.txt) d’ID et de gènes ressemblant à ceci:

Probe Set ID Gene Symbol 1007_s_at DDR1 /// MIR4640 1053_at RFC2 117_at HSPA6 121_at PAX8 1255_g_at GUCA1A 1294_at MIR5193 /// UBA7 

dans ceci:

 DDR1 MIR4640 RFC2 HSPA6 PAX8 GUCA1A MIR5193 UBA 

J’ai d’abord essayé de faire ça:

 cat myGeneFile.txt | tail -n +2 | awk '{split($2,a,"///"); print a[1] "\t" a[2] "\t" a[3] "\t" a[4] "\t" a[5];}' > test.txt 

(c.-à-d., j’ai supprimé la ligne supérieure (en-tête) du fichier, j’ai essayé de diviser la deuxième ligne le long du délimiteur ///, puis d’imprimer tous les gènes qui pourraient apparaître)

Ensuite, j’ai essayé de faire ceci:

 cat myGeneFile.txt | tail -n +2 | awk '{print $2}' | grep -o -E '\w+' > test.txt 

(listant littéralement tous les mots de la deuxième colonne)

J’ai obtenu le même résultat dans les deux cas – une longue liste de seulement le premier gène dans chaque ligne (par exemple, MIR4640 et UBA7 étaient mal alignés)

Des idées?


EDIT: Merci @CodeGnome pour votre aide. J’ai fini par utiliser ce code et je l’ai modifié car j’ai découvert que mon fichier contenait entre 1 et 30 noms de gènes différents sur chaque ligne. Donc, j’ai utilisé:

 awk 'NR == 1 {next} { sub("///", "") print $2 } { for (i=3; i test2.txt 

@GlenJackson avait aussi une solution qui fonctionnait très bien:

 awk 'NR>1 {for (i=2; i<=NF; i++) if ($i != "///") print $i}' file 

Mon awk prend:

 awk 'NR>1 {for (i=2; i<=NF; i++) if ($i != "///") print $i}' file 

ou sed

 sed ' 1d # delete the header s/[[:blank:]]\+/ /g # squeeze whitespace s/^[^ ]\+ // # remove the 1st word s| ///||g # delete all "///" words s/ /\n/g # replace spaces with newlines ' file 

Utiliser des instructions d’impression conditionnelles dans une action AWK

Ce qui suit donne la sortie souhaitée en supprimant les caractères indésirables avec sub (), puis en utilisant plusieurs instructions d’impression pour créer les sauts de ligne. La deuxième instruction d’impression est conditionnelle et ne se déclenche que lorsque le troisième champ n’est pas vide. Cela évite de créer des lignes vides dans la sortie.

 $ awk 'NR == 1 {next} { sub("///", "") print $2 if ($3) {print $3} }' myGeneFile.txt DDR1 MIR4640 RFC2 HSPA6 PAX8 GUCA1A MIR5193 UBA7 

Cela fonctionnera:

 tail -n+2 tmp | sed -E 's/ +/ /' | cut -d' ' -f2- | sed 's_ */// *_\n_' 

Voici ce qui se passe:

  1. tail -n+2 Enlève l’en-tête
  2. sed -E 's/ +/ /' Condense les espaces
  3. cut -d' ' -f2- Utilisez cut pour sélectionner tous les champs sauf le premier, en utilisant un seul espace comme délimiteur
  4. sed 's_ */// *_\n_' Convertit tous les /// (et tout espace blanc environnant) en une nouvelle ligne

Vous n’avez pas besoin du cat initial, il est généralement préférable de simplement passer le fichier d’entrée en tant qu’argument à la première commande. Si vous voulez le nom du fichier dans un endroit facile à modifier, cette option est préférable car elle évite le processus supplémentaire (et je trouve plus facile de changer le fichier à la fin):

 (tail -n+2 | sed -E 's/ +/ /' | cut -d' ' -f2- | sed 's_ */// *_\n_') < tmp 

Compte tenu de l’entrée existante et de l’exigence modifiée (d’après le commentaire de la réponse de Morgen), ce qui suit devrait faire ce que vous voulez (pour un nombre quelconque de colonnes de gènes).

 awk 'NR > 1 { p=0 for (i = 2; i <= NF; i++) { if ($i == "///") { p=1 continue } printf "%s%s\n", p?"n":"", $i } }' input.txt 

Vos critères pour sélectionner les chaînes à sortir ne sont pas tout à fait claires, mais voici une autre commande qui produit au moins votre sortie attendue:

 tail -n +2 myGeneFile.txt | grep -oE '\<[AZ][A-Z0-9]*\>' 

Il ne fait que 1) sauter la première ligne et 2) trouver tous les autres mots (délimités par des caractères non-mots et / ou début / fin de ligne) qui sont entièrement composés de lettres majuscules ou de chiffres, le premier étant une lettre.