将列输出重写到现有文件
rewrite column output to an existing file
我有这样一行输入。
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=; str2=; getline < "file"; print str1" \t "str2" \t " > "newfile"}'
我从来没有发现需要这个相当晦涩和专门的 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);
连接实用程序将两个文件连接到一个公共字段,如果您不使用 -1、-2 或 -j 选项覆盖它,则默认为每个文件的第一个字段。字段分隔符默认为空格,但可以使用 -t 选项指定(仅作为单个字符)。
对于您的示例数据,排序在技术上不是必需的,因为它已经排序,但由于联接始终需要排序的联接字段,因此将它们放在那里是个好主意。如果加入非第一个字段,您必须使用排序实用程序的 -k 选项按适当的字段排序。
-o 选项的参数指定了输出格式。这是手册页中的引述:
FORMAT is one or more comma or blank separated specifications, each being 'FILENUM.FIELD' or '0'. Default FORMAT outputs the join field, the remaining fields from FILE1, the remaining fields from FILE2, all separated by CHAR. If FORMAT is the keyword 'auto', then the first line of each file determines the number of fields output for each line.
所以比如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;
编辑:我看到您已经编辑了您的问题以指定第一个字段中可能有重复项。我想了一会儿这会破坏我的整个解决方案,因为连接实用程序只对两个输入文件执行 DB 样式的笛卡尔积,但后来我意识到我们可以合成一个具有唯一值的新连接字段。
我不确定 nl
实用程序是否存在于所有类 Unix 系统上,但如果有,请按以下方法进行操作:
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 会忽略所有无关的空格。)结果是输入的每一行都以其行号为前缀,后跟一个制表符作为分隔符,join 将其识别为空格解析为字段时。因此,每个文件都以一个额外的字段结束;行号字段,然后是 2 或 3 个数据字段。因此,我不得不将参数中的字段选择器增加到 -o
选项以加入。
另一个用行号作为输入行前缀的解决方案是cat -n
。
sed "$( echo "$TEST2" | sed 's/\&/#/g;s/^/#/;s/#\(.\)=\([^#]*\)/s_^ [^ ]* _ _;/g')" YourFile
使用预替换生成 sed 操作列表,例如:s_^A [^ ]* _A 18 _;s_^B [^ ]* _B 0 _;s_^C [^ ]* _C 43 _;s_^D [^ ]* _D 12 _;s_^E [^ ]* _E 3 _;
来自 TEST2 内容
全部在 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]}
( in b)&&[=10=]=b[]" "' file
输出
A 18 text
B 0 example
C 43 text
D 12 EX
E 3 test
我有这样一行输入。
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=; str2=; getline < "file"; print str1" \t "str2" \t " > "newfile"}'
我从来没有发现需要这个相当晦涩和专门的 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);
连接实用程序将两个文件连接到一个公共字段,如果您不使用 -1、-2 或 -j 选项覆盖它,则默认为每个文件的第一个字段。字段分隔符默认为空格,但可以使用 -t 选项指定(仅作为单个字符)。
对于您的示例数据,排序在技术上不是必需的,因为它已经排序,但由于联接始终需要排序的联接字段,因此将它们放在那里是个好主意。如果加入非第一个字段,您必须使用排序实用程序的 -k 选项按适当的字段排序。
-o 选项的参数指定了输出格式。这是手册页中的引述:
FORMAT is one or more comma or blank separated specifications, each being 'FILENUM.FIELD' or '0'. Default FORMAT outputs the join field, the remaining fields from FILE1, the remaining fields from FILE2, all separated by CHAR. If FORMAT is the keyword 'auto', then the first line of each file determines the number of fields output for each line.
所以比如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;
编辑:我看到您已经编辑了您的问题以指定第一个字段中可能有重复项。我想了一会儿这会破坏我的整个解决方案,因为连接实用程序只对两个输入文件执行 DB 样式的笛卡尔积,但后来我意识到我们可以合成一个具有唯一值的新连接字段。
我不确定 nl
实用程序是否存在于所有类 Unix 系统上,但如果有,请按以下方法进行操作:
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 会忽略所有无关的空格。)结果是输入的每一行都以其行号为前缀,后跟一个制表符作为分隔符,join 将其识别为空格解析为字段时。因此,每个文件都以一个额外的字段结束;行号字段,然后是 2 或 3 个数据字段。因此,我不得不将参数中的字段选择器增加到 -o
选项以加入。
另一个用行号作为输入行前缀的解决方案是cat -n
。
sed "$( echo "$TEST2" | sed 's/\&/#/g;s/^/#/;s/#\(.\)=\([^#]*\)/s_^ [^ ]* _ _;/g')" YourFile
使用预替换生成 sed 操作列表,例如:s_^A [^ ]* _A 18 _;s_^B [^ ]* _B 0 _;s_^C [^ ]* _C 43 _;s_^D [^ ]* _D 12 _;s_^E [^ ]* _E 3 _;
来自 TEST2 内容
全部在 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]}
( in b)&&[=10=]=b[]" "' file
输出
A 18 text
B 0 example
C 43 text
D 12 EX
E 3 test