Эффективный способ транспонировать файл в Bash


110

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

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 в этом посте , но оно все еще слишком медленное. Есть мысли / предложения / блестящие идеи? :-)


12
Что заставляет вас думать, что существует сценарий bash, который будет работать быстрее, чем сценарий Perl? Это именно та проблема, в которой Perl преуспевает.
Марк Пим

1
@mark, если это чистый bash, это может быть быстрее, чем объединение всех этих инструментов cut / sed и т. д. вместе. Но опять же, если вы определите «bash», как в сочетании инструментов, тогда просто написание сценария awk будет сопоставимо с обработкой текста Perl wrt.
ghostdog74

Добавьте еще один, если вы не понимаете, насколько здесь Perl будет медленным. Медленно писать код? Медленно выполнять? Я искренне не люблю Perl, но он отлично справляется с задачами такого рода.
Кори Портер,

Если ваши столбцы / поля имеют фиксированный размер / ширину, вы можете использовать поиск файла Python, чтобы избежать чтения вашего файла в памяти. У вас есть фиксированные размеры / ширина столбцов / полей?
tommy.carstensen 07

2
Любой, кто думает, что сценарий оболочки будет быстрее, чем awk или perl, должен прочитать unix.stackexchange.com/questions/169716/…, чтобы понять, почему это не так.
Эд Мортон

Ответы:


115
awk '
{ 
    for (i=1; i<=NF; i++)  {
        a[NR,i] = $i
    }
}
NF>p { p = NF }
END {    
    for(j=1; j<=p; j++) {
        str=a[1,j]
        for(i=2; i<=NR; i++){
            str=str" "a[i,j];
        }
        print str
    }
}' file

вывод

$ more file
0 1 2
3 4 5
6 7 8
9 10 11

$ ./shell.sh
0 3 6 9
1 4 7 10
2 5 8 11

Производительность по сравнению с решением Perl от Джонатана в файле на 10000 строк

$ head -5 file
1 0 1 2
2 3 4 5
3 6 7 8
4 9 10 11
1 0 1 2

$  wc -l < file
10000

$ time perl test.pl file >/dev/null

real    0m0.480s
user    0m0.442s
sys     0m0.026s

$ time awk -f test.awk file >/dev/null

real    0m0.382s
user    0m0.367s
sys     0m0.011s

$ time perl test.pl file >/dev/null

real    0m0.481s
user    0m0.431s
sys     0m0.022s

$ time awk -f test.awk file >/dev/null

real    0m0.390s
user    0m0.370s
sys     0m0.010s

РЕДАКТИРОВАТЬ Эда Мортона (@ ghostdog74, не стесняйтесь удалять, если не одобряете).

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

$ cat tst.awk
BEGIN { FS=OFS="\t" }
{
    for (rowNr=1;rowNr<=NF;rowNr++) {
        cell[rowNr,NR] = $rowNr
    }
    maxRows = (NF > maxRows ? NF : maxRows)
    maxCols = NR
}
END {
    for (rowNr=1;rowNr<=maxRows;rowNr++) {
        for (colNr=1;colNr<=maxCols;colNr++) {
            printf "%s%s", cell[rowNr,colNr], (colNr < maxCols ? OFS : ORS)
        }
    }
}

$ awk -f tst.awk file
X       row1    row2    row3    row4
column1 0       3       6       9
column2 1       4       7       10
column3 2       5       8       11

Вышеупомянутые решения будут работать на любом awk (кроме старого, сломанного awk, конечно - там YMMV).

Вышеупомянутые решения действительно читают весь файл в память - если входные файлы слишком велики для этого, вы можете сделать это:

$ cat tst.awk
BEGIN { FS=OFS="\t" }
{ printf "%s%s", (FNR>1 ? OFS : ""), $ARGIND }
ENDFILE {
    print ""
    if (ARGIND < NF) {
        ARGV[ARGC] = FILENAME
        ARGC++
    }
}
$ awk -f tst.awk file
X       row1    row2    row3    row4
column1 0       3       6       9
column2 1       4       7       10
column3 2       5       8       11

который почти не использует память, но считывает входной файл один раз на количество полей в строке, поэтому он будет намного медленнее, чем версия, которая считывает весь файл в память. Также предполагается, что количество полей в каждой строке одинаково, и используется GNU awk для ENDFILEи, ARGINDно любой awk может делать то же самое с тестами на FNR==1и END.


А теперь также обрабатывать метки строк и столбцов?
Джонатан Леффлер,

