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

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

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


person Federico Giorgi    schedule 13.11.2009    source источник
comment
Что заставляет вас думать, что существует сценарий bash, который будет быстрее, чем сценарий Perl? Это именно та проблема, в которой Perl преуспевает.   -  person Mark Pim    schedule 13.11.2009
comment
@mark, если это чистый bash, это может быть быстрее, чем объединение всех этих инструментов cut / sed и т. д. вместе. Но опять же, если вы определяете bash как комбинирующие инструменты, тогда просто написание awk-скрипта будет сравнимо с обработкой текста Perl wrt.   -  person ghostdog74    schedule 13.11.2009
comment
Добавьте еще один, если вы не понимаете, насколько здесь Perl будет медленным. Медленно писать код? Медленно выполнять? Я искренне не люблю Perl, но он отлично справляется с задачами такого рода.   -  person Corey Porter    schedule 13.11.2009
comment
Если ваши столбцы / поля имеют фиксированный размер / ширину, вы можете использовать поиск файла Python, чтобы избежать чтения вашего файла в памяти. У вас есть фиксированные размеры / ширина столбцов / полей?   -  person tommy.carstensen    schedule 08.04.2013
comment
Любой, кто думает, что сценарий оболочки будет быстрее, чем awk или perl, должен прочитать unix.stackexchange.com/questions/169716/, чтобы они могли понять, почему это не так .   -  person Ed Morton    schedule 10.04.2016


Ответы (29)


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.

person ghostdog74    schedule 13.11.2009
comment
А теперь также обрабатывать метки строк и столбцов? - person Jonathan Leffler; 13.11.2009
comment
ОК - вы правы; ваши данные образца не соответствуют образцу данных вопроса, но ваш код отлично работает с образцом данных вопроса и дает требуемый результат (плюс-минус пустое значение или интервал табуляции). В основном моя ошибка. - person Jonathan Leffler; 13.11.2009
comment
Интересные тайминги - я согласен, вы видите выигрыш в производительности в awk. Я использовал MacOS X 10.5.8, в котором не используется gawk; и я использовал Perl 5.10.1 (32-битная сборка). Я так понимаю, ваши данные были 10000 строк с 4 столбцами в строке? Во всяком случае, это не имеет большого значения; и awk, и perl являются жизнеспособными решениями (и решение awk более аккуратное - `` определенные '' проверки в моем Perl необходимы для предупреждения о свободных запусках при строгих / предупреждениях), и ни то, ни другое не является сутулым, и оба, вероятно, будут намного быстрее, чем исходный решение сценария оболочки. - person Jonathan Leffler; 16.11.2009
comment
На моей исходной матрице 2,2 ГБ решение perl немного быстрее, чем awk - 350,103 с против 369,410 с. Я использовал perl 5.8.8 64 бит. - person Federico Giorgi; 16.11.2009
comment
Я использую gawk 3.16a, Perl 5.10.0. - person ghostdog74; 16.11.2009
comment
Каковы требования к памяти для каждого из двух методов в матрице / файле размером 2,2 ГБ? - person tommy.carstensen; 08.04.2013
comment
Какие решения вы в конечном итоге использовали? - person tommy.carstensen; 08.04.2013
comment
Результат можно передать через | column -t, чтобы его было легче читать. - person Vytenis Bivainis; 25.05.2014
comment
Примечание: awk has maximum number of fields size=32767. - person zx8754; 05.06.2014
comment
Привет, что означает эта строка NF ›p {p = NF}? Я не поняла ... большое спасибо - person keypoint; 03.10.2015
comment
Красиво, но вы, возможно, захотите указать таким людям, как я, не занимающимся AWKK, что в test.awk находится бит между кавычками! - person daknowles; 03.04.2016
comment
Мне действительно нравится решение awk, но есть ли простой способ передать вывод find или как-то передать сразу несколько файлов и вывести их все вместе транспонированные результаты? - person user5359531; 05.04.2016
comment
@ tommy.carstensen требования к памяти одинаковы для обоих решений, поскольку они оба читают весь файл в память перед печатью в новом порядке - person Ed Morton; 10.04.2016
comment
@ zx8754, что максимальное количество полей применяется только к старому, не-POSIX awk. Возможно, невероятно неудачно названный nawk. Это не относится к gawk или другим современным awk. - person Ed Morton; 10.04.2016
comment
@keypoint NF>p { p = NF } определяет максимальное количество полей во всех строках файла на тот случай, если не все строки имеют одинаковое количество полей, поэтому инструмент может позже распечатать максимальное количество строк. - person Ed Morton; 10.04.2016
comment
@ user5359531 просто укажите в командной строке все интересующие вас файлы: awk '...' file1 file2 ... fileN. - person Ed Morton; 10.04.2016
comment
Я использовал ваш первый awk код, но после транспонирования ведущие нули из данных 1-го столбца исчезли. Любая идея? - person Sigur; 08.04.2017
comment
Ваше awk решение работало намного быстрее, чем GNU datamash с очень большим файлом, который не помещался в памяти. - person Alex Reynolds; 19.03.2019
comment
geeksforgeeks.org/awk-command-unixlinux-examples один из лучших статья @geeksforgeeks, чтобы узнать о команде awk - person jerinisready; 13.05.2021
comment
@jerinisready в этой статье есть несколько ошибок и плохих советов, и то, что она делает правильно, - это только абсолютные основы, которые ясны из страниц руководства, поэтому лучше избегать этого. Чтобы узнать об awk, прочтите книгу Арнольда Роббинса «Эффективное программирование на AWK», 5-е издание. - person Ed Morton; 17.07.2021

