2
votes

Extraire les colonnes d'un fichier séparé par des tabulations

J'ai un fichier ( data.rdb ) au format suivant:

date    star    jdb texp
2013-11-22#epsInd#2400000.23551544#100.

Certaines propriétés des données:

  • toutes les colonnes sont séparées par des tabulations
  • les colonnes n'ont pas la même largeur
  • les cellules peuvent ne pas avoir la même longueur
  • le fichier aura beaucoup plus de colonnes que ce qui est présenté et quelques centaines de lignes
  • les noms de colonnes que j'ai fournis sont simplement génériques, les vrais noms peuvent être n'importe quel mot, sans tabulation, sans espace ni caractère spécial.

J'ai besoin d'extraire certaines des colonnes par nom en utilisant bash, par exemple, col1 , col3 et col6 , où les colonnes à sélectionner proviennent d'une variable shell définie comme COLUMN_LIST = $ @ $ @ sont les paramètres passés à mon script shell. Le nombre et le nom des paramètres peuvent changer chaque fois que j'appelle le script.

Le script doit être en bash, ne peut pas être python ou similaire.

Des idées? J'ai pensé à utiliser awk / gawk , mais je ne sais pas comment sélectionner par nom de colonne. L'ordre des colonnes peut changer d'un fichier à l'autre.

Merci Jorge

MISE À JOUR

pour une raison quelconque, aucune de ces solutions ne semble fonctionner sur mes vrais fichiers de données (c'est-à-dire que je n'obtiens aucun résultat), donc je poste un sous-ensemble de l'un de ceux-ci:

tr $'\t' '#' < data.rdb | head -2

dans ce cas, je serais intéressé par les colonnes star jdb code> et texp

MISE À JOUR 2

J'ai utilisé le code de @ EdMorton et voici le résultat: p >

BEGIN {
    numCols = split(column_list,cols)
    OFS="\t"
}
{ sub(/\r$/,"") }
NR==1 {
    for (fldNr=1; fldNr<=NF; fldNr++) {
        f[$fldNr] = fldNr
    }
}
{
    for (colNr=1; colNr<=numCols; colNr++) {
        colName = cols[colNr]
        colVal  = (colName in f ? $(f[colName]) : "")
        printf "%s%s", colVal, (colNr<numCols ? OFS : ORS)
    }
}

UPDATE 3

J'ai fini par utiliser la version d'EdMorton de awk - principalement pour la flexibilité sur le sortie - mais avec la modification que je ne veux pas qu'il affiche de mauvaises colonnes:

date    star    jdb texp    date    star    jdb texp
2013-11-22  epsInd   2400000.23551544   100.    2013-11-22  epsInd   2400000.23551544   100.
2013-11-22  epsInd   2400000.23551544   100.    2013-11-22  epsInd   2400000.23551544   100.
2013-11-22  epsInd   2400000.23551544   100.    2013-11-22  epsInd   2400000.23551544   100.
2013-11-22  HD217987 2400000.23551544   900.    2013-11-22  HD217987 2400000.23551544   900.
2013-11-22  TOI-134  2400000.23551544   900.    2013-11-22  TOI-134  2400000.23551544   900.
2013-11-22  tauCet   2400000.23551544   60.     2013-11-22  tauCet   2400000.23551544   60. 
2013-11-22  BD+01316 2400000.23551544   300.    2013-11-22  BD+01316 2400000.23551544   300.
2013-11-22  BD+01316 2400000.23551544   300.    2013-11-22  BD+01316 2400000.23551544   300.
2013-11-22  BD+01316 2400000.23551544   300.    2013-11-22  BD+01316 2400000.23551544   300.
2013-11-22  BD+01316 2400000.23551544   300.    2013-11-22  BD+01316 2400000.23551544   300.

Le principal problème que j'ai eu était que la ligne d'en-tête n'était pas séparée par des tabulations et en tant que telle la ventilation des colonnes n'a pas fonctionné. Un moyen facile de repérer les caractères de tabulation / non-tabulation:

