Переместить столбец в начало по ключевому слову заголовка в bash - PullRequest
1 голос
/ 17 мая 2019

У меня есть файл (data.rdb) в следующем формате:

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.

некоторые свойства:

  • все столбцы разделены табуляцией
  • столбцы не имеют одинаковую ширину
  • ячейки могут иметь разную длину
  • файл будет иметь гораздо больше столбцов, чем представлено, и несколько сотен строк
  • столбцыИмена могут быть любыми словами без символов табуляции, пробелов или специальных символов

Как переместить столбец с заголовком jdb на первый столбец?

Некоторые ограничения:

  • это будет применяться к нескольким файлам, и столбец jdb не всегда будет отображаться в одной и той же позиции
  • в идеале порядок оставшихся столбцов не должен меняться
  • jdb всегда будет первым столбцом в конце.

Спасибо!

ОБНОВЛЕНИЕ

это awk блок, который я использую в данный момент:

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  = (colNr=1 ? $(f["jdb"]): (colNr <= $(f["jdb"] ? 
$(f[colName] -1) : $(f[colName]))))
        printf "%s%s", colVal, (colNr<numCols ? OFS : ORS)
    }
}

, но это не дает мне выхода ... Что я (думаю, я) сделал:

  1. присваивать каждому значению заголовка столбца число

  2. итерацию в диапазоне

    2.1, если итератор = 0 -> печать столбца jdb

    2.2 если итератор <= номер столбца jdb -> вывести номер столбца iterator - 1

    2.3 если итератор> номер столбца jdb -> вывести номер столбца iterator

(это продолжение вопроса, который я поставил в /9360406/izvlechenie-stolbtsov-iz-faila-razdelennogo-tabulyatsiei)


КОНЕЦ РЕЗУЛЬТАТА

В итоге я использовал решение @Ed Morton:

$ cat move_to_first.awk
BEGIN { FS=OFS="\t" }
NR==1 {
    cols[++numCols] = tgt
    for (fldNr=1; fldNr<=NF; fldNr++) {
        f[$fldNr] = fldNr
        if ($fldNr != tgt) {
            cols[++numCols] = $fldNr
        }
    }
}
{
    for (colNr=1; colNr<=numCols; colNr++) {
        colName = cols[colNr]
        printf "%s%s", $(f[colName]), (colNr<numCols ? OFS : ORS)
    }
}

Для любопытства, чтобы переместить столбец в последнюю позицию, приведенный выше код просто нуждается в следующей модификации:

$ cat move_to_last.awk
BEGIN { 
    FS=OFS="\t" 
    }
NR==1 {
    for (fldNr=1; fldNr<=NF; fldNr++) {
        f[$fldNr] = fldNr
        if ($fldNr != target) {
            cols[++numCols] = $fldNr
        }
    }
    cols[++numCols] = target
}
{
    for (colNr=1; colNr<=numCols; colNr++) {
        colName = cols[colNr]
        printf "%s%s", $(f[colName]), (colNr<numCols ? OFS : ORS)
    }
}

Ответы [ 4 ]

1 голос
/ 17 мая 2019

Ну, я действительно надеялся на момент "научить человека ловить рыбу", но вы все равно получаете ответы, так что ... вот как настроить предыдущий ответ , чтобы сделать то, что вы сейчас хотите:

$ cat tst.awk
BEGIN { FS=OFS="\t" }
NR==1 {
    cols[++numCols] = tgt
    for (fldNr=1; fldNr<=NF; fldNr++) {
        f[$fldNr] = fldNr
        if ($fldNr != tgt) {
            cols[++numCols] = $fldNr
        }
    }
}
{
    for (colNr=1; colNr<=numCols; colNr++) {
        colName = cols[colNr]
        printf "%s%s", $(f[colName]), (colNr<numCols ? OFS : ORS)
    }
}

$ awk -v tgt=jdb -f tst.awk data.rdb
jdb     date    star    texp
2400000.23551544        2013-11-22      epsInd  100.
2400000.23551544        2013-11-22      epsInd  100.
2400000.23551544        2013-11-22      epsInd  100.
2400000.23551544        2013-11-22      HD217987        900.
2400000.23551544        2013-11-22      TOI-134 900.
2400000.23551544        2013-11-22      tauCet  60.
2400000.23551544        2013-11-22      BD+01316        300.
2400000.23551544        2013-11-22      BD+01316        300.
2400000.23551544        2013-11-22      BD+01316        300.
2400000.23551544        2013-11-22      BD+01316        300.

