unix 将列中文件的日期与当前日期进行比较,并将其打印为新列

guicsvcw  于 2023-11-18  发布在  Unix
关注(0)|答案(2)|浏览(157)

我把输入作为11列文件;分隔。

ARM5914447;2023-10-17 18:05:38;2023-10-19 16:11:29;Missing name;Reassigned; Release;Mark;.com;p4;Vail;.com
ARM5914448;2023-10-18 19:05:38;2023-10-19 16:11:29;Missing name;Reassigned; Release;Tony;.com;p4;Vail;.com

字符串
我需要比较第二列的日期与今天的日期和打印在同一文件中的第12列的天数差值。
我想出了这个awk命令

awk -F";" 'NR == 1 { print $0";difference of days"; next } { "date -d \"today\" +%s" | getline today; "date -d \""$2"\" +%s" | getline filedate; diff = int((today - filedate) / 86400); print $0";"diff }'


当只是打印diff时,它看起来很好,但是打印$0”;“diff,它没有附加第12列,我在这里缺少了什么

dldeef67

dldeef671#

当我运行你的脚本时,我得到以下输出:

ARM5914447;2023-10-17 18:05:38;2023-10-19 16:11:29;Missing name;Reassigned; Release;Mark;.com;p4;Vail;.com;difference of days
ARM5914448;2023-10-18 19:05:38;2023-10-19 16:11:29;Missing name;Reassigned; Release;Tony;.com;p4;Vail;.com;14
                                                                                                          ^^^^^^^^^^^^^^^^^^^

字符串
请注意,新数据被追加到行的末尾。
如果我将文件转换为包含windows/dos行结尾(\r\n),新数据将在行的开头结束,例如:

$ unix2dos dat_file
$ awk '.....' dat_file
;difference of days17 18:05:38;2023-10-19 16:11:29;Missing name;Reassigned; Release;Mark;.com;p4;Vail;.com
^^^^^^^^^^^^^^^^^^^
;145914448;2023-10-18 19:05:38;2023-10-19 16:11:29;Missing name;Reassigned; Release;Tony;.com;p4;Vail;.com
^^^


请注意,新数据显示在该行的前面。
在这一点上,我想知道如果你的脚本是打印新的数据在行的开头,你碰巧错过了它。
你可以通过几种方式来检查windows/dos的行尾,例如:

$ file dat.file
dat.file: ASCII text, with CRLF line terminators
                           ^^^^^^^^^^^^^^^^^^^^^ - contains windows/dos line endines

$ file dat.file
dat.file: ASCII text
                                                 - no mention of 'CRLF' => does not contain windows/dos line endings

$ od -c dat.file
... snip ...
0000140   ;   V   a   i   l   ;   .   c   o   m  \r  \n   A   R   M   5
                                                 ^^^^^^ - CRLF ==> windows/dos line endings
... snip ...
0000320   l   ;   .   c   o   m  \r  \n
                                 ^^^^^^ - CRLF ==> windows/dos line endings


一个简单的修复方法是从文件中删除\r字符(例如,运行dos2unix),然后运行awk脚本,例如:

$ dos2unix dat.file
$ awk -F";" 'NR == 1 { print $0";difference of days"; next } { "date -d \"today\" +%s" | getline today; "date -d \""$2"\" +%s" | getline filedate; diff = int((today - filedate) / 86400); print $0";"diff }' dat.file

ARM5914447;2023-10-17 18:05:38;2023-10-19 16:11:29;Missing name;Reassigned; Release;Mark;.com;p4;Vail;.com;difference of days
ARM5914448;2023-10-18 19:05:38;2023-10-19 16:11:29;Missing name;Reassigned; Release;Tony;.com;p4;Vail;.com;14
                                                                                                          ^^^^^^^^^^^^^^^^^^^


另一个让awk从每行中删除尾随的\r的选项:

$ awk -F";" '{ sub(/\r$/,"") } NR == 1 { print $0";difference of days"; next } { "date -d \"today\" +%s" | getline today; "date -d \""$2"\" +%s" | getline filedate; diff = int((today - filedate) / 86400); print $0";"diff }' dat.file
             ^^^^^^^^^^^^^^^^^
ARM5914447;2023-10-17 18:05:38;2023-10-19 16:11:29;Missing name;Reassigned; Release;Mark;.com;p4;Vail;.com;difference of days
ARM5914448;2023-10-18 19:05:38;2023-10-19 16:11:29;Missing name;Reassigned; Release;Tony;.com;p4;Vail;.com;14
                                                                                                          ^^^^^^^^^^^^^^^^^^^

7nbnzgx9

7nbnzgx92#

