在Bash中转换文件的有效方法
我有一个巨大的标签分开的文件格式如下在Bash中转换文件的有效方法,bash,parsing,unix,transpose,Bash,Parsing,Unix,Transpose,我有一个巨大的标签分开的文件格式如下 X column1 column2 column3 row1 0 1 2 row2 3 4 5 row3 6 7 8 row4 9 10 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 我希望只使用bash命令以一
X column1 column2 column3
row1 0 1 2
row2 3 4 5
row3 6 7 8
row4 9 10 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
我希望只使用bash命令以一种高效的方式转换它(我可以编写一个十行左右的Perl脚本来实现这一点,但它的执行速度应该比本机bash函数慢)。所以输出应该是
X row1 row2 row3 row4
column1 0 3 6 9
column2 1 4 7 10
column3 2 5 8 11
我想到了这样的解决办法
X column1 column2 column3
row1 0 1 2
row2 3 4 5
row3 6 7 8
row4 9 10 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
cols=`head-n1输入| wc-w`
对于((i=1;i>输出
完成
但它很慢,似乎不是最有效的解决方案。我在中看到了一个vi解决方案,但它仍然过慢。有什么想法/建议/绝妙的想法吗?:-)
Jonathan的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
$head-5文件
1 0 1 2
2 3 4 5
3 6 7 8
4 9 10 11
1 0 1 2
$wc-l<文件
10000
$time perl test.pl文件>/dev/null
实际0.480s
用户0.442s
系统0m0.026s
$time awk-f test.awk文件>/dev/null
实际0.382s
用户0.367s
系统0m0.011s
$time perl test.pl文件>/dev/null
实0.481s
用户0.431s
系统0m0.022s
$time awk-f test.awk文件>/dev/null
实际0.390s
用户0.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
$cat tst.awk
开始{FS=OFS=“\t”}
{
对于(rowNr=1;rowNr maxRows?NF:maxRows)
maxCols=NR
}
结束{
对于(rowNr=1;rowNr而言,一个粗俗的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;
!/usr/bin/perl
使用警告;
严格使用;
我的美元柜台;
打开输入,“temp$”或die(“无法打开输出文件!”);
打印输出联接(“\n”,@array);
闭合输出;
$counter=$。;
}
闭合输入;
#将文件粘贴在一起
我的$execute=“粘贴”;
foreach(1..$counter){
$execute.=“临时$counter”;
}
$execute.=“>$ARGV[1]”;
系统$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
/bin/rm输出2>/dev/null
cols=`head-N1输入| wc-w`
对于((i=1;i>输出
如果已安装,则可以执行以下操作:
psc -r < inputfile | sc -W% - > outputfile
psc-routputfile
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
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
这段代码假设每一行都有相同数量的列(不执行填充)。这里有一个中等可靠的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";
}
请注意,在这台机器上,gawk比awk快得多,但仍然比perl慢。很明显,您的里程数会有所不同。纯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
declare-a-array=()#我们构建了一个一维数组
阅读-一行<“$1”#阅读标题
COLS=${#行[@]}#保存列数
索引=0
读一行的时候;做一件事
对于((COUNTER=0;COUNTER我使用了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
!/bin/bash
declare-a array=()#我们构建一个一维数组
阅读-一行<“$1”#阅读标题
COLS=${#行[@]}#保存列数
索引=0
读一行的时候;做一件事
对于((COUNTER=0;COUNTER),sourceforge上的项目就是一个类似于coreutil的C程序
gcc transpose.c -o transpose
./transpose -t input > output #works with stdin, too.
我只是在寻找类似的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
!/bin/bash
declare-a array=()#我们构建一个一维数组
declare-ancols=()#我们构建一个1-D数组,其中包含每行的元素数
分隔符=“\t”;
填充=”;
MAXROWS=0;
索引=0
indexCol=0
读一行的时候;做一件事
ncols[$indexCol]=${#行[@]};
((indexCol++)
如果[${行[@]}-gt${MAXROWS}]
然后
MAXROWS=${行[@]}
fi
对于((COUNTER=0;COUNTER不是很优雅,但是这个“单行”命令很快解决了问题:
cols=4; for((i=1;i<=$cols;i++)); do \
awk '{print $'$i'}' input | tr '\n' ' '; echo; \
done
cols=4;for((i=1;ii)我正在寻找一种解决方案,用任何类型的数据(数字或数据)转置任何类型的矩阵(nxn或mxn),并得到以下解决方案:
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
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
Row2Trans=number1
Col2Trans=number2
对于((i=1;$i列_$i
完成
完成
粘贴-d',''ls-mv Column_*|sed's/,//g'>>$ARCHIVO
这是一个Haskell解决方案。使用-O2编译时,它的运行速度略快于ghostdog的awk,略慢于Stephan在我的机器上编写的薄薄的c python,用于重复“Hello world”输入行。不幸的是,据我所知,GHC对传递命令行代码的支持是不存在的,因此您必须自己将其写入文件。它会将行截断为最短行的长度
transpose :: [[a]] -> [[a]]
transpose = foldr (zipWith (:)) (repeat [])
main :: IO ()
main = interact $ unlines . map unwords . transpose . map words . lines
如果您只想从文件中提取一行(逗号分隔)的$N并将其转换为列:
head -$N file | tail -1 | tr ',' '\n'
下面是一个Bash one行程序,它只需将每一行转换为一列,然后将它们粘贴在一起:
echo '' > tmp1; \
cat m.txt | while read l ; \
do paste tmp1 <(echo $l | tr -s ' ' \\n) > tmp2; \
cp tmp2 tmp1; \
done; \
cat tmp1
创建tmp1
文件,使其不为空
使用tr
将新列粘贴到tmp1
文件中
将结果复制回tmp1
PS:我真的很想使用io描述符,但无法让它们工作。rs
rs
随BSD和macOS提供,但可从其他平台上的软件包管理器获得。它是以重塑命名的
echo '' > tmp1; \
cat m.txt | while read l ; \
do paste tmp1 <(echo $l | tr -s ' ' \\n) > tmp2; \
cp tmp2 tmp1; \
done; \
cat tmp1
0 1 2
4 5 6
7 8 9
10 11 12
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
{for (f=1;f<=NF;f++) col[f] = col[f]":"$f} END {for (f=1;f<=NF;f++) print col[f]}
$ 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
#!/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
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
for f in 1 2 3 4 ; do cut -d ' ' -f $f foo | xargs echo ; done
n=$(head -n 1 foo | wc -w)
for f in $(seq 1 $n) ; do cut -d ' ' -f $f foo | xargs echo ; done
{ timeout '.01' xargs --show-limits ; } 2>&1 | grep Max
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
for f in 1 2 3 4 ; do set - $(cut -d ' ' -f $f foo) ; echo $@ ; done
apt install datamash
datamash transpose < yourfile
datamash -W transpose infile > outfile
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
X row1 row2 row3 row4
column1 0 3 6 9
column2 1 4 7 10
column3 2 5 8 11
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) "
$ 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
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
col="$(head -1 file.txt | wc -w)"
for i in $(seq 1 $col); do
awk '{ print $'$i' }' file.txt | paste -s -d "\t"
done