ОК - вы правы; ваши образцы данных не соответствуют образцам данных вопроса, но ваш код отлично работает с образцами данных вопроса и дает требуемый результат (плюс-минус пустое значение или интервал табуляции). В основном моя ошибка.
Джонатан Леффлер,

Интересные тайминги - я согласен, вы видите прирост производительности в awk. Я использовал MacOS X 10.5.8, в которой не используется gawk; и я использовал Perl 5.10.1 (32-битная сборка). Я так понимаю, ваши данные были 10000 строк с 4 столбцами в строке? Во всяком случае, это не имеет большого значения; и awk, и perl являются жизнеспособными решениями (и решение awk более аккуратное - `` определенные '' проверки в моем Perl необходимы для предупреждения о свободных запусках при строгих / предупреждениях), и ни то, ни другое не является сутулым, и оба, вероятно, будут намного быстрее, чем оригинал решение сценария оболочки.
Джонатан Леффлер

На моей исходной матрице 2,2 ГБ решение perl немного быстрее, чем awk - 350,103 с против 369,410 с. Я использовал perl 5.8.8 64bit
Федерико Джорджи

1
@ zx8754, что максимальное количество полей применяется только к старому, не-POSIX awk. Возможно, невероятно неудачно названный "nawk". Это не относится к gawk или другим современным awk.
Эд Мортон

47

Другой вариант - использовать rs:

rs -c' ' -C' ' -T

-cизменяет разделитель входного столбца, -Cизменяет разделитель выходного столбца и меняет -Tместами строки и столбцы. Не используйте -tвместо -T, потому что он использует автоматически рассчитанное количество строк и столбцов, что обычно неверно. rs, названный в честь функции изменения формы в APL, поставляется с BSD и OS X, но должен быть доступен в диспетчерах пакетов на других платформах.

Второй вариант - использовать Ruby:

ruby -e'puts readlines.map(&:split).transpose.map{|x|x*" "}'

Третий вариант - использовать jq:

jq -R .|jq -sr 'map(./" ")|transpose|map(join(" "))[]'

jq -R .печатает каждую строку ввода как строковый литерал JSON, -s( --slurp) создает массив для строк ввода после анализа каждой строки как JSON, а -r( --raw-output) выводит содержимое строк вместо строковых литералов JSON. /Оператор перегружен для расщепленных строк.


3
Не знал rs- спасибо за указку! (Ссылка на Debian; апстрим выглядит как mirbsd.org/MirOS/dist/mir/rs )
tripleee

2
@lalebarde По крайней мере, в той реализации, rsкоторая поставляется с OS X, -cодна только вкладка устанавливает разделитель входных столбцов.
nisetama 05

2
@lalebarde, попробуйте цитирование ANSI-C в bash, чтобы получить символ табуляции:$'\t'
glenn jackman

3
Это крайний случай, но для очень большого файла с большим количеством строк, например TTC TTA TTC TTC TTT, выполнение rs -c' ' -C' ' -T < rows.seq > cols.seqдает rs: no memory: Cannot allocate memory. Это система под управлением FreeBSD 11.0-RELEASE с оперативной памятью 32 ГБ. Итак, я предполагаю, что это rsпомещает все в ОЗУ, что хорошо для скорости, но не для больших данных.
jrm 06

1
jq использовал 21 ГБ оперативной памяти для файла размером 766 МБ. Я убил его через 40 минут без вывода.
Glubbdrubb

30

Решение Python:

python -c "import sys; print('\n'.join(' '.join(c) for c in zip(*(l.split() for l in sys.stdin.readlines() if l.strip()))))" < input > output

Вышесказанное основано на следующем:

import sys

for c in zip(*(l.split() for l in sys.stdin.readlines() if l.strip())):
    print(' '.join(c))

Этот код предполагает, что в каждой строке одинаковое количество столбцов (заполнение не выполняется).


3
Одна небольшая проблема: заменить l.split()на l.strip().split()(Python 2.7), иначе последняя строка вывода будет повреждена. Работает для произвольных разделителей столбцов, используйте l.strip().split(sep)и, sep.join(c)если ваш разделитель хранится в переменной sep.
krlmlr 02

21

транспонирования проект SourceForge является coreutil типа C программа именно для этой цели .

gcc transpose.c -o transpose
./transpose -t input > output #works with stdin, too.

Спасибо за ссылку. Однако при работе с большими матрицами / файлами требуется слишком много памяти.
tommy.carstensen 08

она имеет аргументы в пользу и fieldsize размера блока: попробуйте изменить -bи -fаргументы.
летающая овца

