문제

나는 거대한 탭으로 구분하여 파일 포맷처럼 이

X column1 column2 column3
row1 0 1 2
row2 3 4 5
row3 6 7 8
row4 9 10 11

하고 싶 transpose 그것은에서 효율적인 방법만을 사용하여 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 이 게시물, 지만,그것은 여전히 이상 느립니다.어떤 생각이/제안/화려한 아이디어가?:-)

도움이 되었습니까?

해결책

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

10000 라인 파일에서 Jonathan의 Perl 솔루션에 대한 성능

$ 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

Ed Morton의 편집 (@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.

다른 팁

또 다른 옵션은 사용 rs:

rs -c' ' -C' ' -T

-c 변경 사항을 입력 열 분리기, -C 변경 출력 열 분리기,고 -T 바꿉 행과 열에 있습니다.를 사용하지 않는 -t-T, 기 때문에 그것을 사용하여 자동으로 계산한 행과 열의 수입은 일반적으로 정확하다. rs, 는 이름 변경 기능에 APL,와 함께 제공 BSDs OS X,하지만 그것이 있어야에서 사용할 수 있는 패키지 관리자에는 다른 플랫폼입니다.

두 번째 옵션을 사용하는 것 루비:

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 문자열.이 / 운영자 과부하를 분할 문자열.

파이썬 솔루션 :

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))

이 코드는 모든 라인에 동일한 수의 열이 있다고 가정합니다 (패딩이 수행되지 않음).

그만큼 바꾸어 놓다 Sourceforge의 프로젝트는 정확히 그에 대한 핵심과 같은 C 프로그램입니다.

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

순수한 배쉬, 추가 과정이 없습니다. 좋은 운동 :

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

살펴보십시오 GNU Datamash 처럼 사용할 수 있습니다 datamash transpose. 미래 버전은 크로스 테이블 (피벗 테이블)도 지원합니다.

다음은 작업을 수행하는 적당히 견고한 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의 성능 차이는 무시할 수 없었습니다 (총 7 개 중 1 밀리 초). 더 큰 데이터 세트 (100x100 행렬, 각각 6-8 자)의 경우 Perl은 AWK -0.026S 대 0.042S를 약간 능가했습니다. 어느 것도 문제가되지 않을 것입니다.


PERL 5.10.1 (32 비트) 대 AWK (버전 20040207이 주어진 경우 버전 20040207) vs gawk 3.1.7 (32 비트)의 대표 타이밍 (MacOS x 10.5.8)은 5 개의 열이 포함 된 파일에 MacOS x 10.5.8에서 GAWK 3.1.7 (32 비트)의 대표적인 타이밍 선:

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보다 느립니다. 분명히, 당신의 마일리지는 다를 것입니다.

당신이 가지고 있다면 sc 설치하면 다음을 수행 할 수 있습니다.

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

이를위한 목적으로 구축 된 유틸리티가 있습니다.

GNU Datamash 유틸리티

apt install datamash  

datamash transpose < yourfile

이 사이트에서 가져와 https://www.gnu.org/software/datamash/ 그리고 http://www.thelinuxrain.com/articles/transposing-rows-and-columns-3-methods

모든 행이 같은 수의 필드를 가지고 있다고 가정하면이 AWK 프로그램은 문제를 해결합니다.

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

말로, 당신이 행을 통해 루프 할 때, 모든 필드에 대해 f a ':'-분리 된 문자열을 키우십시오 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

GNU Datamash 한 줄의 코드와 잠재적으로 큰 파일 크기 로이 문제에 완벽하게 적합합니다!

datamash -W transpose infile > outfile

Hackish 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;

내가 당신의 예제를 볼 수있는 유일한 개선은 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

나는 보통이 작은 것을 사용합니다 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초기 파일의 열량은 초기 파일이 보유하고 있으므로 인쇄 할 행의 수로 사용됩니다.

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

나는 단지 비슷한 배쉬 고도를 찾고 있었지만 패딩을 지원했습니다. 다음은 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

나는 모든 종류의 매트릭스 (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

파일에서 단일 (Comma Dilimited) 라인 $ n을 잡고 열로 바꾸고 싶다면 다음과 같습니다.

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

그다지 우아하지는 않지만이 "단일 라인"명령은 문제를 빠르게 해결합니다.

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.

또 다른 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
#!/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

약간 *아니야 표준 Util One-Liners, 임시 파일이 필요하지 않습니다. NB : OP를 원했습니다 효율적인 수정, (예 : 더 빠른), 상단 답변은 일반적 으로이 답변보다 빠릅니다. 이것들 1 라이너는 좋아하는 사람들을위한 것입니다 *아니야 소프트웨어 도구, 어떤 이유로 든. 드문 경우 (예를 들어 부족한 io & memory),이 스 니펫은 실제로 최고의 답변보다 빠를 수 있습니다.

입력 파일을 호출하십시오 foo.

  1. 우리가 알고 있다면 foo 4 개의 열이 있습니다.

    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
    

다음은 Haskell 솔루션입니다. -o2로 컴파일하면 고스트 독의 어색보다 약간 빠르고 스테판의 것보다 약간 느리게 작동합니다. 얇게 싸인 c 반복 된 "Hello World"입력 라인을 위해 내 컴퓨터의 Python. 불행히도 명령 줄 코드 전달에 대한 GHC의 지원은 내가 알 수있는 한 존재하지 않으므로 파일에 직접 작성해야합니다. 가장 짧은 행의 길이까지 행을 잘게합니다.

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

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

전체 배열을 메모리에 저장하는 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

이는 (출력 행이 적은 경우 이전 코드보다 빠릅니다).

다음은 각 라인을 단순히 열로 변환하고 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.

추신 : 나는 정말로 io-descriptors를 사용하고 싶었지만 작동하게 할 수 없었습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top