bash脚本 - 进程替换的可变扩展

时间:2012-02-10 14:12:05

标签: bash scripting process substitution

我正在尝试对多列文件的每一列执行分析,并使用粘贴一起重新加入列。我不知道先验有多少列,所以我用“wc -w”和一个循环来定义一个命令数组。然后每个命令都是一个进程替换。以下脚本显示了我正在尝试的内容,输出显示在之后。值得注意的是,如果我将命令数组回显到终端,那么,使用鼠标进行剪切粘贴,它可以正常工作,因此它必须是变量扩展和进程替换的顺序。

简而言之,我需要在 shell变量中进行进程替换。有任何想法吗?提前谢谢。

-------------- script.sh ----------------

#!/bin/bash
f="file.txt";
echo "File contents"
cat $f; 
         # simple solution
echo; echo "First try";
paste <(cat $f) <(tac $f)
         # now define cmd[1] and cmd[2] and merge together with paste
echo; echo "Second try";
cmd[0]="paste";
cmd[1]="cat $f";
cmd[2]="tac $f";
${cmd[0]} <(${cmd[1]}) <(${cmd[1]})
         # but what I really want is something like:
echo; echo "Third try";
cmd[1]="<(cat $f)";
cmd[2]="<(tac $f)";
${cmd[0]} ${cmd[1]} ${cmd[2]}
         # or even better:
echo; echo "Fourth try";
${cmd[*]}
echo; echo "Show the array";
echo ${cmd[*]}

-------------输出--------------------

$ ./scipt.sh 
File contents
A B C
D E F
G H I

First try
A B C   G H I
D E F   D E F
G H I   A B C

Second try
A B C   A B C
D E F   D E F
G H I   G H I

