Unix脚本 - 需要改进性能的建议(shell脚本)

时间:2017-01-13 10:23:49

标签: ksh

我有一个输入csv文件,实际上我需要在输入文件中选择第二和第三列值并需要转换两个值的时区区域(从PT到CT),一旦转换我需要替换转换后的时间区域值到文件中。

注意:所有输入日期值都在太平洋时区,我正在转换为中央时区。

每行有5列 - 逗号分隔文件

CHID-123456323,2017-01-09 17:17:58-08:00,2017-01-09 17:39:25-08:00,hello,123456733
CHID-123456733,2017-01-09 17:16:58-08:00,2017-01-09 18:04:09-08:00,hello,123456734
CHID-123433589,2017-01-09 17:16:55-08:00,2017-01-09 17:40:29-08:00,hello,123456735
CHID-123000789,2017-01-09 17:16:52-08:00,2017-01-09 17:46:41-08:00,hello,123456736

脚本:我写了一个下面的脚本,这给出了我期待的精确结果。但是当输入记录数增加时,需要更多时间。例如,2万条记录需要1小时15分钟。

任何人都可以看看这个脚本并建议如何提高性能吗?

脚本:

while read i
do
    var1=`echo $i | awk -F',' '{ print $2 }'`

    var1_EPOCH=`date --date="${var1}" +%s`
    var1_CTZ=`TZ=":America/Chicago" date +"%Y-%m-%d %T" -d@$var1_EPOCH`
    sed -i "${cnt}s/${var1}/${var1_CTZ}/" filename

    var2=`echo $i | awk -F',' '{ print $3 }'`
    var2_EPOCH=`date --date="${var2}" +%s`
    var2_CTZ=`TZ=":America/Chicago" date +"%Y-%m-%d %T" -d@$var2_EPOCH`
    sed  -i "${cnt}s/${var2}/${var2_CTZ}/" filename

    cnt=$(($cnt+1))
done < filename

这是预期的输出文件

最终输出文件:

CHID-123456323,2017-01-09 19:17:58,2017-01-09 19:39:25,hello,123456733
CHID-123456733,2017-01-09 19:16:58,2017-01-09 20:04:09,hello,123456734
CHID-123433589,2017-01-09 19:16:55,2017-01-09 19:40:29,hello,123456735
CHID-123000789,2017-01-09 19:16:52,2017-01-09 19:46:41,hello,123456736 

2 个答案:

答案 0 :(得分:1)

Ksh有足够的内置功能。<​​/ p>

示例输入文件:

[STEP 100] $ echo $BASH_VERSION
4.4.5(2)-release
[STEP 101] $ cat file
CHID-123456323,2017-01-09 17:17:58-08:00,2017-01-09 17:39:25-08:00,hello,123456733
CHID-123456733,2017-01-09 17:16:58-08:00,2017-01-09 18:04:09-08:00,hello,123456734
CHID-123433589,2017-01-09 17:16:55-08:00,2017-01-09 17:40:29-08:00,hello,123456735
CHID-123000789,2017-01-09 17:16:52-08:00,2017-01-09 17:46:41-08:00,hello,123456736

剧本:

[STEP 102] $ cat time.ksh
tz=America/Chicago
pattern='(.+),(.+),(.+),(.+),(.+)'
while read -r line; do
    if [[ $line =~ $pattern ]]; then
        c1=${.sh.match[1]}
        c2=${.sh.match[2]}
        c3=${.sh.match[3]}
        c4=${.sh.match[4]}
        c5=${.sh.match[5]}

        TZ=$tz printf '%(%Y-%m-%d %T)T' "$c2" | read c2
        TZ=$tz printf '%(%Y-%m-%d %T)T' "$c3" | read c3

        print -r -- "$c1,$c2,$c3,$c4,$c5"
    else
        print -r -- "$line"
    fi
done

示例输出:

[STEP 103] $ ksh time.ksh < file
CHID-123456323,2017-01-09 19:17:58,2017-01-09 19:39:25,hello,123456733
CHID-123456733,2017-01-09 19:16:58,2017-01-09 20:04:09,hello,123456734
CHID-123433589,2017-01-09 19:16:55,2017-01-09 19:40:29,hello,123456735
CHID-123000789,2017-01-09 19:16:52,2017-01-09 19:46:41,hello,123456736

