problem: you want to grep multiple NCSA combined log format log files and sort by timestamp
The first thing we have to acknowledge is the NCSA timestamp format: date +'[%d/%b/%Y:%H:%M:%S %z]' yields: [18/May/2026:22:12:35 +0100] is actually spanning two fields AND requires specific subfield sorting. This is an awkward format for machine sorting π
Let's say you have a dir with 30 log files for an apache2 vhost, some uncompressed, some compressed (logrotated) etc. Months and years might be arbitrary. Let's say you want to concatenate all the log entries, grep/awk a specific keyword such as year, and then sort the entries based on the NCSA combined timestamp format, which doesn't naturally sort as single key/field...
impact: manual repetition required for each file
Without a multi-file pipeline, the operator will:
- have to check each file individually
- not have a single overview e.g.
lessbuffer or concatenated output file - not have a single grepable text stream for further filtering/discovery
solution: zgrep pipeline
find ... -exec ... {} +efficiently avoids any glob argument limitszgrep -Fhiautomatically handles both (un)compressed files, omitting filenames, ignoring case and searching for a fixed string e.g.file.phpawkoptionally filters based on one or more column values, e.g. timestamp yearLANG=C sort -sperforms stable sorting using the C locale.
Multiple-koptions define a cascading sort hierarchy, where each option's argument specifies the sub-field position and sort type (numeric/month/etc). Here they sort by year, month, day, time.- The
less -Spager provides a scrollable and searchable buffer for overview and further discovery
# subshell avoids changing the calling shell's pwd/cwd while allowing find to operate relative to the log directory.
# zgrep -h suppresses filenames, providing a single text stream for awk
# Note in this example I used awk to filter year 2026 on the 4th column (NCSA timestamp sans timezone)
( cd /var/log/apache2/sub.domain.tld && \
find . -maxdepth 1 -name 'access.log*' -exec zgrep -Fhi SEARCH_STRING {} + ) \
| awk '$4 ~ /\/2026:/' \
| LANG=C sort -s -k4.10,4.13n -k4.6,4.8M -k4.3,4.4n -k4.15,4.22 \
| less -S
π This example uses zgrep -F which turns on fixed strings mode (disables regex), remove the -F to use regex.
The awk '$4 ~ /\/2026:/' stage is only an example year filter; remove or adjust to your needs.
The sort keys map to the timestamp as follows: -k4.10,4.13n = year, -k4.6,4.8M = month, -k4.3,4.4n = day, -k4.15,4.22 = time.
π‘π Timezone offset note: The example sorts entries by their local timestamp. The timezone offset is intentionally not included because sorting it as an additional key does not convert timestamps to absolute time. If the logs contain different UTC offsets, such as across a DST transition, entries around that change may not be in true chronological order.
π‘π If absolute timestamp ordering is required, the timestamps must first be normalised to UTC using their timezone offsets before sorting, which is outside the scope of this post.
This scenario reminded me of my 2010 post on Parsing NCSA combined log format - working with columns.
Tested with sort (GNU coreutils) 9.1, and zgrep from the Debian bookworm gzip package version 1.12-1.