使用 awk 或其他方法替换文件中的整个字段值

Posted

技术标签:

【中文标题】使用 awk 或其他方法替换文件中的整个字段值【英文标题】:Replace an entire field value in a file using awk or other 【发布时间】:2017-07-03 15:44:41 【问题描述】:

我有一个从 postgresql 表导出的多个字段,包括布尔值(由 postgresql 导出为 tf 字符),我需要将它导入另一个无法理解的数据库 (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 的简单转换就足够了?

Return Boolean Value as TRUE or FALSE in Select (PostgreSQL/pgAdmin)

关于regclass 和正确转义标识符:

Table name as a PostgreSQL function parameter

如果您需要一个完整的语句,其中 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 的颠覆转储文件

AWK - 使用多个文件查找和替换多个字段

使用 awk 或 sed 在文件中动态替换字符串

如何使用 sed/awk 替换逗号分隔字符串中的第 n 列/字段?

替换整个数据库 mysql 命令或方法

grep/sed/awk - 用新的计算值“$X/10”替换文件中的所有“$X”