将列输出重写为现有文件

时间:2015-01-13 07:31:22

标签: bash awk sed

我有这样一行输入。

TEST2="A=18&A=0&ANY=43&D=12&D=3"

我使用awk设法通过以下列分隔:

echo "$TEST2" | awk 'BEGIN {FS = "[=&]"}{for(i=1; i<= NF; ++i){print $i, $((i+1));++i}}'

A 18    
A 0   
ANY 43      
D 12    
D 3        

但是现在如何将此输出重定向到已有3列的现有文件:

A 15 text   
A 1 example   
ANY 21 text   
D 4 EX   
D 23 test    

最终结果应该是(只有第1列和第3列不变):

A 18 text    
A 0 example  
ANY 43 text      
D 12 EX   
D 3 test

解决方案:

echo "$TEST2"| awk 'BEGIN {FS = "[=&]"}{for(i=1; i<= NF; ++i){print $i, $((i+1));++i}}' | awk '{str1=$1; str2=$2; getline < "file"; print str1" \t "str2" \t "$3 > "newfile"}'

3 个答案:

答案 0 :(得分:1)

我从未发现需要这种相当模糊和专业的GNU实用程序,但它似乎完全符合您的要求:

join -o '2.1 2.2 1.3' <(sort <file) <(echo "$TEST2"| awk 'BEGIN {FS = "[=&]"}{for(i=1; i<= NF; ++i){print $i, $((i+1));++i}}'| sort);

join实用程序在公共字段上连接两个文件,如果不使用-1,-2或-j选项覆盖它,则默认为每个文件的第一个字段。字段分隔符默认为空格,但可以使用-t选项指定(仅作为单个字符)。

对于您的示例数据,这些排序在技术上并不是必需的,因为它已经排序,但由于join总是需要排序的连接字段,因此最好将它们放在那里。如果加入非第一个字段,则必须使用sort实用程序的-k选项按相应字段进行排序。

-o选项的参数指定输出格式。以下是手册页中的引用:

  

FORMAT是一个或多个逗号或空白分隔的规范,每个规范都是'FILENUM.FIELD'或'0'。默认FORMAT输出连接字段,FILE1中的其余字段,FILE2中的其余字段,全部由CHAR分隔。如果FORMAT是关键字'auto',则每个文件的第一行确定每行的输出字段数。

例如,2.1表示第二个文件的第一个字段。

我上面写的命令不会覆盖文件,只是生成所需的输出。要覆盖文件,您可以添加重定向:

join -o '2.1 2.2 1.3' <(sort <file) <(echo "$TEST2"| awk 'BEGIN {FS = "[=&]"}{for(i=1; i<= NF; ++i){print $i, $((i+1));++i}}'| sort) >|file;

但是,通常如果您尝试在尝试使用(文件的原始内容)作为输入的同一命令中使用重定向覆盖文件,则它将无法工作,因为该文件可能会被截断为在将其作为输入读取之前重定向的结果,因此最终不会读取任何输入。现在,测试我当前的系统,我实际上发现上面的重定向工作完美,说实话我不确定为什么;我怀疑shell正在完成在处理重定向之前读取文件的进程替换,但我不确定。我不会依赖它在所有情况下或在所有系统上工作。所以你可以做的是重定向到一个新文件,如果成功则将其移到原始文件上:

join -o '2.1 2.2 1.3' <(sort <file) <(echo "$TEST2" | awk 'BEGIN {FS = "[=&]"}{for(i=1; i<= NF; ++i){print $i, $((i+1));++i}}'| sort) >|file.tmp && mv file.tmp file;

编辑:我看到您已编辑了您的问题,以指定第一个字段中可能存在重复项。我想了一下,这会破坏我的整个解决方案,因为join实用程序只执行两个输入文件的DB风格的笛卡尔积,但后来我意识到我们可以合成一个具有唯一值的新连接字段。

我不确定所有类Unix系统上是否存在nl实用程序,但是如果你有这个实用程序,那么这就是你如何使用它的方法:

join -o '1.2 2.3 1.4' <(nl -w1 <file) <(echo "$TEST2"| awk 'BEGIN {FS = "[=&]"}{for(i=1; i<= NF; ++i){print $i, $((i+1));++i}}'| nl -w1) >|file.tmp && mv file.tmp file;

nl实用程序编号根据您指定的格式排列。我刚刚指定了-w1,意思是字段宽度为1个字符,它只删除了不必要的填充空格,通常由默认的-w6添加。 (实际上这里甚至不需要-w1; join会忽略所有无关的空格。)结果是输入的每一行都以其行号作为前缀,后跟一个制表符作为分隔符,该连接识别为空格解析到字段时。因此,每个文件最终都有一个额外的字段;行号字段,然后是2或3个数据字段。因此,我必须将参数中的字段选择器增加到-o选项才能加入。

为输入行添加行号前缀的另一种解决方案是cat -n

答案 1 :(得分:0)

sed "$( echo "$TEST2" | sed 's/\&/#/g;s/^/#/;s/#\(.\)=\([^#]*\)/s_^\1 [^ ]* _\1 \2 _;/g')" YourFile

使用预替换生成sed操作列表,例如:来自TEST2内容的s_^A [^ ]* _A 18 _;s_^B [^ ]* _B 0 _;s_^C [^ ]* _C 43 _;s_^D [^ ]* _D 12 _;s_^E [^ ]* _E 3 _;

答案 2 :(得分:0)

全部在awk

awk -vS="$TEST2" '!x{x=split(S,a,/[&=]/);for(i=2;i<=x;i+=2)b[a[i-1]]=a[i-1]" "a[i]}
                  ($1 in b)&&$0=b[$1]" "$3' file

输出

A 18 text
B 0 example
C 43 text
D 12 EX
E 3 test