date    star    jdb texp
2013-11-22  epsInd   2400000.23551544   100.
2013-11-22  epsInd   2400000.23551544   100.
2013-11-22  epsInd   2400000.23551544   100.
2013-11-22  HD217987 2400000.23551544   900.
2013-11-22  TOI-134  2400000.23551544   900.
2013-11-22  tauCet   2400000.23551544   60. 
2013-11-22  BD+01316 2400000.23551544   300.
2013-11-22  BD+01316 2400000.23551544   300.
2013-11-22  BD+01316 2400000.23551544   300.
2013-11-22  BD+01316 2400000.23551544   300.

qui a donné sur l'un de mes fichiers de test:

col1    col2    col3    col4    col5    col6    col7
aaa1    bbb1    ccc1    ddd1    eee1    fff1    ggg1
aaa2    bbb2    ccc2    ddd2    eee2    fff2    ggg2
aaa3    bbb3    ccc3    ddd3    eee3    fff3    ggg3

p>


11 commentaires

Comment appelez-vous votre script? Comme ./myscript 1 3 6 ou ./myscript colname1 colname2 colname3


@kvantour: ./myscript colname1 colname2


désolé, par tout ce que je veux dire, un mot, j'ai mis à jour la question.


Ils sont en effet séparés par des tabulations et ne semblent pas trouver de problème avec eux.


La seule différence que je peux trouver est que j'ai beaucoup plus de lignes et plus de colonnes


Je soupçonne fortement que votre fichier d'entrée a des fins de ligne DOS, voir stackoverflow.com/q/45772525/1745001 pour ce que cela signifie et comment le réparer. Pendant ce temps, j'ai mis à jour ma réponse pour me protéger contre les fins de ligne DOS et / ou un utilisateur demandant un nom de colonne qui n'existe pas pour être affiché. Essayez-le maintenant et dites-nous le résultat.


Ok, j'ai vérifié quelques fichiers avec cat -vE et la seule chose étrange que j'obtiens sont des signes dollar à la fin de chaque ligne ... L'exécution de votre version mise à jour du code me permet les en-têtes de colonne (s) correct (s), cependant tous remplis avec N / A.


Les signes $ indiquent le caractère de saut de ligne. Ils sont normaux / attendus à la fin de chaque ligne. D'après votre autre commentaire, il semble que vos colonnes ne soient PAS séparées par des tabulations comme vous l'avez affirmé et c'est votre problème. Pour la prochaine fois - vous auriez pu le repérer en exécutant tr $ '\ t' '#' pour rendre les onglets visibles.


c'est une bonne commande à garder, merci! Quand je l'exécute, j'obtiens # à la place des onglets, mais des espaces dans les en-têtes, bien que je crée mes en-têtes avec `header = $ (echo date $ '\ t'star $' \ t ' rv $ '\ t'dvrms $' \ t '). Pourquoi en est-il ainsi?


Vous êtes les bienvenus. Vous n'avez pas cité la chaîne que vous avez passée à echo, donc echo a vu chaque valeur séparée par des tabulations comme un argument séparé et les a sorties séparées par des blancs. Essayez simplement header = $ 'date \ tstar \ trv \ tdvrms' à la place.


ok, merci, va corriger mon code en tenant compte de cela, merci!


3 Réponses :


3
votes

L'ordre des colonnes peut changer d'un fichier à l'autre.

Vous pouvez utiliser cette approche en utilisant awk qui prend les noms de colonnes d'en-tête séparés par des espaces en entrée et les convertit d'abord en numéro de colonne en traitant le premier enregistrement. Une fois les numéros de colonne requis récupérés, nous les imprimons simplement à partir de la ligne suivante.

star      jdb               texp
epsInd    2400000.23551544  100.
epsInd    2400000.23551544  100.
epsInd    2400000.23551544  100.
HD217987  2400000.23551544  900.
TOI-134   2400000.23551544  900.
tauCet    2400000.23551544  60.
BD+01316  2400000.23551544  300.
BD+01316  2400000.23551544  300.
BD+01316  2400000.23551544  300.
BD+01316  2400000.23551544  300.

awk -v cols='col1 col3 col6' 'BEGIN {
   FS=OFS="\t"
   n = split(cols, a, " ")
   for (i=1; i <= n; i++)
      c[a[i]]
}
{
   sub(/\r$/, "")
}
NR == 1 {
   for (i=1; i<=NF; i++)
      if ($i in c)
         hdr[i]
}
{
   for (i=1; i<=NF; i++)
      if (i in hdr)
         s = sprintf(s "%s%s", OFS, $i)
   sub(OFS, "", s)
   print s
   s =""
} ' file | column -t

