Ефективний спосіб перемістити файл у 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
Що змушує вас думати, що існував би скрипт, який буде швидше, ніж сценарій Perl? Саме ця проблема вирішує Перл.
Марк Пім

1
@mark, якщо його чистий баш, він може швидше, ніж зв'язувати всі ці інструменти cut / sed тощо. Але знову ж таки, якщо ви визначите "bash" як у поєднанні інструментів, то просто написання awk-скрипту буде порівняно з обробкою тексту Perl wrt.
ghostdog74

Додайте ще одну, щоб не зрозуміти, як Perl буде повільним тут. Повільно написати код? Повільно виконувати? Мені справді не подобається perl, але це справді перевершує цю задачу.
Corey Porter

Якщо ваші стовпці / поля мають фіксований розмір / ширину, ви можете використовувати файл Python, щоб не читати файл у пам'яті. У вас є фіксовані розміри стовпців / полів / ширини?
tommy.carstensen

2
Кожен, хто думає, що сценарій оболонки буде швидше, ніж awk або perl, повинен прочитати unix.stackexchange.com/questions/169716/…, щоб вони зрозуміли, чому це не так.
Ед Мортон

Відповіді:


114
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

EDIT by Ed Morton (@ ghostdog74 сміливо видаляйте, якщо ви не схвалюєте).

Можливо, ця версія з деякими більш чіткими назвами змінних допоможе відповісти на деякі питання нижче та загалом уточнити, що робить сценарій. Він також використовує вкладки як сепаратор, про який спочатку запитував ОП, щоб він обробляв порожні поля, і він збіг обробляти вихідний результат для цього конкретного випадку.

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


А тепер також обробляти мітки рядків та стовпців?
Джонатан Леффлер

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

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

На моїй оригінальній матриці 2,2 Гб рішення perl трохи швидше, ніж awk - 350.103s проти 369.410s, я використовував perl 5.8.8 64bit
Federico Giorgi

1
@ zx8754, що максимальна кількість полів застосовується лише до старого, що не є POSIX. Можливо, неймовірно, на жаль, назвали "nawk". Це не стосується гауку чи інших сучасних страхів.
Ед Мортон

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окремо встановлює роздільник стовпців вхідних даних на вкладку.
нісетама

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

1
jq використав 21Gb оперативної пам’яті у файлі 766MB. Я вбив його через 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

21

транспонування проект SourceForge є coreutil типу C програма саме для цієї мети .

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

Дякуємо за посилання. Однак для роботи з великими матрицями / файлами потрібно занадто багато пам'яті.
tommy.carstensen

у нього є аргументи для блочного розміру та поля: спробуйте налаштувати -bта -fаргументи.
літаючі вівці

За замовчуванням розмір блоку (--block або -b) - 10kb, а розмір поля за замовчуванням (--fieldmax або -f) - 64, так що не може бути. Я намагався. Дякую за пропозицію, хоча.
tommy.carstensen

1
Добре працював із csv розміром 2 Гб.
дисципліна

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

16

Чистий БАШ, без додаткового процесу. Приємна вправа:

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

Це працювало для мого файлу, хоча цікаво, що він виводить список каталогів для першого рядка таблиці. Я не знаю достатньо БАШу, щоб зрозуміти, чому.
bugloaf

@bugloaf у вашому столі є * у куті.
Привіт71

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

16

Подивіться на GNU datamash, який може бути використаний datamash transpose. Майбутня версія також підтримуватиме перехресну таблицю (зведені таблиці)


9

Ось помірно твердий сценарій 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 мілісекунда з 7 усього). З більшим набором даних (матриця 100x100, записи 6-8 символів кожна), Perl трохи перевершив awk - 0,026s проти 0,042s. Ні це, ймовірно, не буде проблемою.


Репрезентативні таймінги для 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. Зрозуміло, ваш пробіг буде відрізнятися.


у моїй системі gawk перевершує перл. ви можете побачити мої результати в моєму відредагованому дописі
ghostdog74

4
зібраний висновок: інша платформа, інша версія програмного забезпечення, різні результати.
ghostdog74



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 ідеально підходить для цієї проблеми, маючи лише один рядок коду та потенційно довільно великий розмір файлів!

datamash -W transpose infile > outfile

3

Рішення хек-перла може бути таким. Це приємно, оскільки він не завантажує весь файл у пам'ять, друкує проміжні тимчасові файли, а потім використовує все прекрасну пасту

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

Я просто шукав подібний баш-транш, але з підтримкою накладки. Ось сценарій, який я написав на основі рішення 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 , не потрібні тимчасові файли. Зверніть увагу: ОП хотів ефективного виправлення (тобто швидшого), а відповіді, як правило, є швидшими, ніж відповідь. Ці одноразові вкладиші призначені для тих, хто любить програмні засоби * 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
Це все було б на порядок повільніше, ніж чистий та перламутровий розчин, і тендітне. Читайте unix.stackexchange.com/questions/169716/… .
Ед Мортон

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

вибачте, idk багато про perl. У цьому випадку інструмент, яким користуватися, був би awk. cut, headНе, echoі т.д., не більше POSIX сумісного коду оболонки , ніж awkсценарій - всі вони є стандартними для кожної установки UNIX. Існує просто немає причин використовувати набір інструментів, які в поєднанні вимагають бути обережними щодо вмісту вхідного файлу та каталогу, з якого виконується сценарій, коли ви можете просто використовувати awk, а кінцевий результат швидший і надійніший .
Ед Мортон

Будь ласка, я не антиук , але умови відрізняються. Причина №1: for f in cut head xargs seq awk ; do wc -c $(which $f) ; done Коли зберігання занадто повільне або IO занадто низький, більші перекладачі погіршують ситуацію, незважаючи на те, наскільки хорошими вони були б за більш ідеальних обставин. Причина №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

Ще один баш-варіант

$ 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 '$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-inging разом:

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: Я дуже хотів використовувати іо-дескриптори, але не зміг змусити їх працювати.


Обов’язково встановіть будильник, якщо ви збираєтесь його виконати у великому файлі. Прочитайте 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, який набагато швидше, ніж другий, який знаходиться в "чистому" баші. Можливо, ви зможете адаптувати його до власної програми.

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.