如何在不丢失 awk 格式的情况下替换整个列
Posted
技术标签:
【中文标题】如何在不丢失 awk 格式的情况下替换整个列【英文标题】:How to replace an entire column without losing the formatting in awk 【发布时间】:2017-01-01 16:48:42 【问题描述】:编者注:
这个问题有一个麻烦的编辑历史,因为一个善意但被误导的编辑(它引入了不相关的、“漂亮”的格式,依赖于空格和|
字符。分隔列)暂时混淆了这个问题(因为已恢复)。
OP 的前提是输入是 tab 分隔的,即使这没有直接反映在此处显示的示例输入中。
我有一个包含 6 列的输入文件,它们是制表符分隔的。我想用值'81115'
替换第 5 列中的所有值,同时保持格式不变。
输入文件:
203 ADD 24 IAC 81216 IT
204 ATT 24 IAC 81216 IT
所需的输出文件:
203 ADD 24 IAC 81115 IT
204 ATT 24 IAC 81115 IT
我的解决方案#1
我正在使用以下命令:
awk '$5 = v 1' v="81115" file > file.NEW
使用上述命令,第 5 列将被替换,但列不再以制表符分隔。
输出文件:
203 ADD 24 IAC 81115 IT
204 ATT 24 IAC 81115 IT
我的解决方案#2
为了保持格式,我尝试使用以下命令:
awk -v replace="81115" -F '\t' -v OFS='\t' $5=replace1' file > file.NEW
或
awk -F"\t" -v OFS="\t" '$5=811151' file > file.NEW
或
awk -F '\t' '$5="81115";1' OFS='\t' file > file.NEW
上述所有命令都保持格式不变,但在末尾添加了一个值为81115
的新列;即,第 7 列正在附加。
输出文件:
203 ADD 24 IAC 81216 IT 81115
204 ATT 24 IAC 81216 IT 81115
任何人都可以建议替代解决方案或更改上述命令吗?
【问题讨论】:
试试:awk -v new="81115" 'BEGINOFS=FS="\t" $5=new1' file
您的所有解决方案#2 版本在这里都可以正常工作。解决方案 #1 也可以,只需添加 -v OFS="\t"
即可。我用 MAWK 和 GAWK 进行了测试。测试您的输入文件是否真的是制表符分隔的。
alternate.. perl -pe 's/^(\S+\s+)4\K\S+/81115/' file > file.NEW
.. 或者如果第 5 列始终是 81216
并且在文件中是唯一的,为什么不简单的 sed 's/81216/81115/' file > file.NEW
等一下 - 你说你的字段是制表符分隔的,但你用|
s 和空格分隔它们。哪个是对的?如果您的示例输入/输出旨在向我们展示您的文件在表格格式中的外观而不是您的实际文件 - 不要那样做,向我们展示实际文件,而不是它们的一些图形表示。如果这些确实是您的实际文件 - 您所说的分隔字段的选项卡在哪里?如果您的解决方案 2 脚本中的任何一个将字段附加到行尾,那是因为您的输入文件 不是 制表符分隔。
@EdMorton:完全同意你的观点,最后一个问题编辑导致了这一点。我最初将问题更新为制表符分隔并拒绝了当前存在的问题,但不知何故它进入了。请参阅此编辑历史记录***.com/posts/39142140/revisions
【参考方案1】:
对于保留格式的列内更新,您需要使用拆分功能。请注意,只有 GNU awk 支持带有第四个参数的 split 函数。
试试这个:
awk 'split($0, a, FS, seps) # split based on FS
a[5]="81115"; # Update the 5th column
for (i=1;i<=NF;i++) # print the data back
printf("%s%s", a[i], seps[i]) # keeping the separators
print ""' # print a new line
单线:
awk 'split($0, a, FS, seps); a[5]="81115"; for (i=1;i<=NF;i++) printf("%s%s", a[i], seps[i]); print ""' /tmp/data
归功于https://***.com/a/39326264/2032943
【讨论】:
++,但请添加说明您的解决方案需要 GNUawk
(根据 POSIX,split()
函数不支持第四个参数,并且Mawk 和 BSD/macOS Awk 都不支持)。【参考方案2】:
基于给定示例输入的最简单解决方案是使用sed
进行简单搜索和替换,假设第 5 列只有相同的 81216
值,并且该值不会出现在 1-4 列中的任何位置
$ sed 's/81216/81115/' file
203 ADD 24 IAC 81115 IT
204 ATT 24 IAC 81115 IT
如果需要替换第 5 列中的任何值,
sed -E 's/^((\S+\s+)4)\S+/\181115/' file
如果无法识别\s
和\S
,请使用
sed -E 's/^(([^[:space:]]+[[:space:]]+)4)[^[:space:]]+/\181115/' file
类似的解决方案可以与 GNU awk
一起使用,它具有 gensub
功能
awk '$0 = gensub(/^((\S+\s+)4)\S+/, "\\181115", "1", $0)1' file
或带变量,
awk -v replace='81115' '$0 = gensub(/^((\S+\s+)4)\S+/, "\\1"replace, "1", $0)1' file
以上所有解决方案都保留了输入文件空间格式
【讨论】:
【参考方案3】:注意:
- 如果您必须保留输入 中的 exact 分隔符字符串,并且 您拥有 GNU awk
,请参阅 @Sundeep's helpful answer,或者,涵盖所有字段的解决方案,请参阅Jay Rajput's helpful answer。
- 此答案试图诊断 OP 的问题,并且包含一个将输入转换为一致的制表符分隔输出的解决方案。
您的第一次尝试不会在输出中保留制表符,因为在没有设置 OFS
(输出字段分隔符)的情况下,Awk 将 输出 字段用 空格 每个。
(通过分配到一个字段,就像你对$5 = ...
所做的那样,输入行被隐式重建,使用OFS
的值(默认为空格)作为将(修改的)字段重新组合在一起以形成 output 行的分隔符。)
您的其他尝试看起来都很合理,这表明您的输入文件的结构可能不是您认为的那样。
使用cat -et
验证输入文件中的所有列是否确实由一个制表符分隔:^I
代表cat -et
输出中的一个制表符。
如果您的输入文件包含混合制表符和空格分隔的列和/或如果某些字段之间有多个制表符,您需要依靠awk
的默认解析将您的输入按预期拆分为字段,即通过任何非空空格运行。
然后您将制表符仅用作分隔符输出,方法是仅设置OFS
:
awk -v replace='81115' -v OFS='\t' '$5=replace1' file
请注意缺少 -F
选项,以便依赖 Awk 的默认字段拆分行为。
虽然这不一定会保持准确的输入格式,但您将获得一致制表符分隔的输出。
【讨论】:
可以添加gensub
等价于 sed -E 's/^((\S+\s+)4)\S+/\181115/' file
以保留空间格式...
@Sundeep: gensub
需要 GNU awk
,如果你可以使用它,那么@JayRajput 的答案是最好的方法。
我对语法不是很熟悉,但是awk '$0 = gensub(/^((\S+\s+)4)\S+/, "\\181115", "g", $0)1' file
似乎比使用split简单很多
@Sundeep:好点:您的解决方案更简单而且似乎有效(除了我建议使用"1"
而不是"g"
)。我鼓励您发布此命令作为您自己的答案 - 但请说明该解决方案需要 GNU awk
.以上是关于如何在不丢失 awk 格式的情况下替换整个列的主要内容,如果未能解决你的问题,请参考以下文章
如何在不替换 ES6/Javascript 中的整个属性的情况下深度复制对象 [重复]
在不丢失 Html 样式的情况下更改 NSAttributedString 中的字体大小 - Swift