正如其他人所指出的,你几乎肯定会有DOS行结尾,参见Why does my tool output overwrite itself and how do I fix it?,这可以通过在awk脚本的开头输入{sub(/\r$/,"")}来解决。
除此之外,

  • 每次从awk调用Unix命令时,它都必须生成一个子shell来执行该命令,这是非常慢的,如果可能的话最好避免。考虑到这一点,如果你不能避免调用date,那么你应该将"date -d \"today\" +%s" | getline today移动到BEGIN部分,这样它就必须在每行输入中少生成一个子shell,这样脚本的运行速度就快了一倍。
  • 你没有关闭你的子shell来运行date,在你生成它们之后,所以YMMV会处理一个大的输入文件,因为你可能会超过一个进程允许的最大打开“文件”数量。它可能会失败,出现“打开文件太多”错误(大多数awk),或者当它试图在内部管理打开/关闭文件时(GNU awk),速度会明显变慢。
  • 您调用getline的方式是不安全的,因为失败会悄悄地破坏您的输出,而不会被检测到(请参阅http://awk.freeshell.org/AllAboutGetline)。
  • 执行diff = int((today - filedate) / 86400)不会告诉你两个日期之间的天数,它会告诉你两个日期之间的86400秒间隔,向下舍入。所以,如果文件中的时间是昨天的12:01 pm,而你在今天的12:01 pm运行脚本,那么它会告诉你两天之间有0天,而不是期望的1天,假设你确实需要计算日期之间的天数差。

如果你有或者可以安装GNU awk,它有自己的内置时间函数,然后你可以使用这个脚本来解决上述所有问题:

$ cat tst.awk
function date2secs(date) {
    sub(/ .*/,"",date)
    gsub(/-/," ",date)
    return mktime(date " 12 0 0")
}

BEGIN {
    FS = OFS = ";"
    today = strftime("%F")
    todaySecs = date2secs(today)
}
{
    sub(/\r$/,"")
    inputSecs = date2secs($2)
    daysDiff = (todaySecs - inputSecs) / 86400
    print $0, daysDiff
}

字符串

$ awk -f tst.awk file
ARM5914447;2023-10-17 18:05:38;2023-10-19 16:11:29;Missing name;Reassigned; Release;Mark;.com;p4;Vail;.com;17
ARM5914448;2023-10-18 19:05:38;2023-10-19 16:11:29;Missing name;Reassigned; Release;Tony;.com;p4;Vail;.com;16


我假设这是预期的产出,因为今天是2023年11月3日,并且:

$ date -d 'today - 17 days'
Tue Oct 17 06:40:36 CDT 2023
$ date -d 'today - 16 days'
Wed Oct 18 06:40:49 CDT 2023


它与输入中的$2中的日期匹配。
请注意,我仍然在计算秒,但我正在计算输入日期的中午与今天日期的中午之间的差异(而不是使用当前时间和输入时间)然后除以一天中的秒数,结果总是两个日期之间的天数。该计算依赖于POSIX定义的一天中始终有86400秒Unix时间
除了生成准确的输出之外,上面的脚本运行速度将比现有脚本快几个数量级,因为它不生成任何子shell。
如果你没有GNU awk,那么将你的脚本改为:

$ cat tst.awk
function date2secs(date,        cmd,secs) {
    sub(/ .*/,"",date)
    if ( date in Date_Secs ) {
        secs = Date_Secs[date]
    }
    else {
        cmd = "date -d \"" date " 12:00\" +%s"
        if ( (cmd | getline secs) <= 0 ) {
            printf "%s failed\n", cmd | "cat>&2"
             exit 1
        }
        close(cmd)
        Date_Secs[date] = secs
    }
    return secs
}

BEGIN {
    FS = OFS = ";"
    todaySecs = date2secs("today")
}
{
    sub(/\r$/,"")
    inputSecs = date2secs($2)
    daysDiff = (todaySecs - inputSecs) / 86400
    print $0, daysDiff
}

$ awk -f tst.awk file
ARM5914447;2023-10-17 18:05:38;2023-10-19 16:11:29;Missing name;Reassigned; Release;Mark;.com;p4;Vail;.com;17
ARM5914448;2023-10-18 19:05:38;2023-10-19 16:11:29;Missing name;Reassigned; Release;Tony;.com;p4;Vail;.com;16


这也将解决您的所有问题,除了与GNU awk版本相比,它仍然非常慢,除非您在输入中只有少数几个唯一的日期。它的运行速度将是原始脚本的两倍多,因为它在每行输入中生成一个子shell,其中包含以前看不到的日期(因为我们在Date_Secs[]中缓存日期到秒的Map),而不是在每个输入行生成2个子shell。

相关问题