rs

rs поставляется с BSD и macOS, но его можно получить в менеджерах пакетов на других платформах. Он назван в честь функции изменения формы в APL.

Используйте последовательности пробелов и табуляции в качестве разделителя столбцов:

rs -T

Использовать табуляцию как разделитель столбцов:

rs -c -C -T

Используйте запятую в качестве разделителя столбцов:

rs -c, -C, -T

-c изменяет разделитель входного столбца, а -C изменяет разделитель выходного столбца. Только -c или -C устанавливает разделитель табуляции. -T меняет местами строки и столбцы.

Не используйте -t вместо -T, потому что он использует автоматически выбранное количество выходных столбцов, которое обычно не будет правильным, потому что оно выбрано так, чтобы выходные строки заполняли ширину дисплея (которая по умолчанию составляет 80 символов, но которая можно изменить с помощью -w).

Когда разделитель выходных столбцов указан с помощью -C, в конец каждой строки добавляется дополнительный символ разделителя столбцов, но вы можете удалить его следующим образом:

$ seq 4|paste -d, - -|rs -c, -C, -T
1,3,
2,4,
$ seq 4|paste -d, - -|rs -c, -C, -T|sed 's/.$//'
1,3
2,4

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

$ rs -C, -c, -T<<<$'1,\n3,4'
1,3,4,

таращиться

$ seq 4|paste -d, - -|awk '{for(i=1;i<=NF;i++)a[i][NR]=$i}END{for(i in a)for(j in a[i])printf"%s"(j==NR?"\n":FS),a[i][j]}' FS=,
1,3
2,4

При этом используются массивы массивов, что является особенностью gawk. macOS поставляется с версией nawk от 2007 года, которая не поддерживает массивы массивов.

Чтобы использовать пробел в качестве разделителя без сворачивания последовательностей символов пробела и табуляции, используйте FS='[ ]'.

Рубин

$ seq 4|paste -d, - -|ruby -e'STDIN.map{|x|x.chomp.split(",",-1)}.transpose.each{|x|puts x*","}'
1,3
2,4

Аргумент -1 для split запрещает отбрасывание пустых полей в конце:

$ ruby -e'p"a,,".split(",")'
["a"]
$ ruby -e'p"a,,".split(",",-1)'
["a", "", ""]

Функциональная форма:

$ tp(){ ruby -e's=ARGV[0];STDIN.map{|x|x.chomp.split(s==" "?/ /:s,-1)}.transpose.each{|x|puts x*s}' -- "${1-$'\t'}";}
$ seq 4|paste -d, - -|tp ,
1,3
2,4

s==" "?/ /:s используется выше, потому что, когда аргумент функции split является одним пробелом, он включает специальное поведение, подобное awk, при котором строки разделяются на основе непрерывных пробелов и табуляций:

$ ruby -e'p" a  \tb ".split(/ /,-1)'
["", "a", "", "\tb", ""]
$ ruby -e'p" a  \tb ".split(" ",-1)'
["a", "b", ""]

jq

tp(){ jq -R .|jq --arg x "${1-$'\t'}" -sr 'map(./$x)|transpose|map(join($x))[]';}

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

