【问题标题】:split line into multiple via splitting specific field通过拆分特定字段将行拆分为多个
【发布时间】:2013-08-28 00:21:59
【问题描述】:

我有多行,例如:

"390";"902";"from 4670000 to 4679999, from 4680000 to 4689999, from 9960000 to 9969999";"something1";"something2";"20.09.04"
"390";"903";"from 0770000 to 0779999, from 9170000 to 9179999";"something3";"something4";"09.09.04"

我需要的是:

"390";"902";"from 4670000 to 4679999";"something1";"something2";"20.09.04"
"390";"902";"from 4680000 to 4689999";"something1";"something2";"20.09.04"
"390";"902";"from 9960000 to 9969999";"something1";"something2";"20.09.04"
"390";"903";"from 0770000 to 0779999";"something3";"something4";"09.09.04"
"390";"903";"from 9170000 to 9179999";"something3";"something4";"09.09.04"

如您所见,我需要在 from/to 标记上拆分 variable3(注意有时“,”之间有一个空格)。

理想情况下,我需要结果输出:

"390";"902";"4670000";"4679999";"something1";"something2";"20.09.04"
"390";"902";"4680000";"4689999";"something1";"something2";"20.09.04"
"390";"902";"9960000";"9969999";"something1";"something2";"20.09.04"
"390";"903";"0770000";"0779999";"something3";"something4";"09.09.04"
"390";"903";"9170000";"9179999";"something3";"something4";"09.09.04"

我已经发现我可以通过 awk 进行拆分,但我不确定如何复制该行的其余部分:

awk -F\, '{                       
  for (i = 0; ++i <= NF;)
    print i, $i
  }' <<<'from 4670000 to 4679999, from 4680000 to 4689999, from 9960000 to 9969999'
1 from 4670000 to 4679999
2  from 4680000 to 4689999
3  from 9960000 to 9969999

对不起,这是我在这里的第一个问题,请随时指出我应该如何更正它才能得到完整的回答。

谢谢!

【问题讨论】:

  • +1 用于样本输入、所需输出和(喘气),尝试解决您的问题。继续发帖!
  • 对不起,下次我会添加更多示例!

标签: bash sed awk split


【解决方案1】:
#!/bin/bash

filename='file.txt'
temp=$(mktemp)

sed 's/, */";"/g' "$filename" > "$temp" # replace commas with ;

