我有一个 bash 脚本,它从指定的 ICS (iCalendar) URL 获取日历事件数据并显示当前日期的事件。事件的开始和结束以 ics 格式显示。我不知道如何将 ics 格式转换为 YYYY:MM:DD:HH:mm 格式。

这是我的脚本:

# Define the ICS URL of your calendar
ics_url="your_url"

# Get today's date in YYYYMMDD format
today=$(date +%Y%m%d)

# Use curl to fetch the ICS data and process it
curl -s "$ics_url" | awk -v date="$today" '
BEGIN {
    inEvent = 0;
    matched = 0;
    eventBuffer = "";
}
/BEGIN:VEVENT/ {
    inEvent = 1;
    matched = 0;
    eventBuffer = "";  # Reset event buffer at the beginning of a new event
}
/END:VEVENT/ {
    if (matched) {
        print eventBuffer;  # Print the event buffer if matched
        print "";  # Print an empty line after each event
    }
    inEvent = 0;
}
/^DTSTART:/ {
    if (index($0, date) > 0) {
        matched = 1;
    }
    # Replace "DTSTART" with "start"
    sub(/^DTSTART:/, "start:", $0);
    eventBuffer = eventBuffer "\n" $0;
}
/^DTEND:/ {
    # Replace "DTEND" with "end"
    sub(/^DTEND:/, "end:", $0);
    eventBuffer = eventBuffer "\n" $0;
}
/^SUMMARY|DESCRIPTION/ {
    eventBuffer = eventBuffer "\n" $0;
}
' 

输出示例:

start:20240502T080000Z

end:20240502T090000Z

SUMMARY:Event

期望的输出:

start:2024:05:02:08:00:00

end:2024:05:02:09:00:00

SUMMARY:Event

或者

start:08:00

end:09:00

SUMMARY:Event

我找到了解决方案。感谢所有的答案。他们提供了很多帮助。

ics_url="your_url"

# Get today's date in YYYYMMDD format
today=$(date +%Y%m%d)

# Use curl to fetch the ICS data and process it
curl -s "$ics_url" | awk -v date="$today" '
BEGIN {
    inEvent = 0;
    matched = 0;
}
/BEGIN:VEVENT/ {
    inEvent = 1;
    matched = 0;  # Reset the matched flag at the beginning of a new event
    start = "";  # Reset variables at the start of a new event
    end = "";
    summary = "";
}
/END:VEVENT/ {
    if (matched) {
        # Extract hour and minute from start and end variables
        start1 = substr(start, 10, 2);
        start2 = substr(start, 12, 2);
        end1 = substr(end, 10, 2);
        end2 = substr(end, 12, 2);

        # Output the data in the specified order
        print "Event: " summary;
        print "start: " start1 ":" start2;
        print "end: " end1 ":" end2;
        print "";  # Print an empty line to separate events
    }
    inEvent = 0;
}
/^DTSTART:/ {
    if (index($0, date) > 0) {
        matched = 1;
    }
    # Store the start time without the label
    sub(/^DTSTART:/, "", $0);
    if (matched) {
        start = $0;
    }
}
/^DTEND:/ {
    # Store the end time without the label
    sub(/^DTEND:/, "", $0);
    if (matched) {
        end = $0;
    }
}
/^SUMMARY:/ {
    # Store the summary without the label
    sub(/^SUMMARY:/, "", $0);
    if (matched) {
        summary = $0;
    }
}'

新输出:

Event: Meeting
start: 10:30
end: 11:30

Event: Go for a walk
start: 10:00
end: 10:30

6

  • 1
    请使用通话的输出更新问题curl -s "$ics_url";还更新问题以显示预期结果;如果curl输出包含“太多”条目,则删除除几个条目之外的所有条目(一个符合您的要求,一个不符合您的要求)


    – 


  • 标准 awk 有substr一个冗长但简单的方法


    – 

  • ^SUMMARY|DESCRIPTION可能与您的意图不符


    – 

  • 1
    请使用curl -s“$ics_url”调用的输出更新问题


    – 

  • 你设置并清除inEvent但从未引用它;可以DTSTART并且DTEND发生在一BEGIN:VEVENT / END:VEVENT对之外吗?


    – 


5 个回答
5

假设:

  • DTSTART / DTEND可能发生在任何地方,包括外部BEGIN:VEVENT / END:EVENT但…
  • 我们只对一DTSTART / DTEND中出现的条目感兴趣BEGIN:VEVENT / END:VEVENT
  • 在一BEGIN:VEVENT / END:VEVENT对中,4x 所需的数据点可能按任何顺序甚至丢失(如果这不是真的,那么建议的解决方案可能有点矫枉过正,但它仍然应该生成所需的结果)