Third try
paste: <(cat: No such file or directory

Fourth try
paste: <(cat: No such file or directory

Show the array
paste <(cat file.txt) <(tac file.txt)
$ paste <(cat file.txt) <(tac file.txt)
A B C   G H I
D E F   D E F
G H I   A B C
$ 

在回复shellter时,这里有一些示例输入。

    7.74336e-08 7.30689e-08 0.359106        19.981796       -0.160611       0.027
    7.74336e-08 7.30689e-08 0.363938        19.985069       0.041319        0.035
    7.74336e-08 7.30689e-08 0.363133        19.982094       0.041319        0.068
    7.74336e-08 7.30689e-08 0.360716        19.981796       -0.160611       0.006
    7.74336e-08 7.30689e-08 0.361522        19.981796       0.243249        0.049
    7.74336e-08 7.30689e-08 0.357897        19.986260       0.041319        0.035

这样的数据可能有1亿行。我需要将每列分开,将每列分成(比方说)1000块,然后执行块中每个元素的平均值,然后将平均列重新合并在一起。对于下面的例子,如果我平均只有2个块,每个3个元素(而不是100K,每个1000),那么第6列的输出将是:

0.0165     # =(0.027+0.006)/2 - 1st row from each size-3 block
0.042      # =(0.035+0.049)/2 - 2nd row
0.0515     # =(0.068+0.035)/2 - 3rd row

我已经有程序进行平均(即“Some_Complicated_Analysis”)并且它工作正常。因此,我需要为脚本分离列,将其提供给Some_Comp_Analysis,然后使用paste将各种输出再次合并回列中。但是,这些文件很大,而且我不知道它有多少列。如果我知道只有2列,那么paste <(${cmd[1]}) <(${cmd[2]})就可以了。

解决方案

更新:已找到答案 - 如下面的glenn jackman的回复更新所示。 paste命令必须以eval开头。我不确切知道为什么这是必要的,但没有它们,变量扩展${cmd[]}会混淆过程替换<(...)。上面的答案还在数组扩展"${cmd[*]}"周围加上双引号,但这些看起来并不那么重要 - 尽管如果没有它们,cmd[]内的其他扩展可能会失败。但是,eval是必要的。

3 个答案:

答案 0 :(得分:3)

将“cmd1”和“cmd2”中的每一个定义为单个数组

$ cmd1=(cat $f)
$ cmd2=(tac $f)
$ paste <("${cmd1[@]}") <("${cmd2[@]}")
A B C   G H I
D E F   D E F
G H I   A B C

更新:您只需要eval构建的流程替换:

cols=$(head -n 1 $f|wc -w)
for (( i=1 ; i<=cols ; i++ )); do
  cmd[i]="<(cat $f|cut -f$i|Some_Complicated_Analysis)"
done
eval paste "${cmd[*]}"   # quotes are important here

答案 1 :(得分:1)

如果您的脚本仍然无效,这可能会更有帮助。仍然遵循glenn jackman的回答,但另外你可能想在剧本中做到这一点

设置+ o posix

http://www.linuxjournal.com/content/shell-process-redirection

来自链接: “流程替换不符合POSIX标准,因此可能必须通过以下方式启用:set + o posix”

答案 2 :(得分:0)

试试这个:

{
  cat -<<EOS
10 7.74336e-08 7.30689e-08 0.359106        19.981796       -0.160611       0.027
10 7.74336e-08 7.30689e-08 0.363938        19.985069       0.041319        0.035
10 7.74336e-08 7.30689e-08 0.363133        19.982094       0.041319        0.068
10 7.74336e-08 7.30689e-08 0.360716        19.981796       -0.160611       0.006
10 7.74336e-08 7.30689e-08 0.361522        19.981796       0.243249        0.049
10 7.74336e-08 7.30689e-08 0.357897        19.986260       0.041319        0.035
EOS
} |
awk '
  BEGIN { binSz=3; binSzLim=binSz ; binSzLim++ }
  NR==1{
    # base error checking on number of cols in first record
    maxCols=NF
    maxColsLim=maxCols ; maxColsLim++
    r=0
  }
  {
    if (NF != maxCols) {
      print "Skipping record, Mismatch in data, expected " maxCols ", found " NF " recs at " NR ":" $0
      next
    }
    r++
    #dbg print "r="r" NR=" NR ":$0=" $0

    # load data into temp arr[] by column
    for (c=1;c<maxColsLim;c++) {
      arr[r,c]+=$c
      #dbg printf ("arr["r","c"]=" arr[r,c] " " )

      avgArr[r,c]++
      #dbg print "avgArr["r","c"]="avgArr[r,c]
    }

    if(r>=binSz) {
      r=0
    }
  }
  END {
    for (r=1;r<binSzLim;r++) {
      #dbg print "r=" r " binSzLim=" binSzLim " " (r<binSzLim) "\t"
      for (c=1;c<maxColsLim;c++) {
        #dbg printf("arr["r","c"]=" arr[r,c] "\tavg=" arr[r,c]/binSz "\t")
        printf(  arr[r,c]/avgArr[r,c] " ")
      }
      printf "\n"
    }
  }
'

生成输出

10 7.74336e-08 7.30689e-08 0.359911 19.9818 -0.160611 0.0165
10 7.74336e-08 7.30689e-08 0.36273 19.9834 0.142284 0.042
10 7.74336e-08 7.30689e-08 0.360515 19.9842 0.041319 0.0515

我在数据中添加了第一列10,以便于调试总和和平均值正常工作。

一个有趣的问题,感谢发布并感谢您继续回答我的问题的良好意愿;-)

{ cat -<<EOS ... EOS }|只是为了让整个事情在一个副本/粘贴中运行变得容易,并且看到它正在运行。您可以将awk代码放在顶部#!/bin/awk -f的{​​{1}}文件中,并将其作为chmod 755 myScript.awk运行。

您只需要更改为BEGIN块中的myScript.awk BigFile > AvgsFile即可按预期处理文件。

我很想知道这个版本的时间安排。