Размер блока по умолчанию (--block или -b) составляет 10 КБ, а размер поля по умолчанию (--fieldmax или -f) равен 64, так что этого не может быть. Я попытался. Спасибо за предложение.
tommy.carstensen

1
Хорошо работал с CSV размером 2 ГБ.
Discusse

2
Для файла матрицы с размерами примерно 11k на 5k я обнаружил, что transpose.c примерно в 7 раз быстрее и примерно в 5 раз эффективнее с точки зрения памяти, чем первое awk-решение ghostdog74. Кроме того, я обнаружил, что awk-код "почти не использует память" от ghostdog74 не работает должным образом. Также обратите внимание на флаг --limit в программе transpose.c, который по умолчанию ограничивает вывод размером 1k на 1k.
ncemami

16

Чистый 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\t" ${array[$COUNTER]}
  done
  printf "\n" 
done

Это сработало для моего файла, хотя, что интересно, он распечатывает список каталогов для первой строки таблицы. Я не знаю достаточно BASH, чтобы понять, почему.
bugloaf

@bugloaf в углу вашего стола стоит *.
Hello71

2
@bugloaf: правильное цитирование переменных должно предотвратить это:printf "%s\t" "${array[$COUNTER]}"
Приостановлено до дальнейшего уведомления.


9

Вот умеренно надежный Perl-скрипт для этой работы. Есть много структурных аналогий с решением @ ghostdog74 awk.

#!/bin/perl -w
#
# SO 1729824

use strict;

my(%data);          # main storage
my($maxcol) = 0;
my($rownum) = 0;
while (<>)
{
    my(@row) = split /\s+/;
    my($colnum) = 0;
    foreach my $val (@row)
    {
        $data{$rownum}{$colnum++} = $val;
    }
    $rownum++;
    $maxcol = $colnum if $colnum > $maxcol;
}

my $maxrow = $rownum;
for (my $col = 0; $col < $maxcol; $col++)
{
    for (my $row = 0; $row < $maxrow; $row++)
    {
        printf "%s%s", ($row == 0) ? "" : "\t",
                defined $data{$row}{$col} ? $data{$row}{$col} : "";
    }
    print "\n";
}

С размером данных выборки разница в производительности между perl и awk была незначительной (1 миллисекунда из семи). При большем наборе данных (матрица 100x100, элементы по 6-8 символов) perl немного превзошел awk - 0,026 с против 0,042 с. Ни то, ни другое вряд ли будет проблемой.


Типичные тайминги для Perl 5.10.1 (32-разрядная версия) vs awk (версия 20040207 при задании -V) vs gawk 3.1.7 (32-разрядная версия) на MacOS X 10.5.8 для файла, содержащего 10000 строк с 5 столбцами в каждом линия:

Osiris JL: time gawk -f tr.awk xxx  > /dev/null

real    0m0.367s
user    0m0.279s
sys 0m0.085s
Osiris JL: time perl -f transpose.pl xxx > /dev/null

real    0m0.138s
user    0m0.128s
sys 0m0.008s
Osiris JL: time awk -f tr.awk xxx  > /dev/null

real    0m1.891s
user    0m0.924s
sys 0m0.961s
Osiris-2 JL: 

Обратите внимание, что gawk на этой машине намного быстрее, чем awk, но все же медленнее, чем perl. Ясно, что ваш пробег будет другим.


в моей системе gawk превосходит perl. Вы можете увидеть мои результаты в моем отредактированном сообщении
ghostdog74

4
Сделан вывод: другая платформа, другая версия ПО, разные результаты.
ghostdog74

6

Если вы scустановили, вы можете:

psc -r < inputfile | sc -W% - > outputfile

4
Обратите внимание, что это поддерживает ограниченное количество строк, потому что scимена столбцов являются одним или комбинацией двух символов. Предел есть 26 + 26^2 = 702.
Тор


5

Предполагая, что все ваши строки имеют одинаковое количество полей, эта программа awk решает проблему:

{for (f=1;f<=NF;f++) col[f] = col[f]":"$f} END {for (f=1;f<=NF;f++) print col[f]}

Проще говоря, по мере того, как вы перебираете строки, для каждого поля fвырастает разделенная ':' строка, col[f]содержащая элементы этого поля. После того, как вы закончите со всеми строками, распечатайте каждую из этих строк в отдельной строке. Затем вы можете заменить нужный разделитель (например, пробел) ':', пропустив вывод черезtr ':' ' ' .

Пример:

$ echo "1 2 3\n4 5 6"
1 2 3
4 5 6

$ echo "1 2 3\n4 5 6" | awk '{for (f=1;f<=NF;f++) col[f] = col[f]":"$f} END {for (f=1;f<=NF;f++) print col[f]}' | tr ':' ' '
 1 4
 2 5
 3 6

