2015-09-02 62 views
0

我想解析一个巨大的文件(14 GB左右)并将其转置为csv(也可以将其转置两三个)。 该文件在一行上具有记录,这就是为什么它具有500.000.000行左右的记录。同时,各个记录的属性可能不同 - 某些列可能会出现,有些可能会出现。 我想出了一个用于转置它的shell脚本,但需要12分钟来处理1.000.000行,因此解析洞文件需要100个小时。解析巨大的文件外壳(或其他脚本语言)

的shell脚本如下:

############################################# 
# Define the usage 
############################################# 

gUsage=" 
usage: %> `basename $0` <Run Date> <Input path> <Preprocessing path> <filename> 

where 
    Input path: Generic folder where the input file is for transposing 
    Preprocessing path: Generic folder where the processed file will be moved 
    filename: Template for filename 

" 

ls_current_date=`date +'%Y-%m-%d'` 
ls_current_time=`date +'%H%M%S'` 
ls_run_name="${ls_current_date}"_"${ls_current_time}" 

i=-1 
j=0 
d=-1 

# Check number of parameters 
if [ $# -ne 4 ]; then 
    echo "" 
    echo "ERROR: Expecting 4 parameters" 
    echo "$gUsage" 
    exit 
fi 


ls_current_date=`date +'%Y-%m-%d'` 
ls_current_time=`date +'%H%M%S'` 
ls_run_name="${ls_current_date}"_"${ls_current_time}" 




############################################# 
# VN Declare & Check User Parameters + input files existence 
############################################# 



p_InputPath=$2 
p_PreprocessingPath=$3 
p_filename=$4 

echo "Start time : $ls_run_name " > "${p_PreprocessingPath}/log.txt" 
echo " Starting the transposing process..." >> "${p_PreprocessingPath}/log.txt" 
echo " " >> "${p_PreprocessingPath}/log.txt" 
echo " " >> "${p_PreprocessingPath}/log.txt" 
### Parameter 1 is the Run Date will test for TODAY (today's date in the format YYYY-MM-DD) 

if [ "$1" -eq "TODAY" ]; then 
    p_Rundate=`date +'%Y-%m-%d'` 
else 
p_Rundate=$1 
fi 


echo "*************************************************************" 
echo "Checking File Existence" 
echo "*************************************************************" 

ODSM_FILE="$p_InputPath/$p_filename" 

if [ -f $ODSM_FILE ]; 
then 
    echo "Source file ODSM found: $ODSM_FILE !" 
else 
    echo "ERROR: source file ODSM_FILE does not exist or does not match the pattern $ODSM_FILE." 
    exit 
fi 

#Define the header of the file 
header="entry-id;kmMsisdn;serialNumber;kmSubscriptionType;kmSubscriptionType2;kmVoiceTan;kmDataTan;kmPaymentMethod;kmMccsDate;kmCustomerBlocked;kmNetworkOperatorBlocked;kmBlockedNetwork;kmMmpNoStatus;kmMmpM3cCreditLimit;kmMmpM3cStatus;kmMmpM3cStatusDate;kmMmpM3cRegistrationDate;creatorsName;createTimestamp;modifiersName;modifyTimestamp;kmBrandName;objectClass;cn;kmBlockedServices;kmServiceProvider" 
delimiter=";" 
number_col=$(grep -o "$delimiter" <<< "$header" | wc -l) 
number_col2=`expr "$number_col + 1" | bc` 

#Create the new file 
v=$(basename $p_filename) 
name=${v%.*} 
extension=${v#*.} 
p_shortFileName=$name 
#Insert Header in file 

p_newFileName="${p_PreprocessingPath}/${p_shortFileName}_Transposed.csv" 
echo $header > $p_newFileName 

#Create the matrix with the columns and their values 


declare -A a 
#Parse line by line the file 
while read -r line; 
do 
    var=$line 
    #echo $line 
    Column_Name=${var%:*} 
    Column_Value=${var#*:} 
    var="# entry-id" 
    if [[ "$Column_Name" == "$var" && $Column_Value -ne 1 ]]; 
    then 
     ((i++)) 
     if [ $i -gt 0 ]; 
     then 
      z=$(($i-1)) 
      #Write the previous loaded record 

      echo ${a[$z,0]} ${a[$z,1]} ${a[$z,2]} ${a[$z,3]} ${a[$z,4]} ${a[$z,5]} ${a[$z,6]} ${a[$z,7]} ${a[$z,8]} ${a[$z,9]} ${a[$z,10]} ${a[$z,11]} ${a[$z,12]} ${a[$z,13]} ${a[$z,14]} ${a[$z,15]} ${a[$z,16]} ${a[$z,17]} ${a[$z,18]} ${a[$z,19]} ${a[$z,20]} ${a[$z,21]} ${a[$z,22]} ${a[$z,23]} ${a[$z,24]} ${a[$z,25]} >> $p_newFileName 

     fi 
     c=0 
     a[$i,0]=";" 
     a[$i,1]=";" 
     a[$i,2]=";" 
     a[$i,3]=";" 
     a[$i,4]=";" 
     a[$i,5]=";" 
     a[$i,6]=";" 
     a[$i,7]=";" 
     a[$i,8]=";" 
     a[$i,9]=";" 
     a[$i,10]=";" 
     a[$i,11]=";" 
     a[$i,12]=";" 
     a[$i,13]=";" 
     a[$i,14]=";" 
     a[$i,15]=";" 
     a[$i,16]=";" 
     a[$i,17]=";" 
     a[$i,18]=";" 
     a[$i,19]=";" 
     a[$i,20]=";" 
     a[$i,21]=";" 
     a[$i,22]=";" 
     a[$i,23]=";" 
     a[$i,24]=";" 
     a[$i,25]=";" 
     a[$i,26]=" " 

     a[$i,0]="$Column_Value ;" 
     #v[$i]=$i 

    elif [[ $Column_Name == "kmMsisdn" && $i -gt -1 ]]; 
    then 
     a[$i,1]="$Column_Value ;" 
    elif [[ $Column_Name == "serialNumber" && $i -gt -1 ]]; 
    then 
     a[$i,2]="$Column_Value ;" 
    elif [[ $Column_Name == "kmSubscriptionType" && $i -gt -1 ]]; 
    then 
     a[$i,3]="$Column_Value ;" 
    elif [[ $Column_Name == "kmSubscriptionType2" && $i -gt -1 ]]; 
    then 
     a[$i,4]="$Column_Value ;" 
    elif [[ $Column_Name == "kmVoiceTan" && $i -gt -1 ]]; 
    then 
     a[$i,5]="$Column_Value ;" 
    elif [[ $Column_Name == "kmDataTan" && $i -gt -1 ]]; 
    then 
     a[$i,6]="$Column_Value ;" 
    elif [[ $Column_Name == "kmPaymentMethod" && $i -gt -1 ]]; 
    then 
     a[$i,7]="$Column_Value ;" 
    elif [[ $Column_Name == "kmMccsDate" && $i -gt -1 ]]; 
    then 
     a[$i,8]="$Column_Value ;" 
    elif [[ $Column_Name == "kmCustomerBlocked" && $i -gt -1 ]]; 
    then 
     a[$i,9]="$Column_Value ;" 
    elif [[ $Column_Name == "kmNetworkOperatorBlocked" && $i -gt -1 ]]; 
    then 
     a[$i,10]="$Column_Value ;" 
    elif [[ $Column_Name == "kmBlockedNetwork" && $i -gt -1 ]]; 
    then 
     a[$i,11]="$Column_Value ;" 
    elif [[ $Column_Name == "kmMmpNoStatus" && $i -gt -1 ]]; 
    then 
     a[$i,12]="$Column_Value ;" 
    elif [[ $Column_Name == "kmMmpM3cCreditLimit" && $i -gt -1 ]]; 
    then 
     a[$i,13]="$Column_Value ;" 
    elif [[ $Column_Name == "kmMmpM3cStatus" && $i -gt -1 ]]; 
    then 
     a[$i,14]="$Column_Value ;" 
    elif [[ $Column_Name == "kmMmpM3cStatusDate" && $i -gt -1 ]]; 
    then 
     a[$i,15]="$Column_Value ;" 
    elif [[ $Column_Name == "kmMmpM3cRegistrationDate" && $i -gt -1 ]]; 
    then 
     a[$i,16]="$Column_Value ;" 
    elif [[ $Column_Name == "creatorsName" && $i -gt -1 ]]; 
    then 
     a[$i,17]="$Column_Value ;" 
    elif [[ $Column_Name == "createTimestamp" && $i -gt -1 ]]; 
    then 
     a[$i,18]="$Column_Value ;" 
    elif [[ $Column_Name == "modifiersName" && $i -gt -1 ]]; 
    then 
     a[$i,19]="$Column_Value ;" 
    elif [[ $Column_Name == "modifyTimestamp" && $i -gt -1 ]]; 
    then 
     a[$i,20]="$Column_Value ;" 
    elif [[ $Column_Name == "kmBrandName" && $i -gt -1 ]]; 
    then 
     a[$i,21]="$Column_Value ;" 
    elif [[ $Column_Name == "objectClass" && $i -gt -1 ]]; 
    then 
     if [ $c -eq 0 ]; 
     then 
     a[$i,22]="$Column_Value ;" 
     ((c++)) 
     else 
     a[$i,22]="$Column_Value+${a[$i,22]}" 
     ((c++)) 
     fi 
    elif [[ $Column_Name == "cn" && $i -gt -1 ]]; 
    then 
     a[$i,23]="$Column_Value ;" 
    elif [[ $Column_Name == "kmBlockedServices" && $i -gt -1 ]]; 
    then 
     a[$i,24]="$Column_Value ;" 
    elif [[ $Column_Name == "kmServiceProvider" && $i -gt -1 ]]; 
    then 
     a[$i,25]="$Column_Value " 
    fi 
done < $ODSM_FILE 
#Write the last line of the matrix 
echo ${a[$i,0]} ${a[$i,1]} ${a[$i,2]} ${a[$i,3]} ${a[$i,4]} ${a[$i,5]} ${a[$i,6]} ${a[$i,7]} ${a[$i,8]} ${a[$i,9]} ${a[$i,10]} ${a[$i,11]} ${a[$i,12]} ${a[$i,13]} ${a[$i,14]} ${a[$i,15]} ${a[$i,16]} ${a[$i,17]} ${a[$i,18]} ${a[$i,19]} ${a[$i,20]} ${a[$i,21]} ${a[$i,22]} ${a[$i,23]} ${a[$i,24]} ${a[$i,25]} >> $p_newFileName 


echo "Created transposed file: $p_newFileName ." 

ls_current_date2=`date +'%Y-%m-%d'` 
ls_current_time2=`date +'%H%M%S'` 
ls_run_name2="${ls_current_date2}"_"${ls_current_time2}" 
echo "Completed " 
echo "End time : $ls_run_name2 " >> "${p_PreprocessingPath}/log.txt" 
` 

下面你可以找到该文件的样本(第1项是文件的头,我不需要它的话)。

version: 1 

# entry-id: 1 
dn: ou=CONNECTIONS,c=NL,o=Mobile 
modifyTimestamp: 20130223124344Z 
modifiersName: cn=directory manager 
aci: (targetattr = "*") 

# entry-id: 3 
dn: kmmsisdn=31653440000,ou=CONNECTIONS,c=NL,o=Mobile 
modifyTimestamp: 20331210121726Z 
modifiersName: cn=directory manager 
cn: MCCS 
kmBrandName: VOID 
kmBlockedNetwork: N 
kmNetworkOperatorBlocked: N 
kmCustomerBlocked: N 
kmMsisdn: 31653440000 
objectClass: top 
objectClass: device 
objectClass: kmConnection 
serialNumber: 204084400000000 
kmServiceProvider: 1 
kmVoiceTan: 25 
kmSubscriptionType: FLEXI 
kmPaymentMethod: ABO 
kmMccsDate: 22/03/2004 
nsUniqueId: 2b72cfe9-f8b221d9-80088800-00000000 

# entry-id: 4 
dn: kmmsisdn=31153128215,ou=CONNECTIONS,c=NL,o=Mobile 
modifyTimestamp: 22231210103328Z 
modifiersName: cn=directory manager 
cn: MCCS 
kmMmpM3cStatusDate: 12/01/2012 
kmMmpM3cStatus: Potential 
kmBrandName: VOID 
kmBlockedNetwork: N 
kmNetworkOperatorBlocked: N 
kmCustomerBlocked: N 
kmMsisdn: 31153128215 
objectClass: top 
objectClass: device 
objectClass: kmConnection 
objectClass: kmMultiMediaPortalService 
serialNumber: 214283011000000 
kmServiceProvider: 1 
kmVoiceTan: 25 
kmSubscriptionType: FLEXI 
kmPaymentMethod: ABO 
kmMccsDate: 22/03/2004 
nsUniqueId: 92723fea-f8e211d9-8011000-01110000 

如果使用shell脚本无法实现这一点。你可以请建议一些更快的方法(perl,python)。我不知道任何其他脚本语言,但我可以学习:)。

+2

shell脚本看起来很不错;您正在使用shell内置功能进行分析,而不是调用外部程序。不幸的是,你已经遇到了shell处理大量数据不够快的事实。你最好用另一种语言写这个。 – chepner

+1

在..'中使用开关('case“$ {Column_Name}”)有点帮助吗?你还可以通过在进入循环之前解析第一个'#entry-id'前面的行来移除$ i上的测试吗?你是否还有很多未使用的属性(使用grep进行预处理或在填充25列时使用循环中的继续)?当你使用另一个varname作为它时,'var =“#entry-id”'可以在循环之上移动。而如何IFS =:读-r Column_Name Column_Value' –

+1

你可以使用1行(或一组var)的数组? 填充i和((i ++))的值后,您永远不会访问旧的行。该阵列将占用大量内存。 –

回答

0

这是一个令人印象深刻的shell脚本,但您正在解决的问题不适合传统的shell脚本。我会想象,使用echo 和输出重定向的所有文件写入将大大减缓 东西了。使用适当的编程语言,你可以缓冲你的文件写入 - 并且一次读入多行。

你已经提到了Perl和Python,这些正是我所建议的 。尽管Python似乎更受数据科学家青睐,但系统管理员都使用这两种语言。我倾向于这两者,Python也是我个人的最爱,因为我喜欢它的语法,与伪代码的相似性,以及我使用的大多数库是如何易于使用的 - 并阅读。

祝你好运,学习你选择的语言。 (关于哪种语言最好的讨论可能会导致这个问题因过于舆论而被封闭)。

+0

问题是,我正在将此转置为csv,以使ETL图形可读的文件将数据存储在数据库表中。该文件的扩展名为.LDIF。如果无法使用脚本+ ETL加载它,还有其他方法,SQL加载器或类似的东西吗?最初的请求是在数据库的一天内加载它,并在字段上执行验证(数据可能已损坏),但我想如果在该时间框架内不可能这样做,验证可以被丢弃(如果阅读过程不会失败)。 – bmcristi

+1

@AnthonyGeoghegan:shell'read'比一次只读一行更糟糕。它不知道行界限在哪里,并且必须避免过冲。它对每个字符进行一次“读取”系统调用。这是bash为什么比awk的文本处理速度慢得多的原因之一。当你可以比运行一个进程的开销更快地修改变量的值时,纯粹的bash是非常棒的,但它不适合批量文本。 –

+0

@bmcristi:你已经编写了一个程序来完成你在纯bash中需要的东西,只需要一些文本处理。为什么awk/perl/python /某些其他脚本语言在做同样的事情时会遇到任何困难,但是使用缓冲I/O? (除了shell'read'致命的慢,你每行关闭/重新打开输出文件,因为重定向在循环内部)。 –

0

你可以用awk试试。
awk具有关联数组,因此您可以对普通行使用类似-F: '{row[$1]=$2}'的东西。 当你有新的设置时,你可以打印/重置。

并且支持时,通过删除它使数组为空{delete row}

它应该比当前版本快很多。

编辑

我看着@Peter的答案,只是编辑他的解决方案。
我添加了其他领域,使用$ ODSM_FILE,$ p_newFileName,并改变了逻辑寻找一个新的纪录:
每一行后有入门ID
感谢彼得awk的代码和他的解释。

awk -vOFS=' ; ' -F'\\s*:\\s*' ' BEGIN { 
       a["entry-id"]="entry-id"; 
       a["kmMsisdn"]="kmMsisdn"; 
       a["serialNumber"]="serialNumber"; 
       a["kmSubscriptionType"]="kmSubscriptionType"; 
       a["kmSubscriptionType2"]="kmSubscriptionType2"; 
       a["kmVoiceTan"]="kmVoiceTan";     
       a["kmDataTan"]="kmDataTan";      
       a["kmPaymentMethod"]="kmPaymentMethod";   
       a["kmMccsDate"]="kmMccsDate";     
       a["kmCustomerBlocked"]="kmCustomerBlocked";  
       a["kmNetworkOperatorBlocked"]="kmNetworkOperatorBlocked"; 
       a["kmBlockedNetwork"]="kmBlockedNetwork";     
       a["kmMmpNoStatus"]="kmMmpNoStatus";      
       a["kmMmpM3cCreditLimit"]="kmMmpM3cCreditLimit";   
       a["kmMmpM3cStatus"]="kmMmpM3cStatus";      
       a["kmMmpM3cStatusDate"]="kmMmpM3cStatusDate";    
       a["kmMmpM3cRegistrationDate"]="kmMmpM3cRegistrationDate"; 
       a["creatorsName"]="creatorsName";       
       a["createTimestamp"]="createTimestamp";     
       a["modifiersName"]="modifiersName"; 
       a["modifyTimestamp"]="modifyTimestamp"; 
       a["kmBrandName"]="kmBrandName"; 
       a["objectClass"]="objectClass"; 
       a["cn"]="cn"; 
       a["kmBlockedServices"]="kmBlockedServices"; 
       a["kmServiceProvider"]="kmServiceProvider"; 
     } 
     function output_rec(){ print a["entry-id"], 
       a["kmMsisdn"], 
       a["serialNumber"], 
       a["kmSubscriptionType"], 
       a["kmSubscriptionType2"], 
       a["kmVoiceTan"], 
       a["kmDataTan"], 
       a["kmPaymentMethod"], 
       a["kmMccsDate"], 
       a["kmCustomerBlocked"], 
       a["kmNetworkOperatorBlocked"], 
       a["kmBlockedNetwork"], 
       a["kmMmpNoStatus"], 
       a["kmMmpM3cCreditLimit"], 
       a["kmMmpM3cStatus"], 
       a["kmMmpM3cStatusDate"], 
       a["kmMmpM3cRegistrationDate"], 
       a["creatorsName"], 
       a["createTimestamp"], 
       a["modifiersName"], 
       a["modifyTimestamp"], 
       a["kmBrandName"], 
       a["objectClass"], 
       a["cn"], 
       a["kmBlockedServices"], 
       a["kmServiceProvider"] } 
     END { output_rec() } 
     /^$/ { next } 
     /entry-id/ {output_rec();delete a; a["entry-id"]=$2;next} 
     { 
      sub(/\s*$/, "", $2); # strip trailing whitespace 
      if ($1 == "objectClass") { a[$1]= (a[$1]"+"$2) } else { a[$1]=$2; } 
     }' $ODSM_FILE > $p_newFileName 

我用25.000行数据测试了它,awk代码比原始代码快30倍。对于1千1百万行的输入文件,awk解决方案需要40秒。
@彼得:干得好!

+0

将尝试。谢谢! – bmcristi

+0

您能否给我完整的语法来使用您的建议? – bmcristi

+0

使用'awk -v OFS =',''所以输出字段用逗号分隔,而不必将它们逐字地包含在'print'中。 @Walter:你的代码比OP的可读性好得多。一个关联数组可以很容易地看到他只是将字段按顺序排列,并且排列在一行上。 –

1

我在评论中说,壳牌read是缓慢的,所以每个记录打开输出一次。

你的shell脚本版本看起来像它永远不会清空它的关联数组,但也不会重新使用旧的。所以最终你的shell将会使用巨大的内存,因为它将每条记录的条目都记录到记录计数器。

您只是将记录从由空行分隔的块重新格式化为以空格分隔字段的单行。这并不难,并且不需要在内存中保留以前的记录。

我正在考虑与Walter A一致。这个awk程序是解决问题的最大方法。

注意在将记录打印到csv行后清除字段后的delete a

awk -vOFS=' ; ' -F'\\s*:\\s*' '/^#/{print; this_is_for_debugging } 
    function output_rec(){ print a["kmMsisdn"], a["serialNumber"], a["kmSubscriptionType"], a["objectClass"] } 
    /^$/ { output_rec(); delete a;next} 
    END { output_rec() } 
    { sub(/\s+$/, "", $2); # strip trailing whitespace if needed 
     if ($1 == "objectClass" && a[$1]) 
      { a[$1]= (a[$1] "+" $2) } else { a[$1]=$2; } 
    }' foo.etl 

我会留给你打印剩下的字段。 (它们已经通过a[$1] = $2声明在“objectClass”条件的else块中得到解析)。

拆分空格*:空白*表示我们不必在打开时剥去空白第二场。显然-F arg需要加倍反斜杠。这可能是一个好主意,添加一个检查NF < = 2,以确保没有任何线路与多个:

输出为您的样品输入

; ; ; 
# entry-id: 1 
; ; ; 
# entry-id: 3 
31653440000 ; 204084400000000 ; FLEXI ; top+device+kmConnection 
# entry-id: 4 
31153128215 ; 214283011000000 ; FLEXI ; top+device+kmConnection+kmMultiMediaPortalService 

为了避免打印头线和印刷领域之间的数据复制,你可以把字段名在数组中,并遍历他们在这两个地方。

我原本以为-v RS='\n\n'会很有用,让每个块都成为AWK记录。其实,这可能仍然有用,FS='\n'。然后,您可以遍历字段(每条记录的行),并将其拆分为:。如果记录不可能包含:,就像您的shell脚本所假定的一样,那么使用split(与我们正在使用-F设置FS)相同,拆分很容易。

(在你的shell版本,使用Column_Name=${var%%:*}删除最长的后缀(包括所有: S),而不是最短的,或者使用IFS=: read Column_Name Column_Value

这可能是更好地用Perl,因为它是越来越庞大的为awk程序。 perl会让分割更容易,只需要在线上的第一个:

+0

谢谢!这工作,有一些小问题。当我把所有的字段在列kmNetworkOperatorBlocked上的记录3上的“N”之后,我得到一个非常长的空字符串。此外,entry-id必须与其他字段放在同一个级别上:3; 31653440000; FLEXI .... – bmcristi

+0

@bmcristi:entry-id行逐行打印以便调试,以便于查看* which * empty行触发了打印。 '/ ^#/ {print}'规则匹配以'#'开始的行。 re:长空白。可能你的输入文件有很多文字空白。你可以删除前/后空格'sub(/^\ s * /,“”,$ 2); sub(/ \ s * $ /,“”,$ 2);'。或者在perl中,'chomp($ fieldval)'。概率。将字段拆分为每个空格更好:'FS ='\ s *:\ s *' –

+0

@bmcristi:我更新了我的答案 –

1
awk -vOFS=' ; ' -F: ' 
function output_rec(){ gsub(/[ \t]+$/, "",$2); 
print a["entry-id"],a["kmMsisdn"],a["kmSubscriptionType"],a["kmSubscriptionType2"],a["kmVoiceTan"],a["kmDataTan"],a["kmPaymentMethod"],a["kmMccsDate"],a["kmCustomerBlocked"],a["kmNetworkOperatorBlocked"],a["kmBlockedNetwork"],a["kmMmpNoStatus"],a["kmMmpM3cCreditLimit"],a["kmMmpM3cStatus"],a["kmMmpM3cStatusDate"],a["kmMmpM3cRegistrationDate"],a["creatorsName"],a["createTimestamp"],a["modifiersName"],a["modifyTimestamp"],a["kmBrandName"],a["objectClass"],a["cn"],a["kmBlockedServices"],a["kmServiceProvider"]} 
/entry-id/ {output_rec(); delete a;a["entry-id"]=$2;next} 
END { output_rec() } 
    {gsub(/[ \t]+$/, "",$2); 
    if ($1 == "objectClass" && a[$1]) { a[$1]= (a[$1]"+"$2) } else { a[$1]=$2; } }' $ODSM_FILE >> $p_newFileName 
+0

我以前使用过,如果gsub(/ [\ t] + $ /,“”,$ 2);整合空间的去除。编辑最终版本的帖子 – bmcristi

+0

我的'awk -F'\\ s *:\\ s *''处理字段开头的空格。你把它留在外面,只是把字段拆分成':'。你的'gsub'只匹配字符串的末尾。另外,把它放在'output_rec()'中是很奇怪的。你在entry-id行调用了'output_rec()',所以如果你想为这些行修改'$ 2',你应该把这个调用放在你使用'$ 2'的地方。 –

+0

为了您的所知,我们设法在源文件中生成了28 m 25 s的531.280.227行转换(它生成了17.422.327个转置记录),并将文件大小从13 GB减少到了4 GB。它适用于我所需要的。非常感谢你,尤其是彼得和沃尔特。 – bmcristi

1

用Perl我想接近它是这样的:

#!/usr/bin/env perl 
use strict; 
use warnings; 

use Text::CSV; 

#configure output columns and ordering. 
my @output_cols = qw (
    entry-id kmMsisdn serialNumber 
    kmSubscriptionType kmSubscriptionType2 
    kmVoiceTan kmDataTan kmPaymentMethod 
    kmMccsDate kmCustomerBlocked 
    kmNetworkOperatorBlocked kmBlockedNetwork 
    kmMmpNoStatus kmMmpM3cCreditLimit 
    kmMmpM3cStatus kmMmpM3cStatusDate 
    kmMmpM3cRegistrationDate creatorsName 
    createTimestamp modifiersName 
    modifyTimestamp kmBrandName 
    objectClass cn 
    kmBlockedServices kmServiceProvider 
); 

#set up our csv engine - separator of ';' particularly. 
#eol will put a linefeed after each line (might want "\r\n" on DOS) 
my $csv = Text::CSV->new(
    { sep_char => ';', 
     eol  => "\n", 
     binary => 1 
    } 
); 

#open output 
open(my $output, '>', 'output_file.csv') or die $!; 
#print header row. 
$csv->print($output, \@output_cols); 
#set columns, so print_hr knows ordering. 
$csv->column_names(@output_cols); 

#set record separator to double linefeed 
local $/ = "\n\n"; 

#iterate the 'magic' filehandle. 
#this either reads data piped on `STDIN` _or_ a list of files specified on 
#command line. 
#e.g. myscript.pl file_to_process 
#or 
#cat file_to_process | myscript.pl 
#this thus emulates awk/grep/sed etc. 
#NB works one record at a time - so a chunk all the way to a double line feed. 

while (<>) { 
    #pattern match the key-value pairs on this chunk of data (record). 
    #multi-line block. 
    #because this regex will return a list of paired values (note - "g" and "m" flags), we can 
    #insert it directly into a hash (associative array) 
    my %row = m/^(?:#)?([-\w]+): (.*)$/mg; 

    #skip if this row is incomplete. Might need to be entry-id? 
    next unless $row{'kmMsisdn'}; 
    $csv->print_hr($output, \%row); 
} 
close ($output); 

这产生:

entry-id;kmMsisdn;serialNumber;kmSubscriptionType;kmSubscriptionType2;kmVoiceTan;kmDataTan;kmPaymentMethod;kmMccsDate;kmCustomerBlocked;kmNetworkOperatorBlocked;kmBlockedNetwork;kmMmpNoStatus;kmMmpM3cCreditLimit;kmMmpM3cStatus;kmMmpM3cStatusDate;kmMmpM3cRegistrationDate;creatorsName;createTimestamp;modifiersName;modifyTimestamp;kmBrandName;objectClass;cn;kmBlockedServices;kmServiceProvider 
3;31653440000;204084400000000;FLEXI;;25;;ABO;22/03/2004;N;N;N;;;;;;;;"cn=directory manager";20331210121726Z;VOID;kmConnection;MCCS;;1 
4;31153128215;214283011000000;FLEXI;;25;;ABO;22/03/2004;N;N;N;;;Potential;12/01/2012;;;;"cn=directory manager";22231210103328Z;VOID;kmMultiMediaPortalService;MCCS;;1 

注:由于我们使用while (<>) {我们可以利用这个脚本,就像您awk/sedperl使用该运营商为两种:

  • 数据在
  • 在命令行中指定,并阅读他们打开的文件管道。

,因此您可以:

./myscript.pl filename1 filename2 

somecommand_to_generate_data | ./myscript.pl 
+0

看起来不错,比我的awk版本更笨重。我想过切换到perl部分的方式。你为什么用嵌入在脚本中的数据编写它? OP表示他可能对学习perl感兴趣,但我认为这会让人困惑。另外,如果我们应该假设'kmMsisdn'将总是*在每条记录中都存在,那么IDK​​。 OP在他当前的awk尝试中使用'#entry-id:1'行,所以我想我们可以假设每个记录都有其中之一。或者你可以检查散列是否为空,对吧? –

+0

我嵌入它,因为它是自包含的 - 我可以在我的IDE中运行它,并摆弄它以使其工作。它也可以很好地作为'while(<>){'的替代品,''我喜欢它的功能,但认为它更不明显;')。我确实看过'entry-id',但第一个'chunk'实际上并没有什么有趣的地方。 “3”和“4”看起来更像记录。但希望这点足够明显。 – Sobrique

+1

编辑为做更多'准备好使用'。 (和评论,因为你永远不会有足够的评论) – Sobrique