簡體   English   中英

在 bash 腳本中使用 While 循環的問題(將文件拆分為多個文件)

[英]Issue with using While loop in bash script (to split a file into multiple files)

我需要從一個文件中讀取數據並插入到多個文件中(每個文件的大小小於 3mb,文件大小可以不同)。 重要的是 - 不應將 Agent 的記錄拆分到多個文件中。 我在 UNIX bash 腳本的 While 循環中執行所有這些操作。

Input.csv
        Src,AgentNum,PhoneNum
        DWH,Agent_1234,phone1  
        NULL,NULL,phone2  
        NULL,NULL,phone3 
        DWH,Agent_5678,phone1 
        NULL,NULL,phone2 
        NULL,NULL,phone3
        DWH,Agent_9999,phone1 
        NULL,NULL,phone2 
        NULL,NULL,phone3

Desired Output -

Output1.csv (less than 3MB)
        Src,AgentNum,PhoneNum
        DWH,Agent_1234,phone1  
        NULL,NULL,phone2  
        NULL,NULL,phone3

Output2.csv (less than 3MB)
        Src,AgentNum,PhoneNum
        DWH,Agent_5678,phone1 
        NULL,NULL,phone2 
        NULL,NULL,phone3
        DWH,Agent_9999,phone1 
        NULL,NULL,phone2 
        NULL,NULL,phone3

Bash Shell 腳本

#!/bin/bash
BaseFileName=$(basename $FileName | cut -d. -f1)
Header=`head -1 $FileName`
MaxFileSize=$(( 3 * 1024 * 1024 ))

    sed 1d $FileName | 
    while read -r line
    do
        echo $line >> ${BaseFileName}_${FileSeq}.csv

        MatchCount=`echo $line | grep -c -E '^.DWH'`

        if [[ $MatchCount -eq 1 ]]
        then
            FileSizeBytes=`du -b ${BaseFileName}_${FileSeq}.csv | cut -f1`
            if [[ $FileSizeBytes -gt $MaxFileSize ]] 
            then
                #Add a header record to each file
                sed -i "1i ${Header}" ${BaseFileName}_${FileSeq}.csv
                FileSeq=$((FileSeq + 1))
            fi
        fi
    done 

它幾乎可以正常工作,除了 1) 沒有按預期拆分記錄(代理的某些記錄拆分到多個文件中)2) 僅為第一個輸出文件插入標頭記錄。 3) 太慢了,一個 10MB 的文件需要 3 分鍾。 實際上我有一個 3GB 的文件。

有人可以建議我哪里做錯了。 有沒有更好的方法來處理這個問題?

這是一個粗略的嘗試——它不像純awk解決方案那么快,但它比你已經擁有的快得多:

#!/bin/bash

# two external parameters: input file name, and max size in bytes (default to 3MB)
InputFile=$1
MaxFileSize=${2:-$(( 3 * 1024 * 1024 ))}

BaseName=${InputFile%.*} # strip extension
Ext=${InputFile##*.}     # store extension
FileSeq=0                # start output file at sequence 0

# redirect stdin from the input file, stdout to the first output file
exec <"$InputFile" || exit
exec >"${BaseName}.${FileSeq}.${Ext}" || exit

# read the header; copy it to the first output file, and initialize CurFileSize
IFS= read -r Header || exit
printf '%s\n' "$Header" || exit
CurFileSize=$(( ${#Header} + 1 ))

# ...then loop over our inputs, and copy appropriately
while IFS= read -r line; do
  if [[ $line = DWH,* ]] && (( CurFileSize > MaxFileSize )); then
    (( FileSeq++ ))
    exec >"${BaseName}.${FileSeq}.${Ext}" || exit
    printf '%s\n' "$Header" || exit
    CurFileSize=$(( ${#Header} + 1 ))
  fi
  printf '%s\n' "$line" || exit
  (( CurFileSize += ${#line} + 1 ))
done

值得注意的變化:

  • 根本不調用任何外部工具。 沒有sed ,沒有basename ,沒有du ,沒有grep 任何時候你寫$()`` ,都會有一個非常重要的性能成本; 除非無法避免,否則不應在緊密循環中使用這些構造——並且當使用 POSIX sh 標准的 ksh 或 bash 擴展時,它們實際上是不可能避免的。
  • 只有在需要打開新的輸出文件時才會調用重定向。 我們不會在每次要寫一行時都使用>>"$filename" ,而是在每次需要開始一個新的輸出文件時使用exec >"$filename"
  • 引號總是在參數擴展期間使用,除非在字符串拆分或通配符被其他語法顯式抑制的上下文中。 不這樣做可能會損壞您的文件(例如,用當前目錄中的文件列表替換* ;用空格替換制表符;等等)。 如有疑問,請多引用。
  • 使用printf '%s\\n'是由POSIX標准優於定義echo -見為標准清晰度echo ,特別是實際應用信息部分。
  • 我們正在明確地進行錯誤處理。 也可以使用set -e ,但它的使用有很多注意事項

測試過程和輸出如下:

$ cat >input.csv <<'EOF'
Src,AgentNum,PhoneNum
DWH,Agent_1234,phone1
NULL,NULL,phone2
NULL,NULL,phone3
DWH,Agent_5678,phone1
NULL,NULL,phone2
NULL,NULL,phone3
DWH,Agent_9999,phone1
NULL,NULL,phone2
NULL,NULL,phone3
EOF

$ ./splitCSV input.csv 100  ## split at first boundary after 100 bytes

$ cat input.0.csv
Src,AgentNum,PhoneNum
DWH,Agent_1234,phone1
NULL,NULL,phone2
NULL,NULL,phone3
DWH,Agent_5678,phone1
NULL,NULL,phone2
NULL,NULL,phone3

$ cat input.1.csv
Src,AgentNum,PhoneNum
DWH,Agent_9999,phone1
NULL,NULL,phone2
NULL,NULL,phone3

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM