【问题标题】:How can I split files by grouping the same lines?如何通过分组相同的行来拆分文件?
【发布时间】:2022-08-18 15:11:01
【问题描述】:

如何通过使用 shell 脚本或 awk 对相同的行进行分组来拆分文件?

例如,我有 1 个文件,其内容如下:

1,1,1,1
2,2,2,2
3,3,3,3
x,x,x,x
x,x,x,x
x,x,x,x
x,x,x,x
y,y,y,y
y,y,y,y
y,y,y,y
4,4,4,4
5,5,5,5

我想要的是:所有相等的行都是一个组,并且必须在一个单独的文件中,其他不同的行需要在一个拆分文件中,直到特定限制。例如,如果我的特定限制为 10,则必须将原始文件拆分为包含数字的所有行,直到限制为 10(<= 10),如果不同的行数超过限制,则创建另一个拆分文件并很快。

对于包含字母的相等行,我需要它们有自己的单独文件。所以一个文件只用于 x,x,x,x 行,另一个用于 y,y,y,y 行等等。

行的内容只是示例,实际情况是一个 CSV,其中包含我需要按特定列值分组的所有列的不同值(我为此使用 sort 和 uniq),但无论如何我需要拆分这个 csv通过相等的行组和不同的行 <= 使用 shell 脚本或 awk 限制(我看到 awk 提供了更好的性能)。

你有什么主意吗?

我当前的代码是(它保留第一行,因为我正在考虑 csv 有一个标题):

#!/bin/bash
COLUMN=$1
FILE=$2
LIMIT=$3
FILELENGTH=`wc -l < $FILE`
COUNTER=$LIMIT
NUMS=\"\"
SORTED=\"sorted_\"`basename $FILE`

sort -t, -k $COLUMN -n $FILE > $SORTED
while [ $COUNTER -le $FILELENGTH ]; do
        NUMS+=`uniq -c $SORTED | awk -v val=$COUNTER \'($1+prev)<=val {prev+=$1} END{print prev}\'`
        NUMS+=\" \"
        ((COUNTER+=LIMIT))
        echo $NUMS \"|\" $COUNTER \"|\" $FILELENGTH \"|\" $SORTED
done

