基于模式拆分文件

时间:2017-10-13 21:32:59

标签: shell unix split ksh

我有一个文件低于模式

HDR1|20160101|1234|
N1|ABC|
XXX|21431415|3522352352|ITEM|
FORE|20140508|20140214|
SD|0|0039 - data|data|data|data|
SD|0|0211 - data|data|data|data|
SD|0|0039 - data|data|data|data|
SD|0|0211 - data|data|data|data|
FORE|20140508|20140214|
SD|0|0039 - data|data|data|data|
SD|0|0039 - data|data|data|data|
SD|0|0211 - data|data|data|data|

我想根据大小拆分文件,但也需要注意以下内容。

前3行是标题,我需要在我创建的每个分割文件中包含它。 从FORE开始的行与从SD开始的下面的行有关,所以我必须将它们保持在一起。

输出应如下所示。

拆分文件1:

HDR1|20160101|1234|
N1|ABC|
XXX|21431415|3522352352|ITEM|
FORE|20140508|20140214|
SD|0|0039 - data|data|data|data|
SD|0|0211 - data|data|data|data|
SD|0|0039 - data|data|data|data|
SD|0|0211 - data|data|data|data|

拆分文件2:

HDR1|20160101|1234|
N1|ABC|
XXX|21431415|3522352352|ITEM|
FORE|20140508|20140214|
SD|0|0039 - data|data|data|data|
SD|0|0039 - data|data|data|data|
SD|0|0211 - data|data|data|data|

我已经构建了一个伪代码,如下所示。我可以将多组这样的FORE和SD作为一组保存在一起,所以我已经设置了一个循环

create $file
create $line_num=5
create $file_size
create $top_size=20mb
read the first 4 lines of the original file and copy it in a temphdr file
    Loop until last $line_num is encountered
        read the header details and Append the header from the temphdr to the $file
        for each $record starting the head -$line_num (5,6,7...etc) that contains FORE| in the first part
            if the $file size is < $top_size
                append the $record in the $file

                increment $line_num
                For each $record in head -$line_num that contains SD| in the first part
                    append the $record in the $file
                    increment $line_num 
            else
                create a $file=$file+1
            fi
        end loop
    end loop    

除了上面提到的高级逻辑之外,有没有人让我知道是否还有其他有效的方法可以使用awk和sed等来实现这一点。

3 个答案:

答案 0 :(得分:1)

没有什么比这更复杂了。这可以在纯shell中实现,根本没有外部命令(没有headawk等)。

#!/usr/bin/env ksh

max_size=$(( 20 * 1024 * 1024 ))

# Read our three fixed header lines
headers=''
read -r line; headers+="$line"$'\n'
read -r line; headers+="$line"$'\n'
read -r line; headers+="$line"$'\n'

splitNum=1                                             # variable to track file number
splitFileName=$(printf 'split.%04d' "$splitNum")       # generate first filename
exec >"$splitFileName"                                 # and redirect stdout to that file

printf '%s' "${headers}"                               # print our headers...
cur_size=$(( ${#headers} ))                            # and set cur_size to their length

while IFS= read -r line; do                            # For each line:
  # check for and manage rotation
  if [[ $line = "FORE|"* ]]; then                      # If it's a FORE...
    if (( cur_size > max_size )); then                 # ...and over size: start a new file
      (( ++splitNum ))                                 # increment the split number
      splitFileName=$(printf 'split.%04d' "$splitNum") # generate a new filename
      exec >"$splitFileName"                           # redirect stdout to that file
      printf '%s' "${headers}"                         # print headers to stdout
      cur_size=$(( ${#headers} ))                      # reset size to size of headers
    fi
  fi
  # whether or not we had to do any of that:
  printf '%s\n' "$line"                                # print the line we just read
  cur_size=$(( cur_size + ${#line} + 1 ))              # and increment cur_size
done

请注意,如果您将此移植到bash,则可能需要将splitFileName=$(printf 'split.%04d' "$splitNum")更改为printf -v splitFileName 'split.%04d' "$splitNum"。 ksh93足够聪明,可以自动优化命令替换中涉及的子shell; bash需要明确的语法来避免开销。

答案 1 :(得分:1)

您可以使用此<allow-navigation href="market://*" />命令:

awk

在一行中:

awk -F '|' 'NR<=3{
   hdr = hdr $0 RS
}
$1=="FORE"{
   close(fn)
   fn="split-" ++n
   printf "%s%s", hdr, $0 RS > fn
}
$1=="SD"{
   print > fn
}
END{close(fn)}' file

答案 2 :(得分:0)

使用

这样的行可以更轻松地回答这个问题
FORE|20140508|20140214|\rSD|0|0039 - data|data|data|data|\rSD|0|0211 - data|data|data|data|\rSD|0|0039 - data|data|data|data|\rSD|0|0211 - data|data|data|data|
FORE|20140508|20140214|\rSD|0|0039 - data|data|data|data|\rSD|0|0039 - data|data|data|data|\rSD|0|0211 - data|data|data|data|

首先使用awk预处理文件,将标头保存在临时文件中,并加入以SD开头的行。 现在使用您喜欢的其他参数调用split -C 20m filename。 接下来tr "\r" "\n"到不同的行,并在所有文件中添加标题。

编辑:连接线的预处理可以使用

完成
awk 'NR<=3 { print >> "filename.head" }
   /^FORE/ { printf("%s%s",skipFirstNewline, $0); skipFirstNewline="\n" }
   /^SD/ { printf("\r%s",$0) }
   END{printf "\n" }' filename

当你检查结果时,你会对回车\r感到困惑。因此,当您要检查输出时,请将\r临时替换为rr