person nisetama    schedule 11.05.2015
comment
Я не был знаком с rs - спасибо за указатель! (Ссылка на Debian; апстрим выглядит как mirbsd.org/MirOS/dist / mir / rs) - person tripleee; 26.11.2015
comment
Похоже, он не может использовать табуляцию в качестве разделителя. Я пробовал '\ t' и '^ t'. Это не задокументировано. - person lalebarde; 27.02.2016
comment
@lalebarde По крайней мере, в реализации rs, которая идет с OS X, только -c устанавливает разделитель входных столбцов на табуляцию. - person nisetama; 05.03.2016
comment
@lalebarde, попробуйте цитирование ANSI-C в bash получить символ табуляции: $'\t' - person glenn jackman; 10.04.2016
comment
Это сработало для меня при переносе файла с разделителями табуляции: rs -c$'\t' -C$'\t' -T - person Nathan S. Watson-Haigh; 22.11.2016
comment
Это крайний случай, но для очень большого файла с большим количеством строк, таких как 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 помещает все в ОЗУ, что хорошо для скорости, но не для больших данных. - person jrm; 06.07.2017
comment
jq использовал 21 ГБ оперативной памяти для файла размером 766 МБ. Я убил его через 40 минут без вывода. - person Glubbdrubb; 20.03.2018
comment
@jrm Ваше предположение верное, об этом сказано в руководстве: BUGS The algorithm currently reads the whole file into memory, so files that do not fit in memory will not be reshaped. - person simlev; 29.08.2018

Решение 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))

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

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

проект transpose на sourceforge - это программа C, подобная coreutil, именно для этого.

gcc transpose.c -o transpose
./transpose -t input > output #works with stdin, too.
person flying sheep    schedule 08.02.2013
comment
Спасибо за ссылку. Однако при работе с большими матрицами / файлами требуется слишком много памяти. - person tommy.carstensen; 08.04.2013
comment
у него есть аргументы для размера блока и размера поля: попробуйте настроить аргументы -b и -f. - person flying sheep; 08.04.2013
comment
Размер блока по умолчанию (--block или -b) составляет 10 КБ, а размер поля по умолчанию (--fieldmax или -f) равен 64, так что этого не может быть. Я пытался. Спасибо за предложение. - person tommy.carstensen; 10.04.2013
comment
Хорошо работал с CSV размером 2 ГБ. - person discipulus; 08.11.2016
comment
Для файла матрицы с размерами примерно 11k на 5k я обнаружил, что transpose.c в ~ 7 раз быстрее и в ~ 5 раз эффективнее с точки зрения памяти, чем первое awk-решение ghostdog74. Кроме того, я обнаружил, что awk-код от ghostdog74 почти не использует память, не работает должным образом. Также обратите внимание на флаг --limit в программе transpose.c, который по умолчанию ограничивает вывод размером 1k на 1k. - person ncemami; 28.11.2016
comment
Если вам не нравится sourceforge или он не работает, я сделал зеркало github проекта . - person jan-glx; 04.03.2018

Взгляните на код данных GNU, который можно использовать как datamash transpose. В будущей версии также будет поддерживаться кросс-табуляция (сводные таблицы).

Вот как это сделать с столбцами, разделенными пробелами:

datamash transpose -t ' ' < file > transposed_file
person pixelbeat    schedule 07.01.2016

Чистый 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
person Fritz G. Mehner    schedule 19.11.2009
comment
Это сработало для моего файла, хотя, что интересно, он распечатывает список каталогов для первой строки таблицы. Я не знаю достаточно BASH, чтобы понять, почему. - person bugloaf; 19.06.2013
comment
@bugloaf в углу вашего стола стоит *. - person Hello71; 28.08.2014
comment
@bugloaf: правильное цитирование переменных должно предотвратить это: printf "%s\t" "${array[$COUNTER]}" - person Dennis Williamson; 26.11.2014

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

datamash -W transpose infile > outfile
person Pal    schedule 07.09.2017

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

#!/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. Ясно, что ваш пробег будет другим.

person Jonathan Leffler    schedule 14.11.2009
comment
в моей системе gawk превосходит perl. вы можете увидеть мои результаты в моем отредактированном сообщении - person ghostdog74; 16.11.2009
comment
Сделан вывод: другая платформа, другая версия ПО, разные результаты. - person ghostdog74; 16.11.2009

Для этого есть специальная утилита,

Утилита для кэширования данных GNU

apt install datamash  

datamash transpose < yourfile

Взято с этого сайта, https://www.gnu.org/software/datamash/ и http://www.thelinuxrain.com/articles/transposing-rows-and-columns-3-methods

person nelaaro    schedule 07.04.2017

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

psc -r < inputfile | sc -W% - > outputfile
person Dennis Williamson    schedule 13.11.2009
comment
Обратите внимание, что это поддерживает ограниченное количество строк, потому что sc называет свои столбцы одним или комбинацией двух символов. Предел составляет 26 + 26^2 = 702. - person Thor; 08.11.2012

Предполагая, что все ваши строки имеют одинаковое количество полей, эта 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
person Guilherme Freitas    schedule 10.06.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], так что он транспонирует данный ввод.