5

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

datamash -W transpose infile > outfile

3

Хакерское решение 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;

использование файлов вставки и временных файлов - это просто лишние ненужные операции. вы можете просто манипулировать внутри самой памяти, например, с массивами / хешами
ghostdog74

2
Да, но разве это не означало бы хранить все в памяти? Файлы, с которыми я имею дело, имеют размер около 2-20 ГБ.
Федерико Джорджи

3

Единственное улучшение, которое я вижу в вашем собственном примере, - это использование 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

Обычно я использую этот небольшой 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количество столбцов в исходном файле, чтобы использовать его в качестве количества строк для обратной печати.


2

Я использовал решение 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

Я просто искал аналогичную транпозицию bash, но с поддержкой заполнения. Вот сценарий, который я написал на основе решения 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

Я искал решение для транспонирования любой матрицы (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

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

head -$N file | tail -1 | tr ',' '\n'

2

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

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

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


2

Другое 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

2

Некоторые стандартные утилиты * nix однострочные, временные файлы не нужны. NB: OP хотел эффективное исправление (то есть быстрее), и основные ответы обычно быстрее, чем этот ответ. Эти однострочные сообщения для тех, кто любит * nix по каким-либо причинам программные инструменты . В редких случаях ( например, нехватка ввода-вывода и памяти) эти фрагменты могут быть быстрее, чем некоторые из основных ответов.

Вызовите входной файл foo .

  1. Если мы знаем, что у foo четыре столбца:

    for f in 1 2 3 4 ; do cut -d ' ' -f $f foo | xargs echo ; done
  2. Если мы не знаем, сколько столбцов имеет foo :

    n=$(head -n 1 foo | wc -w)
    for f in $(seq 1 $n) ; do cut -d ' ' -f $f foo | xargs echo ; done

    xargsимеет ограничение по размеру и, следовательно, будет неполноценной работой с длинным файлом. Какой предел размера зависит от системы, например:

    { timeout '.01' xargs --show-limits ; } 2>&1 | grep Max

    Максимальная длина команды, которую мы можем использовать: 2088944

  3. tr& echo:

    for f in 1 2 3 4; do cut -d ' ' -f $f foo | tr '\n\ ' ' ; echo; done

    ... или если количество столбцов неизвестно:

    n=$(head -n 1 foo | wc -w)
    for f in $(seq 1 $n); do 
        cut -d ' ' -f $f foo | tr '\n' ' ' ; echo
    done
  4. Использование set, которое нравится xargs, имеет аналогичные ограничения на размер командной строки:

    for f in 1 2 3 4 ; do set - $(cut -d ' ' -f $f foo) ; echo $@ ; done

2
Все они будут на порядки медленнее, чем решения awk или perl, и хрупки. Прочтите unix.stackexchange.com/questions/169716/… .
Эд Мортон

@EdMorton, спасибо, квалифицированное вступление к моему ответу, касающееся ваших проблем со скоростью. Re "хрупкие": не 3) , и ни другие, когда программист знает, что данные безопасны для данной техники; и разве код оболочки, совместимый с POSIX, не является более стабильным стандартом, чем perl ?
agc

извините, я много о Perl. В этом случае инструмент для использования будет awk. cut, headНе, echoи т.д., не более POSIX совместимого кода оболочки , чем awkсценарий - все они являются стандартными для каждой установки UNIX. Просто нет причин использовать набор инструментов, которые в сочетании требуют, чтобы вы были осторожны с содержимым вашего входного файла и каталога, из которого вы выполняете скрипт, когда вы можете просто использовать awk, и конечный результат будет быстрее и надежнее .
Эд Мортон

Пожалуйста, я не анти- awk , но условия меняются. Причина №1: for f in cut head xargs seq awk ; do wc -c $(which $f) ; done Когда объем памяти слишком медленный или объем операций ввода-вывода слишком низкий, более крупные интерпретаторы ухудшают ситуацию, независимо от того, насколько хороши они были бы в более идеальных условиях. Причина № 2: awk (или почти любой другой язык) также страдает более крутой кривой обучения, чем небольшая утилита, предназначенная для того, чтобы хорошо выполнять одну задачу. Когда время выполнения дешевле, чем человеко-часы кодера, простое программирование с помощью «программных инструментов» экономит деньги.
AGC

1
#!/bin/bash

aline="$(head -n 1 file.txt)"
set -- $aline
colNum=$#

#set -x
while read line; do
  set -- $line
  for i in $(seq $colNum); do
    eval col$i="\"\$col$i \$$i\""
  done
