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

Альтернатива, которую я не вижу здесь, является "Ответвлением по Изменению" философия.

Вместо того, чтобы иметь Вашу соединительную линию "Дикий Запад", что, если соединительная линия является "Текущим Выпуском"? Это работает хорошо, когда существует только одна версия приложения, выпущенного за один раз - такого как веб-сайт. Когда новая возможность или исправление ошибки необходимы, ответвление сделано содержать то изменение. Часто это позволяет мерам быть перемещенными для выпуска индивидуально и предотвращает кодеры ковбоя от случайного добавления опции для выпуска этого, Вы не предназначали. (Часто это - бэкдор - "Только для разработки/тестирования")

, указатели от Ben Collins довольно полезны в определении, какой стиль работал бы хорошо на Вашу ситуацию.

105
задан Community 23 May 2017 в 12:26
поделиться

8 ответов

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

output

$ 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 .

107
ответ дан 24 November 2019 в 03:56
поделиться

Хакерское решение для 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;
3
ответ дан 24 November 2019 в 03:56
поделиться

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

psc -r < inputfile | sc -W% - > outputfile
6
ответ дан 24 November 2019 в 03:56
поделиться

Вот умеренно надежный 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-разрядная версия) против awk (версия 20040207, если задано '-V') против gawk 3.1.7 (32-разрядной версии) в MacOS X 10.5. 8 для файла, содержащего 10 000 строк с 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. Очевидно, ваш пробег будет другим.

9
ответ дан 24 November 2019 в 03:56
поделиться

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

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

30
ответ дан 24 November 2019 в 03:56
поделиться

Чистый 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
16
ответ дан 24 November 2019 в 03:56
поделиться

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

Я использовал решение 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
ответ дан 24 November 2019 в 03:56
поделиться
Другие вопросы по тегам:

Похожие вопросы: