如果列有多个值,请分别复制包含每个值的行

如果列有多个值,请分别复制包含每个值的行

我有一个具有以下格式的文件,每列由制表符分隔:

C1  C2  C3
a   b,c d
e   f,g,h   i
j   k   l
...

现在我需要根据第二列中用逗号分隔的值的数量(如果是这种情况)获得行数。这些行必须具有其中一个值,而不是其他值。结果是这样的:

C1  C2  C3
a   b   d
a   c   d
e   f   i
e   g   i
e   h   i
j   k   l
...
...

由于这是由于尽快工作,我刚刚做了一个不要在家里这样做while脚本,由于我缺乏 的技能awk,或者没有使用其他工具探索其他可能的解决方案,因此使用 a 逐行阅读。脚本如下:

同时我正在修改剧本

# DON'T DO THIS AT HOME SCRIPT
> duplicados.txt
while IFS= read -r line; do
  # get the value of the column of interest
  cues="$(echo "$line" | awk -F'\t' '{ print $18 }')"
  # if the column has commas then it has multiple values
  if [[ "$cues" =~ , ]]; then
    # count the commas
    c=$(printf "%s" "$cues" | sed 's/[^,]*//g' | wc -c)
    # loop according to the number of commas
    for i in $(seq $(($c + 1))); do
      # get each value of the column of interest according to the position
      cue="$(echo "$cues" | awk -F',' -v c=$i '{ print $c; ++c }')"
      # save the line to a file substituting the whole column for the value
      echo "$line" | sed "s;$cues;$cue;" >> duplicados.txt
    done
    continue
  fi
  # save the single value lines
  echo "$line" >> duplicados.txt
done < inmuebles.txt

这样我就得到了想要的结果(据我所知)。正如您可以想象的那样,该脚本速度缓慢且效率很低。我如何使用awk其他工具来做到这一点?

真实数据的样本如下所示,感兴趣的列是数字 18:

1409233 UNION   VIAMONTE    Estatal Provincial  DGEP    3321    VIAMONTE                            -33.7447365;-63.0997115 Rural Aglomerado    140273900   140273900-ESCUELA NICOLAS AVELLANEDA
1402961 UNION   SAN MARCOS SUD  Estatal Provincial  DGEA, DGEI, DGEP    3029, 3311, Z11 SAN MARCOS SUD                          -32.629557;-62.483976 / -32.6302699949582;-62.4824499999125 / -32.632417;-62.484932 Urbano  140049404, 140164000, 140170100, 140173100  140049404-C.E.N.M.A. N° 201 ANEXO SEDE SAN MARCOS SUD, 140164000-C.E.N.P.A. N° 13 CASA DE LA CULTURA(DOC:BERSANO), 140170100-ESCUELA HIPOLITO BUCHARDO, 140173100-J.DE INF. HIPOLITO BUCHARDO
1402960 UNION   SAN ANTONIO DE LITIN    Estatal Provincial  DGEA, DGEI, DGETyFP 3029, TZONAXI, Z11  SAN ANTONIO DE LITIN    3601300101020009    360102097366    0250347         SI / SI -32.212126;-62.635999 / -32.2122558;-62.6360432 / -32.2131931096409;-62.6291815804363   Rural Aglomerado    140049401, 140313000, 140313300, 140483400, 140499800   140049401-C.E.N.M.A. N° 201 ANEXO SAN ANTONIO DE LITIN, 140313000-I.P.E.A. Nº 214. MANUEL BELGRANO, 140313300-J.DE INF. PABLO A. PIZZURNO, 140483400-C.E.N.P.A. DE SAN ANTONIO DE LITIN, 140499800-C.E.N.P.A. B DE SAN ANTONIO DE LITIN

答案1

awk您可以通过拆分复合列,并循环结果来完成此操作:

awk -F'\t' 'BEGIN{OFS=FS} {n=split($2,a,/,/); for(i=1;i<=n;i++){$2 = a[i]; print}}' file

也许更干净,你可以这样做磨坊主- 特别是,使用嵌套动词

$ cat file
C1      C2      C3
a       b,c     d
e       f,g,h   i
j       k       l

$ mlr --tsv nest --explode --values --across-records --nested-fs ',' -f C2 file
C1      C2      C3
a       b       d
a       c       d
e       f       i
e       g       i
e       h       i
j       k       l

更紧凑的--explode --values --across-records --nested-fs ','可以替换为--evar ','

答案2

由于您还用 标记了问题sed,我觉得有必要添加一个sed解决方案:

sed -e '/,/{s//\n/;h;s/[^\t]*\n//;x;s/\n[^\t]*//p;G;D;}'

(注意:为了便于阅读,我使用了\n换行符和\t制表符,就像使用 GNU 一样sed。对于可移植的解决方案,请使用带有实际换行符的反斜杠,而不是使用\n实际制表符\t,输入ctrlV如下tab

带有逗号的行被复制到保留空间,一个副本打印逗号之前的内容,另一个副本打印逗号之后的部分进入下一个循环。详细地:

  • 为了避免与多个逗号混淆,我们用换行符替换一个逗号s//\n/
  • h在我们搞乱线路之前,将一份副本保存到旧空间
  • s/[^\t]*\n//删除第一个逗号之前的部分
  • 然后我们x改变缓冲区
  • s/\n[^\t]*//p删除从逗号开始的部分并打印它
  • G将保留空间附加到模式空间。这可以包含附加逗号,所以
  • D删除第一行(已打印)并从该行的其余部分重新开始

答案3

awk(或perlawk模式下)可能是最好的标准解决方案,但你ksh在大多数 shell 中,尤其是那些带有数组 ( , bash, zsh)的 shell 中,可以相当有效地执行此操作:

set -f # split but don't glob unquoted substitutions
#bash
while IFS=$'\t' read -ra ary; do 
#ksh
while read -r line; do IFS=$'\t'; ary=($line)
#zsh I haven't worked out

  IFS=,; for v in ${ary[17]}; do 
    ary[17]=$v; IFS=$'\t'; printf '%s\n' "${ary[*]}"
  done
  # bash,ksh arrays are 0-origin versus 1-origin fields in awk
  # we don't need to special-case no-comma, it splits to a single value
done <input >output

对于没有数组的旧的/有限的 shell,请使用位置参数,例如(可能会有所不同):

set -f
while read -r line; do IFS=$'\t'; set -- $line
  IFS=,; for v in ${18}; do
    # can't alter $num so yucky
    for i in $(seq $#); do
      case $i in (1);; (*) printf '\t';; esac
      case $i in (18) printf %s "$v";; (*) eval printf %s \"\${$i}\";; esac
    done
    # or maybe i=1; while [ $i -le $# ]; do ... i=$((i+1)); done
    # where [/test is likely shell builtin and seq is unlikely 
  done
done <input >output

答案4

 while read line
 do
 fic=$(echo $line | awk '{print $1}')
 laco=$(echo $line | awk '{print $NF}')
 secon_colu=$(echo $line| awk '$2 ~ /,/{print $2}')
 if [[ "$secon_colu" =~ "," ]]
 then
 for ko in $(echo $line | awk '$2 ~ /,/{print $2}'| sed 's/,/ /g')
 do
 echo "$fic $ko  $laco"
 done
 else
 echo $line
 fi
 done<file.txt

输出

C1 C2 C3
a b  d
a c  d
e f  i
e g  i
e h  i
j k l

相关内容