done < file.txt

for i in $(seq $colNum); do
  eval echo \${col$i}
done

другая версия с set eval


Прочтите unix.stackexchange.com/questions/169716/…, чтобы понять некоторые, но не все, проблемы с этим решением.
Эд Мортон

1

Другой вариант bash

$ cat file 
XXXX    col1    col2    col3
row1    0       1       2
row2    3       4       5
row3    6       7       8
row4    9       10      11

Сценарий

#!/bin/bash

I=0
while read line; do
    i=0
    for item in $line; { printf -v A$I[$i] $item; ((i++)); }
    ((I++))
done < file
indexes=$(seq 0 $i)

for i in $indexes; {
    J=0
    while ((J<I)); do
        arr="A$J[$i]"
        printf "${!arr}\t"
        ((J++))
    done
    echo
}

Вывод

$ ./test 
XXXX    row1    row2    row3    row4    
col1    0       3       6       9   
col2    1       4       7       10  
col3    2       5       8       11

0

Вот решение для Haskell. При компиляции с -O2 он работает немного быстрее, чем awk ghostdog, и немного медленнее, чем тонко обернутый c python Стефана на моей машине для повторяющихся строк ввода «Hello world». К сожалению, GHC не поддерживает передачу кода командной строки, насколько я могу судить, поэтому вам придется записать его в файл самостоятельно. Он усекает строки до длины самой короткой строки.

transpose :: [[a]] -> [[a]]
transpose = foldr (zipWith (:)) (repeat [])

main :: IO ()
main = interact $ unlines . map unwords . transpose . map words . lines

0

Решение awk, которое хранит весь массив в памяти

    awk '$0!~/^$/{    i++;
                  split($0,arr,FS);
                  for (j in arr) {
                      out[i,j]=arr[j];
                      if (maxr<j){ maxr=j}     # max number of output rows.
                  }
            }
    END {
        maxc=i                 # max number of output columns.
        for     (j=1; j<=maxr; j++) {
            for (i=1; i<=maxc; i++) {
                printf( "%s:", out[i,j])
            }
            printf( "%s\n","" )
        }
    }' infile

Но мы можем «обходить» файл столько раз, сколько потребуется выходных строк:

#!/bin/bash
maxf="$(awk '{if (mf<NF); mf=NF}; END{print mf}' infile)"
rowcount=maxf
for (( i=1; i<=rowcount; i++ )); do
    awk -v i="$i" -F " " '{printf("%s\t ", $i)}' infile
    echo
done

Что (для небольшого количества строк вывода быстрее, чем в предыдущем коде).


0

Вот однострочник Bash, который основан на простом преобразовании каждой строки в столбец и pasteобъединении их вместе:

echo '' > tmp1;  \
cat m.txt | while read l ; \
            do    paste tmp1 <(echo $l | tr -s ' ' \\n) > tmp2; \
                  cp tmp2 tmp1; \
            done; \
cat tmp1

m.txt:

0 1 2
4 5 6
7 8 9
10 11 12
  1. создает tmp1файл, поэтому он не пустой.

  2. читает каждую строку и преобразует ее в столбец, используя tr

  3. вставляет новый столбец в tmp1файл

  4. копирует результат обратно в tmp1.

PS: Я действительно хотел использовать io-дескрипторы, но не мог заставить их работать.


Не забудьте установить будильник, если вы собираетесь запускать его для большого файла. Прочтите unix.stackexchange.com/questions/169716/…, чтобы понять некоторые, но не все, проблемы с этим подходом.
Эд Мортон

0

Один лайнер с использованием R ...

  cat file | Rscript -e "d <- read.table(file('stdin'), sep=' ', row.names=1, header=T); write.table(t(d), file=stdout(), quote=F, col.names=NA) "

0

Ранее я использовал два сценария для выполнения аналогичных операций. Первый находится в awk, что намного быстрее, чем второй, который находится в "чистом" bash. Возможно, вы сможете адаптировать его к своему собственному приложению.

awk '
{
    for (i = 1; i <= NF; i++) {
        s[i] = s[i]?s[i] FS $i:$i
    }
}
END {
    for (i in s) {
        print s[i]
    }
}' file.txt
declare -a arr

while IFS= read -r line
do
    i=0
    for word in $line
    do
        [[ ${arr[$i]} ]] && arr[$i]="${arr[$i]} $word" || arr[$i]=$word
        ((i++))
    done
done < file.txt

for ((i=0; i < ${#arr[@]}; i++))
do
    echo ${arr[i]}
done
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.