awk -v nums=\"$NUMS\" -v fname=`basename $2` -v dname=`dirname $2` \'
   NR==1 { header=$0; next}
   (NR-1)==1 {
        c=split(nums,b)
        for(i=1; i<=c; i++) a[b[i]]
        j=1; out = dname\"/\" \"splited\" j \"_\"fname
        print header > out
        system(\"touch \"out\".fin\")
    }
    { print > out }
    NR in a {
        close(out)
        out = dname \"/\" \"splited\" ++j \"_\"fname
        print header > out
        system(\"touch \"out\".fin\")
    }\' $SORTED
  • 如果您的真实数据中没有全部为 .s 的行,则不要在示例数据中放置此类行 (.......)。只需给我们一个minimal reproducible example,其中包含简洁、可测试的样本输入和预期输出,我们可以复制/粘贴以进行测试(无需编辑出一堆无用的....... 行),以便我们为您提供帮助。
  • 您说I have specific limit as 10,但我们不想看到一个包含多个 10+ 行块的冗长示例,只需将其设置为 3,例如,您的问题 - 您可以将 3 更改为 10 或稍后您想要的任何其他内容。
  • 正如您使用的bash tag 指示的那样,您应该将脚本复制/粘贴到shellcheck.net 并修复它告诉您的问题。另请阅读correct-bash-and-shell-script-variable-capitalization

标签: bash shell unix awk sh


【解决方案1】:

使用 GNU awk,您可以尝试按照您显示的示例编写的以下代码。在这里输入 2 遍 Input_file。对于在 Input_file 中多次出现的行,它们的输出文件将使用名称创建,例如:firstfieldValue.outFile,并且唯一的文件(在您的 Input_file 中仅出现 1 次)将使用名称创建,例如:1.singleOccurrence.outFile、@987654324 @ 等等。

awk '
BEGIN{
  count1="1"
  FS=OFS=","
}
FNR==NR{
  arr[$0]++
  next
}
arr[$0]>1{
  print > ($1".outFile")
  next
}
{
  count1+=(++count2%10==0?1:0)
  print > (count1".singleOccurrence.outFile")
}
'  Input_file  Input_file


或者要将标题(Input_file 的第一行)保留到每个输出文件中,请尝试遵循awk 代码,在上面的代码中稍作调整:

awk '
BEGIN{
  count1="1"
  FS=OFS=","
}
FNR==1{ headers = $0; next }
FNR==NR && FNR>1{
  arr[$0]++
  next
}
arr[$0]>1{
  if(!arr1[$0]++){ print headers > ($1".outFile") }
  print > ($1".outFile")
  next
}
{
  count1+=(++count2%10==0?1:0)
  if(prev!=count1){print headers > count1".singleOccurrence.outFile"}
  print > (count1".singleOccurrence.outFile")
  prev=count1
}
'  Input_file  Input_file

【讨论】:

  • 好的!谢谢!在我的真实场景中,我需要按列值拆分比较,所以我相信它只是将 arr[$0] 更改为 arr[$columnNumber] 对吗?以及如何在这些文件中保留标题?最后一个问题,如果我在原始文件中有 100 万行,我该如何管理错误 awk: cannot open "1021.singleOccurrence.outFile" for output (Too many open files)?
  • @bmelo,对于您在这里的第一个查询,我需要更好地理解它(所以您的意思是您要按字段值或字段编号保存文件?对于您的第二个查询,请使用 GNU awk(或最好称为 gawk)的当时的最新版本我们不应该打开太多文件错误恕我直言,让我知道。
  • @bmelo,除了我在之前的评论中提出的问题之外,对于要保留在每个输出文件的第一行中的标题?
  • 1] 例如,我需要按第三列对文件进行分组,因此第三列包含不同值的所有行将被有限的 <= 10 个文件分割,而其他文件中具有相等值的文件 2] 好吧,我的gawk 版本仅限于旧版本,因为...嗯,生产服务器... =/ 3] 是的,原始文件的第一行需要在所有其他文件中复制。
  • @bmelo,对于您的组查询,如果您希望获得文件名的 3 个字段/列,则将其从 arr[$0]++ 更改为 arr[$3]++ 并从 arr[$0]&gt;1 更改为 arr[$3]&gt;1。第二:这需要 gawk 最新版本,至少在沙箱中尝试(非产品)将尝试提出非 gnu(但这将非常冗长且效率较低,相信我)。第三:我已经添加了解决方案(或在我的答案中添加了一个),这样可以将标题保留在输出文件中,您只需按照我的第一点进行更改。试试这些,让我知道它是怎么回事,干杯。
【解决方案2】:
 awk -F, -v limit=3 '
    BEGIN{i=1}
    NR==1{
        header=$0                                       # save the header
        next                                            # go to next line
    }
    FNR==NR{                                            # process letters-lines
        if(f!=$0) print header " > " "tmp/file_" $1     # print initial header      
        f=$0                                            # save line
        print $0 " > " "tmp/file_" $1                   # print line to file
        next                                            # go to next line
    }
    {                                                   # process numbers-lines    
        if (x!=i) print header " > " "tmp/file_" i      # print initial header
        x=i                                             # save number    
        print $0 " > " "tmp/file_" i                    print line to file    
    }
    FNR % limit == 0{                                   # check limit 
        i++
    }
' <(head -n 1 split.csv;                      # getting the header
    grep "^[a-Z]" <(sed '1d' split.csv)|sort  # getting sorted letters-lines
   ) \
  <(grep "^[^a-Z]" split.csv)                 # getting numbers-lines


$ head tmp/*
==> tmp/file_1 <==
header
1,1,1,1
2,2,2,2
3,3,3,3

==> tmp/file_2 <==
header
4,4,4,4
5,5,5,5

==> tmp/file_x <==
header
x,x,x,x
x,x,x,x
x,x,x,x
x,x,x,x

==> tmp/file_y <==
header
y,y,y,y
y,y,y,y
y,y,y,y

【讨论】:

    猜你喜欢
    • 2020-02-27
    • 1970-01-01
    • 2021-08-03
    • 2019-12-31
    • 1970-01-01
    • 1970-01-01
    • 2015-04-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多