Обратите внимание, насколько простой цикл выполняется один раз для каждой строки ввода, где вы хотите, чтобы эффективность была, потому что все тяжелая работа по определению порядка вывода выполняется вNR==1 блок, который выполняется только один раз для всего файла.

В этом конкретном случае, когда вы на самом деле не заботитесь о других именах столбцов, вы можете написать его более кратко и эффективно, как:

$ cat tst.awk
BEGIN { FS=OFS="\t" }
NR==1 {
    numOutFlds = 1
    for (inFldNr=1; inFldNr<=NF; inFldNr++) {
        out2inFldNrs[$inFldNr == tgt ? 1 : ++numOutFlds] = inFldNr
    }
}
{
    for (outFldNr=1; outFldNr<=numOutFlds; outFldNr++) {
        inFldNr = out2inFldNrs[outFldNr]
        printf "%s%s", $inFldNr, (outFldNr<numOutFlds ? OFS : ORS)
    }
}

$ awk -v tgt=jdb -f tst.awk data.rdb
jdb     date    star    texp
2400000.23551544        2013-11-22      epsInd  100.
2400000.23551544        2013-11-22      epsInd  100.
2400000.23551544        2013-11-22      epsInd  100.
2400000.23551544        2013-11-22      HD217987        900.
2400000.23551544        2013-11-22      TOI-134 900.
2400000.23551544        2013-11-22      tauCet  60.
2400000.23551544        2013-11-22      BD+01316        300.
2400000.23551544        2013-11-22      BD+01316        300.
2400000.23551544        2013-11-22      BD+01316        300.
2400000.23551544        2013-11-22      BD+01316        300.
1 голос
/ 17 мая 2019

Это немного многословно, но оно выполняет свою работу:

awk 'NR==1{for(i=1;i<=NF;i++){if ($i=="jdb") break;}} {printf "%s\t",$i; for (j=1;j<=NF;j++){if (i!=j){printf j==NF||(j==NF-1&&j+1==i)?"%s\n":"%s\t", $j}}}' yourfile.txt

За прекрасное предложение Эда Мортона.Вот скрипт с правильными пробелами, отступами и переводами строки:

    NR == 1 {
            for (i = 1; i <= NF; i++) {
                    if ($i == "jdb") {
                            break
                    }
            }
    }

    {
            printf "%s\t", $i
            for (j = 1; j <= NF; j++) {
                    if (i != j) {
                            printf (j == NF || j == NF - 1 && j + 1 == i ? "%s\n" : "%s\t"), $j
                    }
            }
    }

Вы можете вставить это в свой собственный файл (скажем ... script.awk) и затем вызвать его: awk -f script.awk yourfile.txt

0 голосов
/ 17 мая 2019

В Perl вы можете воспользоваться библиотекой Text :: CSV_XS :

#! /usr/bin/perl
use warnings;
use strict;

use Text::CSV_XS;

open my $fh, '<', shift or die $!;

my $csv = 'Text::CSV_XS'->new({sep_char => "\t"});

my $row = $csv->getline($fh);

my ($jdb) = grep $row->[$_] eq 'jdb', 0 .. $#$row;

do {
    unshift @$row, splice @$row, $jdb, 1;
    $csv->say(*STDOUT, $row);
} while $row = $csv->getline($fh);
0 голосов
/ 17 мая 2019

Таким образом, задача состоит из двух частей:

  • сначала определите, какой столбец является столбцом, которым мы хотим быть первым
  • , затем измените порядок столбцов

Итак:

# our testing input file
cat <<EOF >file
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.
EOF

# my copy+paste messed up tabs with spaces, fix it
sed 's/[[:space:]]\+/\t/g' -i file


# first we need header count.
# I could remove all characters except tabs and use wc -c
# but was lazy, this will not affect performance anyway
hdrcnt=$(
    head -n1 file |
    tr '\t' '\n' |
    wc -l
)

# get the column number that has jdb
# I get the first line
# substitute tab with newlines
# and get the line number with "jdb"
num=$(
    head -n1 file |
    tr '\t' '\n' |
    grep -n jdb | 
    cut -d: -f1
)

# ten I generate the awk script
# so it's like '{print $num, $1, $2 ... except $num ... $hdrcnt }'
awkarg='{print $'"$num"', '"$(
    seq $hdrcnt |
    grep -v "$num" |
    sed 's/\(.*\)/$\1, /' |
    sed '$s/, //' |
    tr -d '\n'
)"'}'

# finally run awk
awk -vIFS='\t' -vOFS='\t' "$awkarg" file
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...