효율적인 방법을 바꾸기에서 파일 Bash
문제
나는 거대한 탭으로 구분하여 파일 포맷처럼 이
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
이를위한 목적으로 구축 된 유틸리티가 있습니다.
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.
우리가 알고 있다면 foo 4 개의 열이 있습니다.
for f in 1 2 3 4 ; do cut -d ' ' -f $f foo | xargs echo ; done
얼마나 많은 열을 모른다면 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
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
사용
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
생성
tmp1
비어 있지 않도록 파일.각 줄을 읽고 사용하는 열로 변환합니다.
tr
새 칼럼을 페이스트합니다
tmp1
파일사본 결과가 다시 들어옵니다
tmp1
.
추신 : 나는 정말로 io-descriptors를 사용하고 싶었지만 작동하게 할 수 없었습니다.