【问题标题】:Delimiter substitution excluding character inside string column分隔符替换不包括字符串列内的字符
【发布时间】:2013-02-08 11:32:10
【问题描述】:

我有一个这种格式的 3Gb 文件:

...
201211 001093223359 "PLANO ESPECIAL" "PLANO NOVO"
201211 001199175239 "PLANO ESPECIAL" "PLANO NOVO"
201211 001292676219 "PLANO ESPECIAL" "PLANO NOVO"
...

我需要将其格式更改为如下所示:

...
201211;001093223359;"PLANO ESPECIAL";"PLANO NOVO"
201211;001199175239;"PLANO ESPECIAL";"PLANO NOVO"
201211;001292676219;"PLANO ESPECIAL";"PLANO NOVO"
...

前 5 行与输入文件中的完全相同:

"Mes_Referencia" "Num_Telefone" "Dsc_Plano_Tarifario" "Grupo Plano"
201211 2183223350 "INFINITY PR?" "PLANO INFINITY"
201211 2169175232 "INFINITY PR?" "PLANO INFINITY"
201211 2182676211 "INFINITY PR?" "PLANO INFINITY"
201211 2281699337 "INFINITY PR?" "PLANO INFINITY"
201211 2179173096 "INFINITY PR?" "PLANO INFINITY"

评论: ?在“无限公关?”是因为它是“É”(它是葡萄牙语 - 巴西)。

如何将分隔符“”(空格)更改为“;”不改变最后两列字符串中的空格?

提前致谢!

【问题讨论】:

  • 我不知道如何包含折线。日期 201211 是第一个字段,字符串 PLANO NOVO 是每条记录的最后一个字段。
  • 列宽是变量吗?

标签: regex shell unix awk


【解决方案1】:

无论您的文件中有多少行,这都会起作用,因为它一次只处理一行:

awk 'BEGIN{FS=OFS="\""} {for (i=1;i<NF;i+=2) gsub(/ /,";",$i)} 1' file

例如:

$ cat file
"Mes_Referencia" "Num_Telefone" "Dsc_Plano_Tarifario" "Grupo Plano"
201211 2183223350 "INFINITY PR?" "PLANO INFINITY"
201211 2169175232 "INFINITY PR?" "PLANO INFINITY"
201211 2182676211 "INFINITY PR?" "PLANO INFINITY"
201211 2281699337 "INFINITY PR?" "PLANO INFINITY"
201211 2179173096 "INFINITY PR?" "PLANO INFINITY"
$
$ awk 'BEGIN{FS=OFS="\""} {for (i=1;i<NF;i+=2) gsub(/ /,";",$i)} 1' file
"Mes_Referencia";"Num_Telefone";"Dsc_Plano_Tarifario";"Grupo Plano"
201211;2183223350;"INFINITY PR?";"PLANO INFINITY"
201211;2169175232;"INFINITY PR?";"PLANO INFINITY"
201211;2182676211;"INFINITY PR?";"PLANO INFINITY"
201211;2281699337;"INFINITY PR?";"PLANO INFINITY"
201211;2179173096;"INFINITY PR?";"PLANO INFINITY"

【讨论】:

  • 在这种情况下我担心性能因为这个文件有 70.051.095 百万行!
  • @LucasRezende - 为什么你认为这会比任何其他解决方案都慢?
  • @Fredrik - 啊,谢谢。我会修复它以使用;s。所以习惯于将其视为 CSV 问题!
  • @LucasRezende - 在我的帖子中查看测量值。 Ed 的 awk 解决方案实际上是这里最快的解决方案(甚至超过了我的 sed)。接受的答案慢了 10 倍!
【解决方案2】:

通过以下命令过滤您的文件:

sed -E -e 's/ ([^ "]*|"[^"]*")/;\1/g'

此命令假定第一列没有被引用。如果可以的话,正则表达式会稍微复杂一些。

示例输入:

201211 2183223350 "INFINITY PRE" "PLANO INFINITY"
201211 2182067250 "ASS. PLANO NOSSO MODO-G11" "OUTROS"
201211 8199712912 "TIM LIBERTY CONTROLE" "PLANO LIBERTY"

样本输出:

201211;2183223350;"INFINITY PRE";"PLANO INFINITY"
201211;2182067250;"ASS. PLANO NOSSO MODO-G11";"OUTROS"
201211;8199712912;"TIM LIBERTY CONTROLE";"PLANO LIBERTY"