OP 尚未提供curl调用的实际输出,因此我将复制/修改 azbarcea 的数据集:

$ cat curl.out
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Example Corp.//iCal4j 1.0//EN
CALSCALE:GREGORIAN

BEGIN:VEVENT
DTSTAMP:20240502T120000Z
DTSTART:20240502T090000Z
DTEND:20240505T100000Z
SUMMARY:Team Meeting
DESCRIPTION:Weekly team meeting to discuss progress and upcoming tasks.
LOCATION:Conference Room
UID:1234567890
SEQUENCE:0
END:VEVENT

        # scramble order of entries; remove SUMMARY

BEGIN:VEVENT
DESCRIPTION:Present our latest project updates to the client.
DTSTART:20240510T140000Z
DTEND:20240510T160000Z
DTSTAMP:20240502T120000Z
LOCATION:Client's Office
UID:0987654321
SEQUENCE:0
END:VEVENT

END:VCALENDAR

一个awk想法:

cat curl.out | awk -v date="${today}" '

function parse_dt(dt) {                                         # reformat the ICS datetime string

  if (dt ~ /[0-9]{8}T[0-9]{6}Z/)
     return substr(dt,1,4)  ":"  \
            substr(dt,5,2)  ":"  \
            substr(dt,7,2)  ":"  \
            substr(dt,10,2) ":"  \
            substr(dt,12,2) ":"  \
            substr(dt,14,2)
}

function print_event() {

    if (event[1] != "") {                                       # if we have a match (ie, DSTART == date) then print array
       print event[1]                                           # event[1] == DTSTART
       for (i=2;i<=4;i++)                                       # event[2] == DTEND
           if (event[i] != "")                                  # event[3] == SUMMARY
              print event[i]                                    # event[4] == DESCRIPTION
    }

    delete event                                                # clear/reset array
}

BEGIN        { FS =":"
               map["SUMMARY"]     = 3
               map["DESCRIPTION"] = 4
             }

$2=="VEVENT" { if ($1 == "BEGIN")                               # we are only interested in DSTART/DTEND pairs within a BEGIN:VEVENT/END:VEVENT pair
                  inEvent = 1

               else
               if ($1 == "END" ) {
                  print_event()
                  inEvent = 0
               }

               next
             }

inEvent      { if ($1 == "DTSTART" && index($2,date) > 0)
                  event[1] = "start:" parse_dt($2)

               else
               if ($1 == "DTEND")
                  event[2] = "end:" parse_dt($2)

               else
               if ($1 in map)                                   # SUMMARY or DESCRIPTION ?
                  event[map[$1]] = $0
             }
'

注意: OP 将替换cat curl.out |curl -s "$ics_curl" |

准备试驾…

因为today=20240502我们得到:

start:2024:05:02:09:00:00
end:2024:05:05:10:00:00
SUMMARY:Team Meeting
DESCRIPTION:Weekly team meeting to discuss progress and upcoming tasks.

因为today=20240510我们得到:

start:2024:05:10:14:00:00
end:2024:05:10:16:00:00
DESCRIPTION:Present our latest project updates to the client.

因为today=20240505我们得到:

-- no ouput

如果这不能满足 OP 的要求,那么 OP 可能需要用更多详细信息更新问题(例如,调用的实际输出curl、完整的预期输出集)。

以下更新应该可以满足您的要求:

# Define the ICS URL of your calendar
ics_url="your_url"

# Get today's date in YYYYMMDD format
today=$(date +%Y%m%d)

# Use curl to fetch the ICS data and process it
curl -s "$ics_url" | awk -v date="$today" '
BEGIN {
    inEvent = 0;
    matched = 0;
    eventBuffer = "";
}
/BEGIN:VEVENT/ {
    inEvent = 1;
    matched = 0;
    eventBuffer = "";  # Reset event buffer at the beginning of a new event
}
/END:VEVENT/ {
    if (matched) {
        print eventBuffer;  # Print the event buffer if matched
        print "";  # Print an empty line after each event
    }
    inEvent = 0;
}
/^DTSTART:/ {
    if (index($0, date) > 0) {
        matched = 1;
    }
    # Replace "DTSTART" with "start"
    t = mktime(substr($0,9,4) " " substr($0,13,2) " " substr($0,15,2) " " substr($0,18,2) " " substr($0,20,2) " " substr($0,22,2))
    line = "start:" strftime("%Y:%m:%d:%H:%M:%S",t)
    eventBuffer = eventBuffer "\nstart:" line;
}
/^DTEND:/ {
    # Replace "DTEND" with "end"
    t = mktime(substr($0,7,4) " " substr($0,11,2) " " substr($0,13,2) " " substr($0,16,2) " " substr($0,18,2) " " substr($0,20,2))
    line = strftime("%Y:%m:%d:%H:%M:%S",t)
    eventBuffer = eventBuffer "\nend:" line;
}
/^SUMMARY|DESCRIPTION/ {
    eventBuffer = eventBuffer "\n" $0;
}
'