echo -n > "$filename" # clear our file
while read line; do
    IFS=';' read -a fields <<< "$line" # make an array out of the string

    for ((i=2; i<${#fields[@]}-3; i++)); do
        from=$(echo "${fields[$i]}" | cut -d ' ' -f2)
        to=$(echo "${fields[$i]}" | cut -d ' ' -f4)
        echo "${fields[0]};${fields[1]};\"$from\";\"$to;${fields[-3]};${fields[-2]};${fields[-1]}" >> "$filename"
    done
done < "$temp"

rm "$temp"

exit 0

它也将处理逗号前的空格。

【讨论】:

  • 不幸的是,这不是我需要的。请更多地查看我给出的示例,我需要通过第三个字段拆分行并复制该行的其余部分。我已经用 PHP 编写了一些代码,因为我在 sed/awk 方面不是那么好,一旦我得到了解决方案,我会在这里发布。
  • omg,您正在创建的所有这些子进程来解析一行文本!?在使用 shell 和 $()((...)) 构造时为胆大妄为 +1,但要了解 ${var/s/r}、${var%x}、${var%%x}、${var#y} , ${var##y} (他们可能在这个问题上的适用性有限,你可能已经知道了)。可悲的是,子进程为 -1,所以 . . . .洗个澡。忏悔,忏悔和学习awk(或python或perl);-)祝大家好运。
  • @shellter 当然有更好的方法使用 awk,但它们的可读性也较差。只是想提供一些人可能喜欢的替代方案。
  • @Aleks-DanielJakimenko 我比 Awk 更喜欢 Bash,但这次我不认为 Bash 可能比 Awk 更简单,而且更具可读性。另外,我认为使用剪切和命令替换是一种浪费。您可以只使用特殊的参数扩展方法,例如 ${X##Y} 等。
  • 如果something* 包含逗号或分号或反斜杠或...将失败。shell 用于对工具的调用进行排序,而不是用于解析文本文件 - 这就是 awk 的发明目的和非常擅长。
【解决方案2】:

这是使用 的一种方式。我知道您没有标记它,但对我来说,使用良好的解析器处理 csv 文件似乎更容易。它将第三个字段 (row[2]) 用逗号分隔,然后将该字段的每个字符串拆分为空格并提取奇数 (v.split()[1::2])。

script.py的内容:

#!/usr/bin/env python3

import csv
import sys
import copy

with open(sys.argv[1], 'r') as f:
        csvfile = csv.reader(f, delimiter=';')
        csvout = csv.writer(sys.stdout, delimiter=';', quoting=csv.QUOTE_ALL)
        for row in csvfile:
                v3 = row[2].split(r', ')
                for v in v3:
                        newrow = copy.deepcopy(row)
                        fields = v.split()[1::2]
                        newrow[2:3] = fields
                        csvout.writerow(newrow)

像这样运行它:

python3 script.py infile

产生:

"390";"902";"4670000";"4679999";"something1";"something2";"20.09.04"
"390";"902";"4680000";"4689999";"something1";"something2";"20.09.04"
"390";"902";"9960000";"9969999";"something1";"something2";"20.09.04"
"390";"903";"0770000";"0779999";"something3";"something4";"09.09.04"
"390";"903";"9170000";"9179999";"something3";"something4";"09.09.04"

【讨论】:

    【解决方案3】:

    awk 单行:

    awk -F'";"' -v OFS='";"' '{n=split($3,a,/,\s*/);for(i=1;i<=n;i++){$3=a[i];print}}' file
    

    输出:

    kent$  cat f
    "390";"902";"from 4670000 to 4679999, from 4680000 to 4689999, from 9960000 to 9969999";"something1";"something2";"20.09.04"
    "390";"903";"from 0770000 to 0779999, from 9170000 to 9179999";"something3";"something4";"09.09.04"
    
    kent$  awk -F'";"' -v OFS='";"' '{n=split($3,a,/,\s*/);for(i=1;i<=n;i++){$3=a[i];print}}' f
    "390";"902";"from 4670000 to 4679999";"something1";"something2";"20.09.04"
    "390";"902";"from 4680000 to 4689999";"something1";"something2";"20.09.04"
    "390";"902";"from 9960000 to 9969999";"something1";"something2";"20.09.04"
    "390";"903";"from 0770000 to 0779999";"something3";"something4";"09.09.04"
    "390";"903";"from 9170000 to 9179999";"something3";"something4";"09.09.04"
    

    编辑

    如果你也想解析 from...to,仍然是一个 awk oneliner:

    awk -F'";"' -v OFS='";"' '{n=split($3,a,/,\s*/);for(i=1;i<=n;i++)
    {$3=a[i];sub(/\s*to\s*/,"\";\"",$3);sub(/\s*from\s*/,"",$3);print}}' file
    

    使用相同的输入文件进行测试:

    kent$  awk -F'";"' -v OFS='";"' '{n=split($3,a,/,\s*/);for(i=1;i<=n;i++){$3=a[i];sub(/\s*to\s*/,"\";\"",$3);sub(/\s*from\s*/,"",$3);print}}' f                              
    "390";"902";"4670000";"4679999";"something1";"something2";"20.09.04"
    "390";"902";"4680000";"4689999";"something1";"something2";"20.09.04"
    "390";"902";"9960000";"9969999";"something1";"something2";"20.09.04"
    "390";"903";"0770000";"0779999";"something3";"something4";"09.09.04"
    "390";"903";"9170000";"9179999";"something3";"something4";"09.09.04"
    

    【讨论】:

    • 请注意,他也希望解析“from .. to ..”字段。
    • @Aleks-DanielJakimenko thx,我没有注意到他实际上有两个预期的输出有问题。我添加了那部分。
    • 由于某种原因,您的第二个示例在第三个和第四个变量中留下了空格,例如:gawk -F'";"' -v OFS='";"' '{n=split($3,a,/,\s*/);for(i=1;i&lt;=n;i++){$3=a[i];sub(/\s*to\s*/,"\";\"",$3);sub(/\s*from\s*/,"",$3);print}}' |head -2 "390";"901";" 6000000 ";" 6009999";"ОАО \"Ростелеком\" (ОАО \"Сибирьтелеком\")";"Республика Хакасия";"13.05.09" "";"954";" 1010000 ";" 1019999";"ЗАО \"Джикая ФФЭерика Хакасия";" ";"04.06.12"
    【解决方案4】:

    对于输入:

    "390";"902";"from 4670000 to 4679999, from 4680000 to 4689999, from 9960000 to 9969999";"something1";"something2";"20.09.04"
    "390";"903";"from 0770000 to 0779999, from 9170000 to 9179999";"something3";"something4";"09.09.04"
    

    这段代码

    #!/usr/bin/awk -f
    
    BEGIN {
        FS = ";"
    }
    
    {
        t = $3
        gsub(/"/, "", t)
        n = split(t, a, /, /)
        for (i = 1; i <= n; ++i) {
            print $1 ";" $2 ";\"" a[i] "\";" $4 ";" $5 ";" $6
        }
    }
    

    会给

    "390";"902";"from 4670000 to 4679999";"something1";"something2";"20.09.04"
    "390";"902";"from 4680000 to 4689999";"something1";"something2";"20.09.04"
    "390";"902";"from 9960000 to 9969999";"something1";"something2";"20.09.04"
    "390";"903";"from 0770000 to 0779999";"something3";"something4";"09.09.04"
    "390";"903";"from 9170000 to 9179999";"something3";"something4";"09.09.04"
    

    浓缩形式(我认为它不能真正称为真正的“单线”):

    awk -F ";" -- '{ t = $3; gsub(/"/, "", t); n = split(t, a, /, /); for (i = 1; i <= n; ++i) print $1 ";" $2 ";\"" a[i] "\";" $4 ";" $5 ";" $6 }'
    

    还有这段代码

    #!/usr/bin/awk -f
    
    BEGIN {
        FS = ";"
    }
    
    {
        t = $3
        gsub(/"|from /, "", t)
        n = split(t, a, /, | to /)
        for (i = 1; i <= n; i += 2) {
            print $1 ";" $2 ";\"" a[i] "\";\"" a[i + 1] "\";"$4 ";" $5 ";" $6
        }
    }
    

    愿意

    "390";"902";"4670000";"4679999";"something1";"something2";"20.09.04"
    "390";"902";"4680000";"4689999";"something1";"something2";"20.09.04"
    "390";"902";"9960000";"9969999";"something1";"something2";"20.09.04"
    "390";"903";"0770000";"0779999";"something3";"something4";"09.09.04"
    "390";"903";"9170000";"9179999";"something3";"something4";"09.09.04"
    

    精简形式:

    awk -F ";" -- '{ t = $3; gsub(/"|from /, "", t); n = split(t, a, /, | to /); for (i = 1; i <= n; i += 2) print $1 ";" $2 ";\"" a[i] "\";\"" a[i + 1] "\";"$4 ";" $5 ";" $6; }'
    

    使用 gawk、nawk 和 mawk 测试脚本。

    【讨论】:

    • 不适用于该输入:"390";"903";"from 0770000 to 0779999,from 9170000 to 9179999";"*";"something4";"09.09.04"
    • @Aleks-DanielJakimenko 我不认为这是示例的一部分。
    • 他说As you can see I need to split variable3 on from/to tag (NOTE there is a space sometimes between ",").,我理解为“有时','周围有空格,有时没有空格”
    • 假设它只是有时而不是总是那么你是正确的。但是,可以通过将任何 , 表达式替换为 , ? 来轻松解决。
    【解决方案5】:

    这是在 Bash 中的另一种方式:

    #!/bin/bash
    
    shopt -s extglob
    
    IFS=';'
    
    while read -a FIELDS; do
        TEMP=${FIELDS[2]//\"}
        read -a RANGES <<< "${TEMP//,?( )/;}"
        for A in "${RANGES[@]}"; do
            echo "${FIELDS[0]};${FIELDS[1]};\"$A\";${FIELDS[*]:3}"
        done
    done
    

    运行

    bash script.sh < file
    

    这将给出第一个预期的输出。

    或者

    #!/bin/bash
    
    shopt -s extglob
    
    IFS=';'
    
    while read -a FIELDS; do
        TEMP=${FIELDS[2]//@(\"|from )}
        read -a RANGES <<< "${TEMP//@(,?( )| to )/;}"
        for (( I = 0; I < ${#RANGES[@]}; I += 2 )); do
            echo "${FIELDS[0]};${FIELDS[1]};\"${RANGES[I]}\";\"${RANGES[I + 1]}\";${FIELDS[*]:3}"
        done
    done
    

    这将获得第二个预期的输出。

    【讨论】:

    • 例如,如果something1 表示的文本包含分号,则会失败。如示例输入中所示,使用 3 字符字符串 ";" 分隔字段会更可靠。
    • @EdMorton 好的,我对这种可能性进行了更新。该解决方案不一定需要像您的建议那样,因为即使在文本解析或字符串操作方面,Bash 也比 Awk 具有优势。
    • 有趣,这不是我期望的解决方案,我认为它会是sed 's/";"/&lt;some char&gt;/g' | script | sed 's/&lt;some char&gt;/";"/g'。我实际上看不到那个解决方案在做什么!
    【解决方案6】:
    $ cat tst.awk
    BEGIN{ FS=OFS="\";\"" }
    {
        gsub(/from /,"",$3)
        split($3,a,/ *, */)
        for (i=1;i in a;i++) {
            $3 = a[i]
            sub(/ to /,OFS,$3)
            print
        }
    }
    $
    $ awk -f tst.awk file
    "390";"902";"4670000";"4679999";"something1";"something2";"20.09.04"
    "390";"902";"4680000";"4689999";"something1";"something2";"20.09.04"
    "390";"902";"9960000";"9969999";"something1";"something2";"20.09.04"
    "390";"903";"0770000";"0779999";"something3";"something4";"09.09.04"
    "390";"903";"9170000";"9179999";"something3";"something4";"09.09.04"
    

    【讨论】:

      【解决方案7】:

      这可能对你有用(GNU sed):

      sed -r 's/, /","/g;s/^(([^;]*;){2})([^,]*),([^;]*)(.*)/\1\3\5\n\1\4\5/;P;D' file
      

      【讨论】:

        猜你喜欢
        • 2016-07-25
        • 1970-01-01
        • 2022-01-04
        • 1970-01-01
        • 2018-12-06
        • 1970-01-01
        • 1970-01-01
        • 2023-03-19
        • 1970-01-01
        相关资源
        最近更新 更多