- State “DONE” from
Welcome back to the Linux CLI tools series. Today’s focus is on awk, a versatile text-processing tool, and how to use it to filter out events from a calendar file. This had a specific use case for me, because I wanted to filter out all events from an exported .ics file before a certain date. I needed to import the filtered events to a new calendar and want to avoid duplicating events. So, my main goal: Delete all calendar events before April 1, 2025, from a .ics file, using just the command line.
The Use Case
Let’s say, you’ve exported a calendar to .ics format. It contains events like:
BEGIN:VEVENT
UID:event1@example.com
DTSTART;TZID=Africa/Addis_Ababa:20250331
SUMMARY:Outdated Event
END:VEVENT
BEGIN:VEVENT
UID:event2@example.com
DTSTART:20250401T090000Z
SUMMARY:Relevant Event
END:VEVENTWe want to delete everything before April 1, 2025, but preserve the calendar structure. Now, we could use a calendar program to just delete the events you do not want. But this can easily become a pain in the ass if there are a lot of events in there.
The script
Here’s a complete solution using awk:
awk '
BEGIN {
keep = 0
in_event = 0
}
/^BEGIN:VEVENT/ {
in_event = 1
event = ""
keep = 0
next
}
/^END:VEVENT/ {
event = event $0 ORS
in_event = 0
if (keep) print event
next
}
in_event {
event = event $0 ORS
if ($0 ~ /^DTSTART/) {
split($0, parts, ":")
if (length(parts) > 1) {
date = substr(parts[2], 1, 8)
if (date >= "20250401") {
keep = 1
}
}
}
next
}
{ if (!in_event) print }
' input.ics > output.icsExplanation
So what is happening here? Let’s go through this section by section. AWK goes through a text file line by line and compares it against each of the blocks in the awk program.
First, we need to initialize to variables to keep track if we want to keep an event and a second variable to check if we are in an event or not.
Initialize variables
BEGIN { keep = 0 in_event = 0 }keep: whether to keep the current VEVENT blockin_event: whether we are currently inside an event
Begin a VEVENT block
In this block we check if a line begins with BEGIN:VEVENT. If so, we set the in_event flag and start recording the event. We set the keep flag to zero because we do not know yet if we want to keep it.
/^BEGIN:VEVENT/ { in_event = 1 event = "" keep = 0 next }- Start collecting lines into the
eventbuffer and reset state flags.
- Start collecting lines into the
End a VEVENT block Check lines for the END:EVENT bit and then append all the lines. if it is marked to keep, print it into the file.
/^END:VEVENT/ { event = event $0 ORS in_event = 0 if (keep) print event next }- When an event ends, print it only if it’s marked to keep.
Process lines inside VEVENT Check if we are in an event (is the in_event flag set?). If so, we want to find the line starting with DTSTART and extract the date from it. If the date is greater than the date given, set the keep flag.
in_event { event = event $0 ORS if ($0 ~ /^DTSTART/) { split($0, parts, ":") if (length(parts) > 1) { date = substr(parts[2], 1, 8) if (date >= "20250401") { keep = 1 } } } next }- Detect and parse the
DTSTARTline, account for additional test likeTZIDorVALUE=DATEby splitting at the “:”. Extract just theYYYYMMDDportion.
- Detect and parse the
Pass through everything else
{ if (!in_event) print }- This ensures lines outside event blocks, like
BEGIN:VCALENDARorEND:VCALENDAR, are preserved.
- This ensures lines outside event blocks, like
Why awk?
- Lightweight and fast
- No need for full-fledged calendar libraries
- Easily adaptable to other criteria (e.g.,
SUMMARY,LOCATION)