Bash에서 파일을 바꾸는 효율적인 방법
다음과 같은 형식의 탭으로 구분 된 거대한 파일이 있습니다.
X column1 column2 column3
row1 0 1 2
row2 3 4 5
row3 6 7 8
row4 9 10 11
bash 명령 만 사용하여 효율적으로 전치 하고 싶습니다 (10 줄 정도의 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의 reshape 함수 이름을 따서 명명 된은 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 문자열 리터럴 문자열의 내용을 출력한다. /
연산자 분할 문자열 과부하.
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))
이 코드는 모든 행에 동일한 수의 열이 있다고 가정합니다 (패딩이 수행되지 않음).
sourceforge 의 전치 프로젝트는 정확히이를위한 coreutil과 유사한 C 프로그램입니다.
gcc transpose.c -o transpose
./transpose -t input > output #works with stdin, too.
순수 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
.NET 과 같이 사용할 수있는 GNU 데이터 매시 를 살펴보십시오 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 ( '-V'가 제공되는 경우 버전 20040207) 대 MacOS X 10.5.8의 gawk 3.1.7 (32 비트)에 대한 대표적인 타이밍은 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보다 느립니다. 분명히 귀하의 마일리지는 다를 것입니다.
당신이 경우 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
하는 ':'로 구분 된 문자열이 커 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 -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
이것은 모든 데이터를 2 차원 배열로로드 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
나는 비슷한 bash tranpose를 찾고 있었지만 패딩을 지원했습니다. 다음은 작동하는 것처럼 보이는 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
파일에서 단일 (쉼표로 구분 된) 줄 $ 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
일부 * nix 표준 유틸리티 한 줄, 임시 파일이 필요하지 않습니다. NB : OP는 효율적인 수정 (즉, 더 빠름)을 원했으며 일반적으로 상위 답변이이 답변보다 빠릅니다. 이 한 줄짜리는 어떤 이유로 든 * nix 소프트웨어 도구 를 좋아하는 사람들을 위한 것입니다. 드물지만 ( 예 : 부족한 IO 및 메모리) 이러한 스 니펫은 실제로 일부 상위 답변보다 빠를 수 있습니다.
입력 파일 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로 컴파일 할 때, 그것은 ghostdog의 awk보다 약간 더 빠르게 실행되고 반복 된 "Hello world"입력 라인에 대해 Stephan의 얇게 래핑 된 c 파이썬 보다 약간 느립니다 . 불행히도 명령 줄 코드 전달에 대한 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
하고 함께 -하는 Bash 한 줄입니다 .
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 설명자를 사용하고 싶었지만 작동하지 못했습니다.
참고 URL : https://stackoverflow.com/questions/1729824/an-efficient-way-to-transpose-a-file-in-bash
'programing' 카테고리의 다른 글
Angular CLI 오류 : serve 명령을 Angular 프로젝트에서 실행해야하지만 프로젝트 정의를 찾을 수 없습니다. (0) | 2020.08.14 |
---|---|
데이터 URL 파일 다운로드 (0) | 2020.08.14 |
JavaScript에서 개체 / 배열의 성능은 무엇입니까? (0) | 2020.08.14 |
QMake .pro 파일에서 다른 디버그 / 릴리스 출력 디렉토리를 지정하는 방법 (0) | 2020.08.14 |
문자열에 하위 문자열이 포함되어 있는지 확인 (0) | 2020.08.14 |