使用 awk 或其他方法替换文件中的整个字段值
Posted
技术标签:
【中文标题】使用 awk 或其他方法替换文件中的整个字段值【英文标题】:Replace an entire field value in a file using awk or other 【发布时间】:2017-07-03 15:44:41 【问题描述】:我有一个从 postgresql 表导出的多个字段,包括布尔值(由 postgresql 导出为 t
和 f
字符),我需要将它导入另一个无法理解的数据库 (monetdb) f 作为布尔值。
(EDIT删除了空格以反映真实的文件方面并避免愤怒的 cmets - 以前显示空格)
id|val_str|bool_1|bool2|bool_3|bool4|
1|help|t|t|f|t|
2|test|f|t|f|f|
...
由于我无法替换所有出现的 t
/f
我需要在我的模式中集成字段分隔符。
我尝试使用awk
将字段t
替换为TRUE
并将f
替换为FALSE
:
awk -F'|' 'gsub(/\|t\|/, "|TRUE|"); gsub(/\|f\|/, "|FALSE|"); print;'
这是部分工作,因为具有相同值 (|t|t|
) 的连续字段只会替换第一个匹配项(|TRUE|t|
- 因为第二个匹配项实际上是 t|
而不是 |t|
)。
id|val_str|bool_1|bool2|bool_3|bool4|
1|help|TRUE|t|FALSE|TRUE|
2|test|FALSE|TRUE|FALSE|f|
...
表有大约 450 列,所以我不能真正指定要替换的列列表,也不能在 postgres 中“转换”布尔列(我可以但......)。
我可以运行 gsub()
两次,但我正在寻找更优雅的方式来匹配所有字段的整个字段内容。
gsub(/^t$/, ...)
也无济于事,因为我们大部分时间都在排队。
【问题讨论】:
您是否考虑过让 postgresql 输出所需格式的可能性?我本来希望有一个标志或其他一些参数使它变得容易,它看起来像there's no such thing but it's still possible。[Original file has no space]
是什么意思?如果您发布的示例输入与您的真实数据格式不同,那么显然,请修复它,否则您将获得一个比必要的更复杂的解决方案,或者一个不起作用的解决方案,任何一种方式都会浪费人们的时间。如果这只是一个完全不相关的陈述,那就去掉它。
我认为它会更容易阅读。
【参考方案1】:
表格有大约 450 列,所以我不能真正指定列的列表 被替换,也不能在 postgres 中“转换”布尔列(我 可以但...)。
您可以让 Postgres 为您完成工作。生成SELECT
列表的基本查询:
SELECT string_agg(CASE WHEN atttypid = 'bool'::regtype
THEN quote_ident(attname) || '::text'
ELSE quote_ident(attname) END, ', ' ORDER BY attnum)
FROM pg_attribute
WHERE attrelid = 'mytable'::regclass -- provide table name here
AND attnum > 0
AND NOT attisdropped;
产生如下形式的字符串:
col1, "CoL 2", bool1::text, "Bool 2"::text
所有标识符都已正确转义。列按默认顺序排列。复制并执行它。使用COPY
导出到文件。 (或 psql 中的 \copy
。)性能与导出普通表大致相同。如果不需要大写,请忽略upper()
。
为什么对text
的简单转换就足够了?
关于regclass
和正确转义标识符:
如果您需要一个完整的语句,其中 TRUE
/ FALSE
/ NULL
大写,标准 SQL 转换表示法(不带冒号 ::
),仍然是原始列名,可能还有模式限定的表名:
SELECT 'SELECT '
|| string_agg(CASE WHEN atttypid = 'bool'::regtype
THEN format('upper(cast(%1$I AS text)) AS %1$I', attname)
ELSE quote_ident(attname) END, ', ' ORDER BY attnum)
|| ' FROM myschema.mytable;' -- provide table name twice now
FROM pg_attribute
WHERE attrelid = 'myschema.mytable'::regclass
AND attnum > 0
AND NOT attisdropped;
产生一个完整的陈述形式:
SELECT col1, "CoL 2", upper(cast(bool1 AS text) AS bool1, upper(cast("Bool 2" AS text)) AS "Bool 2" FROM myschema.mytable;
【讨论】:
"我可以但是..." => 现在我可以了! DBeaver(JDBC 客户端)抱怨“意外符号 (:)”,但使用psql
很好,除了我需要添加列名:THEN 'upper(' || quote_ident(attname) || '::text) AS ' || quote_ident(attname)
以获得正确的列名。或者删除upper
。
@ant1j:我不确定您是否需要大写和/或原始列名。至于短转换表示法 (col::text
),您可以将其替换为 SQL 标准语法 cast(col AS text)
。考虑上面的更新。
短转换符号通常对 DBeaver 来说很好,我不知道他为什么抱怨这个...感谢更新【参考方案2】:
假设(基于您的 cmets)您的输入文件实际上与您发布的示例不同,而是如下所示:
$ cat file
id|val_str|bool_1|bool2|bool_3|bool4|
1|help|t|t|f|t|
2|test|f|t|f|f|
那么你只需要:
$ awk 'while(gsub(/\|t\|/,"|TRUE|")); while(gsub(/\|f\|/,"|FALSE|"));1' file
id|val_str|bool_1|bool2|bool_3|bool4|
1|help|TRUE|TRUE|FALSE|TRUE|
2|test|FALSE|TRUE|FALSE|FALSE|
N 个替换字符串的一般解决方案是:
$ awk 'BEGINm["f"]="FALSE"; m["t"]="TRUE" for (k in m) while(gsub("\\|"k"\\|","|"m[k]"|")); 1' file
id|val_str|bool_1|bool2|bool_3|bool4|
1|help|TRUE|TRUE|FALSE|TRUE|
2|test|FALSE|TRUE|FALSE|FALSE|
【讨论】:
【参考方案3】:如果perl
没问题,您可以使用环视:
$ cat ip.txt
id | val_str | bool_1 | bool2 | bool_3 | bool4 |
1 | help | t | t | f | t |
2 | test | f | t | f | f |
$ perl -pe 's/\|\K\h*t\h*(?=\|)/ TRUE /g; s/\|\K\h*f\h*(?=\|)/ FALSE /g' ip.txt
id | val_str | bool_1 | bool2 | bool_3 | bool4 |
1 | help | TRUE | TRUE | FALSE | TRUE |
2 | test | FALSE | TRUE | FALSE | FALSE |
\|\K
正向向后看以匹配 |
\h*
可选水平空间,如果输入中不存在则删除
(?=\|)
正向前瞻匹配 |
也可以通过sed
使用循环。在GNU sed 4.2.2
上测试,语法可能因其他实现而异
$ sed ':a s/| *t *|/| TRUE |/;ta; :b s/| *f *|/| FALSE |/;tb' ip.txt
id | val_str | bool_1 | bool2 | bool_3 | bool4 |
1 | help | TRUE | TRUE | FALSE | TRUE |
2 | test | FALSE | TRUE | FALSE | FALSE |
:a
标签
s/| *t *|/| TRUE |/
替换命令
ta
分支到标签 a
只要替换命令成功
同样适用于:b
输入中没有空格
perl -pe 's/\|\Kt(?=\|)/TRUE/g; s/\|\Kf(?=\|)/FALSE/g' ip.txt
sed ':a s/|t|/|TRUE|/;ta; :b s/|f|/|FALSE|/;tb' ip.txt
awk 'BEGINFS=OFS="|" for(i=1;i<=NF;i++)if($i=="t")$i="TRUE" if($i=="f")$i="FALSE" print' ip.txt
【讨论】:
源文件实际上没有空间。对我来说看上去很好。不熟悉 perl 但我想我可以将输出发送到管道 STDOUT ? 是的,这就像任何其他命令一样...输出可以通过管道传输 又来了 -source file has no space in fact.
。听起来您要求我们帮助您解决一个(由于空白)比您真正遇到的简单得多的问题更难解决的问题。
现在我也有一个解决空间的办法......谢谢大家【参考方案4】:
使用sed
,这是标准的。
sed 's/| *t */| TRUE /g;s/| *f */| FALSE /g'
这告诉sed
用| TRUE
替换以管道字符、未知数量的空格(可能为零)、t
和后跟未知数量的空格的每个子字符串;与f
相同。
如果线路长度搞砸了,通过column -t
管道输出。
【讨论】:
这不适用于所有情况...例如:echo 'abc| t 12| t |' | sed 's/| *t */| TRUE /g'
究竟是什么是 t 12
?
假设列数据...您的解决方案未检查两侧的|
边界
@Sundeep 此处唯一可以包含空格的数据是字符串。字符串通常以引号开头,因此与正则表达式不匹配。顺便说一句,您的解决方案也不会检查类似的东西:| "HELLO IMMA STRING! | t |" |
这是真的.. 如果|
不能安全地用作分隔符,使用正则表达式将是一个坏主意以上是关于使用 awk 或其他方法替换文件中的整个字段值的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 sed/awk 或其他工具辅助查找和替换 12GB 的颠覆转储文件