【讨论】:

  • 几乎......就像:201211 2183223350 "INFINITY PRE" "PLANO INFINITY" 201211 2182067250 "ASS. PLANO NOSSO MODO-G11" "OUTROS" 201211 8199712912 "TIM LIBERTY CONTROLE" "PLANO LIBERTY" 变成了:201211;2183223350;"INFINITY;PRE";"PLANO INFINITY" 201211;2182067250;"ASS. PLANO NOSSO MODO-G11";"OUTROS" 201211;8199712912;"TIM LIBERTY CONTROLE";"PLANO LIBERTY"
  • @LucasRezende 对我来说很好。查看示例输出。
  • @LucasRezende Mikhail 的版本在我这边产生了正确的样本输出。
  • 真的吗?这是我用来进行测试的完整命令:head -30 Liberty_Infinity.txt | sed -E -e 's/ ([^ "]*|"[^"]*")/;\1/g' &gt; TESTE_LIBERTY.txt
  • head -5 Liberty_Infinity.txt 201211 2183223350 “无限公关?” “PLANO INFINITY” 201211 2169175232 “INFINITY PR?” “PLANO INFINITY” 201211 2182676211 “INFINITY PR?” “PLANO INFINITY” 201211 2281699337 “INFINITY PR?” “普莱诺无限”
【解决方案3】:

如何将前两个空格替换为;,并将每个" " 替换为";"

$ sed 's/ /;/;s/ /;/;s/" "/";"/g' file
201211;001093223359;"PLANO ESPECIAL";"PLANO NOVO"
201211;001199175239;"PLANO ESPECIAL";"PLANO NOVO"
201211;001292676219;"PLANO ESPECIAL";"PLANO NOVO"

使用-i 开关进行内联更改。

使用 30000003 行文件的一些计时:

$ time sed 's/ /;/;s/ /;/;s/" "/";"/g' f1 > /dev/null

real    1m58.305s
user    1m54.811s
sys 0m1.488s

$ time awk 'BEGIN{FS=OFS="\""} {for (i=1;i<NF;i+=2) gsub(/ /,",",$i)} 1' f1 > /dev/null

real    1m46.916s
user    1m45.831s
sys 0m0.852s


$ time sed -E -e 's/ ([^ "]*|"[^"]*")/;\1/g' f1 > /dev/null

real    20m52.172s
user    20m47.430s
sys 0m2.536s

BIG 对贪婪运算符和反向引用的惩罚!

【讨论】:

    【解决方案4】:

    awk 应该可以解决问题。

    awk -v OFS=";" '{print $1,$2,$3" "$4,$5" "$6}'

    【讨论】:

    • 它应该可以工作,但问题是字符串字段并不总是这样。空格数发生变化。例如,这可能是“PLANO ESPECIAL NATAL”或“PLANO TARIFADO PROMOCAO FALE MAIS ILIMITADO”。
    • 忽略“”之间的空格必须是动态的。 =/
    • 我不太清楚。这是电信公司的一个月通话!我猜选项太多了:S
    • 我在想是否可以在同一行中执行两个 awk 命令。例如(它不起作用!):head -30 file.txt | awk -F" " '{ print $1";"$2";"}' && awk -F"\"" '{ print $2 ..... }'
    • 也许将 sed 与正则表达式混合... sed "(space)" to ";"其中“(空格)”不在“”之间。我只是不知道这是否可能。
    【解决方案5】:

    试试:

    awk 'NR%2{gsub(/[ \t]+/,";")}1' RS=\" ORS=\" file
    

    【讨论】:

    • 不错的方法,但会在输出末尾添加换行符和双引号。
    • 很好 - 谢谢.. 这只是一个双引号,不是吗?
    • 我认为它会添加什么取决于文件中的最后一行是否以 " 结尾,以及它是偶数行还是奇数行也可能会影响它。关键是你'd 需要稍微调整脚本以使其永远不会添加任何内容...在 GNU awk IIRC 中,您通常通过测试 RT 来做到这一点。
    • 我找不到偶数行和奇数行之间的区别。 RT 和 IIRC 是什么意思?想到一个可能的快速解决方法:awk 'END{printf "\n"} NR%2{gsub(/[ \t]+/,";")}NF' RS=\" ORS=\" file,但是常规的 for 循环可能更直接..
    • IIRC 是“如果我没记错的话”。 gawk 中的 RT 是记录终止符 - 这是与当前记录的 RS 正则表达式匹配的字符串,因此不会为输入文件中最后一个 " 之后的换行符设置它,因为它后面没有 RS。尝试awk 'NR%2{gsub(/[ \t]+/,";")}RT; END{printf "\n"}' RS=\" ORS=\" file 但是我认为使用同样的方法可能有一个更简洁的脚本。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-13
    • 2010-10-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多