При этом необходимо отслеживать maximum количество столбцов в исходном файле, чтобы оно использовалось в качестве количества строк для обратной печати.

person fedorqui 'SO stop harming'    schedule 12.05.2015

Хакерское решение 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;
person Federico Giorgi    schedule 13.11.2009
comment
использование файлов вставки и временных файлов - это просто лишние ненужные операции. вы можете просто манипулировать внутри самой памяти, например, массивы / хэши - person ghostdog74; 13.11.2009
comment
Ага, но разве это не означало бы хранить все в памяти? Файлы, с которыми я имею дело, имеют размер около 2-20 ГБ. - person Federico Giorgi; 16.11.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
person Simon C    schedule 13.11.2009

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

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

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

    for f in 1 2 3 4 ; do cut -d ' ' -f $f foo | xargs echo ; done
    
  2. # P4 #
    n=$(head -n 1 foo | wc -w)
    for f in $(seq 1 $n) ; do cut -d ' ' -f $f foo | xargs echo ; done
    
    # P5 #
    { timeout '.01' xargs --show-limits ; } 2>&1 | grep Max
    
    # P6 #
  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
    
person agc    schedule 10.04.2016
comment
Все они будут на порядки медленнее, чем решения awk или perl, и хрупки. Прочтите unix.stackexchange.com/questions/169716/. - person Ed Morton; 10.04.2016
comment
@EdMorton, спасибо, квалифицированное вступление к моему ответу на ваши проблемы со скоростью. Re fragile: ни 3), ни другие, когда программист знает, что данные безопасны для данной техники; и разве код оболочки, совместимый с POSIX, не является более стабильным стандартом, чем perl? - person agc; 10.04.2016
comment
извините, я много о Perl. В этом случае инструментом будет awk. cut, head, echo и т. Д. Являются не более POSIX-совместимым кодом оболочки, чем awk скрипт - все они являются стандартными для каждой установки UNIX. Просто нет причин использовать набор инструментов, которые в сочетании требуют, чтобы вы были осторожны с содержимым вашего входного файла и каталога, из которого вы выполняете скрипт, когда вы можете просто использовать awk, и конечный результат будет быстрее и надежнее . - person Ed Morton; 10.04.2016
comment
Пожалуйста, я не против awk, но условия меняются. Причина №1: for f in cut head xargs seq awk ; do wc -c $(which $f) ; done Когда память слишком медленная или объем операций ввода-вывода слишком мал, более крупные интерпретаторы ухудшают ситуацию, независимо от того, насколько хороши они были бы в более идеальных условиях. Причина № 2: awk (или почти любой другой язык) также страдает более крутой кривой обучения, чем небольшая утилита, предназначенная для того, чтобы хорошо выполнять одну задачу. Когда время выполнения дешевле, чем человеко-часы кодировщика, простое программирование с помощью программных инструментов экономит деньги. - person agc; 10.04.2016

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

Я просто искал аналогичную транпозицию 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
person user3251704    schedule 30.01.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
person Another.Chemist    schedule 06.08.2014

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

head -$N file | tail -1 | tr ',' '\n'
person allanbcampbell    schedule 06.11.2014

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

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.

person Felipe    schedule 06.05.2014

Еще одно 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
person αғsнιη    schedule 19.09.2018

#!/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

person Dyno Fu    schedule 19.08.2015
comment
Прочтите unix.stackexchange.com/questions/169716/, чтобы понять некоторые, но не все, проблемы с этим решением. - person Ed Morton; 10.04.2016

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

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-дескрипторы, но не мог заставить их работать.

person kirill_igum    schedule 07.12.2014
comment
Обязательно установите будильник, если вы собираетесь запускать его для большого файла. Прочтите unix.stackexchange.com/questions/169716/, чтобы понять некоторые, но не все, проблемы этого подхода. - person Ed Morton; 10.04.2016

Другой вариант 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
person Ivan    schedule 28.02.2020

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

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

main :: IO ()
main = interact $ unlines . map unwords . transpose . map words . lines
person stelleg    schedule 26.08.2014

Решение 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

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

person Community    schedule 28.01.2016

Один лайнер с использованием 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) "
person dputhier    schedule 27.02.2020

Ранее я использовал два сценария для выполнения аналогичных операций. Первый находится в 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
person Sam    schedule 02.04.2020

Простой четырехстрочный ответ, пусть он будет читабельным.

col="$(head -1 file.txt | wc -w)"
for i in $(seq 1 $col); do
    awk '{ print $'$i' }' file.txt | paste -s -d "\t"
done
person Penny Liu    schedule 17.09.2020