Альтернатива, которую я не вижу здесь, является "Ответвлением по Изменению" философия.
Вместо того, чтобы иметь Вашу соединительную линию "Дикий Запад", что, если соединительная линия является "Текущим Выпуском"? Это работает хорошо, когда существует только одна версия приложения, выпущенного за один раз - такого как веб-сайт. Когда новая возможность или исправление ошибки необходимы, ответвление сделано содержать то изменение. Часто это позволяет мерам быть перемещенными для выпуска индивидуально и предотвращает кодеры ковбоя от случайного добавления опции для выпуска этого, Вы не предназначали. (Часто это - бэкдор - "Только для разработки/тестирования")
, указатели от Ben Collins довольно полезны в определении, какой стиль работал бы хорошо на Вашу ситуацию.
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
.
Хакерское решение для 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;
Если у вас установлен sc
, вы можете сделать:
psc -r < inputfile | sc -W% - > outputfile
Вот умеренно надежный 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. Очевидно, ваш пробег будет другим.
Решение 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))
Этот код предполагает, что каждая строка имеет одинаковое количество столбцов (заполнение не выполняется).
Чистый 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
Единственное улучшение, которое я вижу в вашем собственном примере, - это использование 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
Я использовал решение 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