Эффективный способ транспонировать файл в Bash - PullRequest
102 голосов
/ 13 ноября 2009

У меня огромный разделенный табуляцией файл, отформатированный так:

X column1 column2 column3
row1 0 1 2
row2 3 4 5
row3 6 7 8
row4 9 10 11

Я хотел бы транспонировать эффективным способом, используя только команды bash (для этого я мог бы написать Perl-скрипт длиной около десяти строк, но он должен выполняться медленнее, чем встроенные функции bash). ). Таким образом, вывод должен выглядеть как

X row1 row2 row3 row4
column1 0 3 6 9
column2 1 4 7 10
column3 2 5 8 11

Я думал о таком решении

cols=`head -n 1 input | wc -w`
for (( i=1; i <= $cols; i++))
do cut -f $i input | tr $'\n' $'\t' | sed -e "s/\t$/\n/g" >> output
done

Но это медленно и не кажется самым эффективным решением. Я видел решение для vi в этом посте , но оно все еще слишком медленное. Есть мысли / предложения / блестящие идеи? : -)

Ответы [ 25 ]

4 голосов
/ 07 сентября 2017

GNU datamash идеально подходит для этой проблемы, имея только одну строку кода и потенциально произвольно большой размер файла!

datamash -W transpose infile > outfile
3 голосов
/ 12 мая 2015

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

  awk '{for (i=1; i<=NF; i++) a[i,NR]=$i
        max=(max<NF?NF:max)}
        END {for (i=1; i<=max; i++)
              {for (j=1; j<=NR; j++) 
                  printf "%s%s", a[i,j], (j==NR?RS:FS)
              }
        }' file

Это просто загружает все данные в двумерный массив a[line,column] и затем печатает их обратно как a[column,line], так что он транспонирует заданный вход.

Это необходимо для отслеживания количества столбцов max imum, которое имеется в исходном файле, чтобы оно использовалось в качестве количества строк для обратной печати. ​​

3 голосов
/ 13 ноября 2009

Единственное улучшение, которое я вижу в вашем собственном примере, - это использование awk, которое уменьшит количество запущенных процессов и объем данных, передаваемых между ними:

/bin/rm output 2> /dev/null

cols=`head -n 1 input | wc -w` 
for (( i=1; i <= $cols; i++))
do
  awk '{printf ("%s%s", tab, $'$i'); tab="\t"} END {print ""}' input
done >> output
3 голосов
/ 13 ноября 2009

Может быть таким хакерским решением Perl. Это хорошо, потому что он не загружает весь файл в память, печатает промежуточные временные файлы, а затем использует замечательную вставку

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

my $counter;
open INPUT, "<$ARGV[0]" or die ("Unable to open input file!");
while (my $line = <INPUT>) {
    chomp $line;
    my @array = split ("\t",$line);
    open OUTPUT, ">temp$." or die ("unable to open output file!");
    print OUTPUT join ("\n",@array);
    close OUTPUT;
    $counter=$.;
}
close INPUT;

# paste files together
my $execute = "paste ";
foreach (1..$counter) {
    $execute.="temp$counter ";
}
$execute.="> $ARGV[1]";
system $execute;
2 голосов
/ 30 января 2014

Я просто искал похожий bash tranpose, но с поддержкой padding. Вот сценарий, который я написал на основе решения fgm, который, кажется, работает. Если это может быть полезным ...

#!/bin/bash 
declare -a array=( )                      # we build a 1-D-array
declare -a ncols=( )                      # we build a 1-D-array containing number of elements of each row

SEPARATOR="\t";
PADDING="";
MAXROWS=0;
index=0
indexCol=0
while read -a line; do
    ncols[$indexCol]=${#line[@]};
((indexCol++))
if [ ${#line[@]} -gt ${MAXROWS} ]
    then
         MAXROWS=${#line[@]}
    fi    
    for (( COUNTER=0; COUNTER<${#line[@]}; COUNTER++ )); do
        array[$index]=${line[$COUNTER]}
        ((index++))

    done
done < "$1"

for (( ROW = 0; ROW < MAXROWS; ROW++ )); do
  COUNTER=$ROW;
  for (( indexCol=0; indexCol < ${#ncols[@]}; indexCol++ )); do
if [ $ROW -ge ${ncols[indexCol]} ]
    then
      printf $PADDING
    else
  printf "%s" ${array[$COUNTER]}
fi
if [ $((indexCol+1)) -lt ${#ncols[@]} ]
then
  printf $SEPARATOR
    fi
    COUNTER=$(( COUNTER + ncols[indexCol] ))
  done
  printf "\n" 
done
2 голосов
/ 06 ноября 2014

Если вы хотите извлечь из файла только одну строку (разделенную запятыми) $ N и превратить ее в столбец:

head -$N file | tail -1 | tr ',' '\n'
2 голосов
/ 06 августа 2014

Я искал решение для транспонирования любой матрицы (nxn или mxn) с любыми данными (числами или данными) и получил следующее решение:

Row2Trans=number1
Col2Trans=number2

for ((i=1; $i <= Line2Trans; i++));do
    for ((j=1; $j <=Col2Trans ; j++));do
        awk -v var1="$i" -v var2="$j" 'BEGIN { FS = "," }  ; NR==var1 {print $((var2)) }' $ARCHIVO >> Column_$i
    done
done

paste -d',' `ls -mv Column_* | sed 's/,//g'` >> $ARCHIVO
2 голосов
/ 07 мая 2014

Не очень элегантно, но эта «однострочная» команда быстро решает проблему:

cols=4; for((i=1;i<=$cols;i++)); do \
            awk '{print $'$i'}' input | tr '\n' ' '; echo; \
        done

Здесь столбцы - это количество столбцов, где вы можете заменить 4 на head -n 1 input | wc -w.

2 голосов
/ 22 марта 2010

Я использовал решение fgm (спасибо fgm!), Но мне нужно было удалить символы табуляции в конце каждой строки, поэтому изменил скрипт так:

#!/bin/bash 
declare -a array=( )                      # we build a 1-D-array

read -a line < "$1"                       # read the headline

COLS=${#line[@]}                          # save number of columns

index=0
while read -a line; do
    for (( COUNTER=0; COUNTER<${#line[@]}; COUNTER++ )); do
        array[$index]=${line[$COUNTER]}
        ((index++))
    done
done < "$1"

for (( ROW = 0; ROW < COLS; ROW++ )); do
  for (( COUNTER = ROW; COUNTER < ${#array[@]}; COUNTER += COLS )); do
    printf "%s" ${array[$COUNTER]}
    if [ $COUNTER -lt $(( ${#array[@]} - $COLS )) ]
    then
        printf "\t"
    fi
  done
  printf "\n" 
done
2 голосов
/ 19 сентября 2018

Другое решение awk и ограниченный ввод с объемом имеющейся у вас памяти.

awk '{ for (i=1; i<=NF; i++) RtoC[i]= (RtoC[i]? RtoC[i] FS $i: $i) }
    END{ for (i in RtoC) print RtoC[i] }' infile

Это объединяет все позиции с указанным номером и в END выводит результат, который будет первой строкой в ​​первом столбце, второй строкой во втором столбце и т. Д. Будет выводить:

X row1 row2 row3 row4
column1 0 3 6 9
column2 1 4 7 10
column3 2 5 8 11
...