使用以下内容进行测试input

$ cat input.txt 
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Example Corp.//iCal4j 1.0//EN
CALSCALE:GREGORIAN
BEGIN:VEVENT
DTSTAMP:20240502T120000Z
DTSTART:20240505T090000Z
DTEND:20240505T100000Z
SUMMARY:Team Meeting
DESCRIPTION:Weekly team meeting to discuss progress and upcoming tasks.
LOCATION:Conference Room
UID:1234567890
SEQUENCE:0
END:VEVENT
BEGIN:VEVENT
DTSTAMP:20240502T120000Z
DTSTART:20240510T140000Z
DTEND:20240510T160000Z
SUMMARY:Client Presentation
DESCRIPTION:Present our latest project updates to the client.
LOCATION:Client's Office
UID:0987654321
SEQUENCE:0
END:VEVENT
END:VCALENDAR

输出将是:

start:start:2024:05:05:09:00:00
end:2024:05:05:10:00:00
SUMMARY:Team Meeting
DESCRIPTION:Weekly team meeting to discuss progress and upcoming tasks.


start:start:2024:05:10:14:00:00
end:2024:05:10:16:00:00
SUMMARY:Client Presentation
DESCRIPTION:Present our latest project updates to the client.

我运行它有点不同……我的输入为input.txtawk脚本为format.awk

BEGIN {
    inEvent = 0;
    matched = 0;
    eventBuffer = "";
}
/BEGIN:VEVENT/ {
    inEvent = 1;
    matched = 0;
    eventBuffer = "";  # Reset event buffer at the beginning of a new event
}
/END:VEVENT/ {
    if (matched) {
        print eventBuffer;  # Print the event buffer if matched
        print "";  # Print an empty line after each event
    }
    inEvent = 0;
}
/^DTSTART:/ {
    if (index($0, date) > 0) {
        matched = 1;
    }
    # Replace "DTSTART" with "start"
    t = mktime(substr($0,9,4) " " substr($0,13,2) " " substr($0,15,2) " " substr($0,18,2) " " substr($0,20,2) " " substr($0,22,2))
    line = "start:" strftime("%Y:%m:%d:%H:%M:%S",t)
    eventBuffer = eventBuffer "\nstart:" line;
}
/^DTEND:/ {
    # Replace "DTEND" with "end"
    t = mktime(substr($0,7,4) " " substr($0,11,2) " " substr($0,13,2) " " substr($0,16,2) " " substr($0,18,2) " " substr($0,20,2))
    line = strftime("%Y:%m:%d:%H:%M:%S",t)
    eventBuffer = eventBuffer "\nend:" line;
}
/^SUMMARY|DESCRIPTION/ {
    eventBuffer = eventBuffer "\n" $0;
}

并使用 CLI:

awk -f format.awk input.txt

5

  • 2
    第一个脚本的输出似乎显示了如果today未设置 (bash) 变量会发生什么(因此您能够显示 2 个不同日期的事件 – 20240505 和 20240510);调用第二个脚本 ( awk -f format.awk input.txt) 缺少该-v date="${today}"子句;这些mktime()/strftime()调用并不是真正需要的…您可以直接填充各种调用eventBuffer的结果substr()


    – 


  • 真的。真的。真的。我${today}在测试中没有使用。我曾经mktime举例说明,如果格式需要扩展为其他内容。


    – 

  • 您应该提到,时间函数(mktime 和 strftime)需要 GNU awk。


    – 

  • 对我来说没有 1:1 效果,因为 MacOS 不支持 Gawk(我可能应该提到这一点)。但我最终使用了 substr。谢谢您的回答!


    – 


  • 2
    @MoritzSchäfer MacOS 确实支持 gawk,只是默认情况下不支持 gawk。您可以轻松安装它,如果您将来需要使用 awk 进行其他项目,它将为您节省大量时间和精力。


    – 