制作20,000行文件:

[STEP 104] $ rm -f bigfile
[STEP 105] $ fourlines=$(<file)
[STEP 106] $ for ((i=0; i<5000; ++i)); do printf '%s\n' "$fourlines" >> bigfile; done
[STEP 107] $ wc -l bigfile
   20000 bigfile

让我们对它进行性能测试:

[STEP 108] $ time ksh time.ksh < bigfile > newfile

real    1m36.849s
user    0m27.376s
sys     0m46.741s
[STEP 109] $ tail -n 4 newfile
CHID-123456323,2017-01-09 19:17:58,2017-01-09 19:39:25,hello,123456733
CHID-123456733,2017-01-09 19:16:58,2017-01-09 20:04:09,hello,123456734
CHID-123433589,2017-01-09 19:16:55,2017-01-09 19:40:29,hello,123456735
CHID-123000789,2017-01-09 19:16:52,2017-01-09 19:46:41,hello,123456736
[STEP 110] $ ksh --version
  version         sh (AT&T Research) 93u+ 2012-08-01
[STEP 111] $

答案 1 :(得分:0)

你不应该尝试sed循环内的文件,而只是写入输出。当您知道awk可以在一次调用中读取更多变量时,可以避免使用某些read命令。这些改进将您的代码更改为

while IFS=, read -r chid d1 d2 rest
do
   var1_EPOCH=`date --date="${d1}" +%s`
   var1_CTZ=`TZ=":America/Chicago" date +"%Y-%m-%d %T" -d@$var1_EPOCH`
   var2_EPOCH=`date --date="${d2}" +%s`
   var2_CTZ=`TZ=":America/Chicago" date +"%Y-%m-%d %T" -d@$var2_EPOCH`
   printf "%s,%s,%s,%s\n" "${chid}" "${var1_CTZ}" "${var2_CTZ}" "${rest}"
done < filename

您可以使用

升级到$(command)表示法,避免使用某些变量和双重赋值
tz=":America/Chicago"
tformat="%Y-%m-%d %T"
while IFS=, read -r chid d1 d2 rest
do
   printf "%s,%s,%s,%s\n" "${chid}" \
       "$(TZ=${tz} date +"${tformat}" -d@$(date --date="${d1}" +%s))" \
       "$(TZ=${tz} date +"${tformat}" -d@$(date --date="${d2}" +%s))" \
       "${rest}"
done < filename

逻辑上的下一个改进是使用awk(比编写while循环更快)。

编辑:添加awk解决方案

在这种情况下,awk很难,因为您不想使用system()调用来转换日期(避免调用其他工具)。当您的csv有1个时区时,您可以避免使用固定值查找时区信息 跳过大量计算将使下一个awk成为明显的赢家:

awk -F, '{
    split($2,A,"[-: ]");
    T1=mktime(A[1] " " A[2] " " A[3] " " A[4] " " A[5] " " A[6]);
    split($3,B,"[-: ]");
    T2=mktime(B[1] " " B[2] " " B[3] " " B[4] " " B[5] " " B[6]);
    printf("%s,%s,%s,%s,%s\n",$1,
       strftime("%Y-%m-%d %T",T1+7200),
       strftime("%Y-%m-%d %T",T2+7200),
       $4,
       $5);
} filename

如果需要,您可以使用A[7]B[7]计算时间偏移的其他值。

以上作品时请跳过此处阅读。当你还需要更多的见解时,下面只是一个想法 另一个策略是避免多次转换同一日期:
当你的inputfile有很多类似的时间戳(相等的日期+小时),并且你有一个大的输入文件,你可以先转换唯一的小时并在使用时 你正在处理一个大文件 做一个&#34;翻译助手&#34;使用固定字符串,类似

# Becomes dirty when you want to cut out the minutes/seconds:
cut -d, -f2,3 filename | tr "," "\n" | sort -u
# Hard to read/debug/maintain
sed 's/^[^,]*,\([^:]*\)[^-]*\([^,]*\).*/\1\2/' filename

将这些日期转换并存储在某个映射文件中,并使用它来翻译文件名可能会更多地改进解决方案,但这应该是最后一次尝试(首先尝试awk)。