在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