每当你的输入中有标签值对时,就像在这种情况下一样,我发现最好首先创建一个数组将标签(例如"DTSTART")映射到它们的关联值(v[]如下),然后你可以比较、打印、修改你喜欢的任何值只需通过适当的标签对数组进行索引即可。使用任何 POSIX awk:

$ cat tst.sh
#!/usr/bin/env bash

today=$(date +%Y%m%d)

# replace this `cat` with your `curl` command
cat input.txt |
awk -v date="$today" '
    {
        if ( match($0,/^[[:upper:]]+:/) ) {
            tag = substr($0,RSTART,RLENGTH-1)
            v[tag] = substr($0,RSTART+RLENGTH)
        }
        else {
            v[tag] = v[tag] ORS $0
        }
    }
    /^END:VEVENT/ {
        if ( v["DTSTART"]+0 == date ) {
            print "start:"   dfmt(v["DTSTART"])
            print "end:"     dfmt(v["DTEND"])
            print "SUMMARY:" v["SUMMARY"] ORS v["DESCRIPTION"] ORS
        }
        delete v
    }
    function dfmt(date) { return \
        substr(date,10,2) ":" \
        substr(date,12,2)
    }
'

即使您的输入包含多行DESCRIPTION或任何其他字段(假设它符合提供的输入的修改版本

$ cat input.txt
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Example Corp.//iCal4j 1.0//EN
CALSCALE:GREGORIAN
BEGIN:VEVENT
DTSTAMP:20240502T120000Z
DTSTART:20240503T090000Z
DTEND:20240505T100000Z
SUMMARY:Team Meeting
DESCRIPTION:Weekly team meeting to
        discuss progress and
 upcoming tasks.
LOCATION:Conference Room
UID:1234567890
SEQUENCE:0
END:VEVENT
BEGIN:VEVENT
DTSTAMP:20240502T120000Z
DTSTART:20240510T140000Z
DTEND:20240510T160000Z
SUMMARY:Client
 Presentation
DESCRIPTION:
 Present our latest project
        updates to the client.
LOCATION:Client's Office
UID:0987654321
SEQUENCE:0
END:VEVENT
END:VCALENDAR

我们会得到这样的输出:

$ ./tst.sh
start:09:00
end:10:00
SUMMARY:Team Meeting
Weekly team meeting to
        discuss progress and
 upcoming tasks.

显然,您可以调整它以调整空白和/或输出日期格式,但是您喜欢,我将 和SUMMARY部分组合DESCRIPTION在一个输出块中,因为这就是问题中的代码的作用,但当然您可以单独打印它们。

关于问题中的脚本:

  1. 你永远不需要做/regexp/ { sub(/regexp/, string); whatever },你可以做sub(/regexp/, string) { whatever }以避免重复,/regexp/因为在这两种情况下,sub()只会做一些事情,并且只有在匹配时才会执行/regexp/。这适用于sub()s您的脚本中的两者
  2. 您可以删除整个BEGIN部分,因为 awk 会自动将这些变量初始化为零或 null,因此手动执行此操作不会为您的脚本添加任何功能。
  3. 您应该在每个部分中使用ORS而不是硬编码,以便这些部分将使用脚本其余部分使用的任何设置,而不是希望/假设将是"\n"eventBuffer = eventBuffer "\n" $0ORSORS\n

我将AWK按照以下方式利用 GNU 来完成这项任务,让file.txt内容成为

start:20240502T080000Z

end:20240502T090000Z

SUMMARY:Event

然后

awk 'BEGIN{FS=OFS=":"}$1=="start"||$1=="end"{gsub(/[TZ]/,"");$0=gensub(/([0-9][0-9])/,":\\1","g");$0=gensub(/::([0-9][0-9]):/,":\\1",1)}{print}' file.txt

给出输出

start:2024:05:02:08:00:00

end:2024:05:02:09:00:00

SUMMARY:Event

说明:我通知 GNUAWK:既是字段分隔符 ( FS) 又是输出字段分隔符 ( OFS)。当遇到startend在第一个字段中时,我删除所有TZ使用 gsub,然后使用为每对数字添加前缀:,然后再次使用它删除:第一个(多余)和第二个(年内)之前的内容。

我还添加了一个选项,将 UTC +0 时间转换为不同的时区(在我的例子中为 +2 UTC)。

     timeshift="2"

     start1 = substr(start, 10, 2);
     start2 = substr(start, 12, 2);
     start1= start1+timeshift;
 
     end1 = substr(end, 10, 2);
     end2 = substr(end, 12, 2);
     end1= end1+timeshift;