PS: Ajout de column -t pour formater la sortie au format tabulaire.


2 commentaires

Merci, mais je n'arrive pas à le faire fonctionner sur mes fichiers. Une note, les vraies colonnes sont des notes nommées "colX" mais ont des noms totalement différents, tels que rv , fwhm , etc., cela ferait-il une différence?


@jorgehumberto: Vérifiez la réponse mise à jour avec votre entrée nouvellement mise à jour



0
votes

Vous pouvez le faire avec coreutils . En supposant que vous ayez un fichier callef cols contenant les colonnes souhaitées, par exemple:

col2    col3    col6
bbb1    ccc1    fff1
bbb2    ccc2    fff2
bbb3    ccc3    fff3

Vous pouvez extraire les numéros de colonne comme ceci:

cut -f $(head -n1 infile | tr '\t' '\n' | grep -nf cols | cut -d: -f1 | paste -sd,) infile


1 commentaires

quel est le format de cols? un fichier ou une variable shell?



2
votes

La meilleure façon de gérer cela est de créer un tableau ( f [] ci-dessous) qui mappe les chaînes d'en-tête de colonne (c'est-à-dire les noms de champ) aux numéros de champ lors de la lecture de la ligne d'en-tête et puis accédez aux champs par leur nom à partir de là.

Mise à jour pour se protéger contre l'appelant demandant un nom de colonne qui n'existe pas et contre les fins de ligne DOS:

$ awk -v column_list='col5 col2 col4' -f tst.awk data.rdb
col5    col2    col4
eee1    bbb1    ddd1
eee2    bbb2    ddd2
eee3    bbb3    ddd3

Notez qu'avec l'approche ci-dessus, si vous le souhaitez, vous pouvez modifier l'ordre des colonnes de sortie, pas simplement les imprimer dans leur ordre d'origine:

$ cat tst.awk
BEGIN {
    numCols = split(column_list,cols)
    FS=OFS="\t"
}
{ sub(/\r$/,"") }
NR==1 {
    for (fldNr=1; fldNr<=NF; fldNr++) {
        f[$fldNr] = fldNr
    }
}
{
    for (colNr=1; colNr<=numCols; colNr++) {
        colName = cols[colNr]
        colVal  = (colName in f ? $(f[colName]) : (NR>1 ? "N/A" : colName))
        printf "%s%s", colVal, (colNr<numCols ? OFS : ORS)
    }
}

$ awk -v column_list='col1 col3 col6' -f tst.awk data.rdb
col1    col3    col6
aaa1    ccc1    fff1
aaa2    ccc2    fff2
aaa3    ccc3    fff3

$ awk -v column_list='col1 col3 col6 bob' -f tst.awk data.rdb
col1    col3    col6    bob
aaa1    ccc1    fff1    N/A
aaa2    ccc2    fff2    N/A
aaa3    ccc3    fff3    N/A


6 commentaires

ok, j'ai réussi à faire fonctionner ce qui précède, il suffit d'échanger FS = OFS = "\ t" par OFS = "\ t" .


Vous avez dit dans votre question que toutes les colonnes sont séparées par des tabulations . Si vous ne deviez pas définir FS sur tabulation pour que cela fonctionne, cette affirmation n'est pas vraie, ce qui expliquerait certainement pourquoi vous ne pouvez pas faire fonctionner l'une des solutions que nous avons fournies sur la base de cette déclaration.


C'est la chose étrange, j'ai revérifié mes fichiers de données et les séparateurs semblent être des onglets (ou au moins montrer comme onglet lorsque je les sélectionne sur la ligne de commande ..).


Et je les ai générés sous forme d'onglets. Lorsque je crée les fichiers avec awk (code adapté de stackoverflow.com/questions/25168259/... ) I assurez-vous que OFS = "\ t" est défini.


Exécutez tr $ '\ t' '#' et publiez la sortie.


ok, le problème semble être avec les en-têtes, désolé à ce sujet.line1: date star jdb texp line2: 2013-11-22 # epsInd # 2400000.23551544 # 100.