How to Bulk Rename Files in Linux Using Regex: Convert Filenames to Lowercase with Date Format Change


13 views

When working with log files in Linux, we often encounter inconsistently named files that need standardization. The specific case we're addressing involves files with patterns like:

System-Log-01-01-2009-NODATA.txt
Something-Log-01-01-2009-NODATA.txt

Our goal is to transform these into a more standardized format:

system.20090101.log
something.20090101.log

The Linux rename command (also known as prename on some systems) is perfect for this task. It supports Perl-compatible regular expressions, allowing for complex pattern matching and substitution.

Here's the complete command that handles all requirements:

rename -n 's/([^-]+)-Log-([0-9]{2})-([0-9]{2})-([0-9]{4}).*\.txt/lc($1) . ".$4$3$2.log"/e' *.txt

Let's examine each component:

([^-]+)     # Captures everything before first hyphen as group 1
-Log-       # Matches literal "-Log-"
([0-9]{2})  # Captures month (group 2)
-([0-9]{2}) # Captures day (group 3)
-([0-9]{4}) # Captures year (group 4)
.*\.txt     # Matches remaining text before .txt extension

Always use -n (dry-run) first to verify changes:

rename -n 's/([^-]+)-Log-([0-9]{2})-([0-9]{2})-([0-9]{4}).*\.txt/lc($1) . ".$4$3$2.log"/e' *.txt

Sample output would show what changes would occur without actually renaming files.

Remove -n when ready to perform the actual rename:

rename 's/([^-]+)-Log-([0-9]{2})-([0-9]{2})-([0-9]{4}).*\.txt/lc($1) . ".$4$3$2.log"/e' *.txt

For systems without rename, you can use a bash loop:

for file in *.txt; do
    if [[ $file =~ ([^-]+)-Log-([0-9]{2})-([0-9]{2})-([0-9]{4}) ]]; then
        prefix=$(echo "${BASH_REMATCH[1]}" | tr '[:upper:]' '[:lower:]')
        newname="${prefix}.${BASH_REMATCH[4]}${BASH_REMATCH[2]}${BASH_REMATCH[3]}.log"
        mv -i "$file" "$newname"
    fi
done

For files that might have different patterns, add additional checks:

rename 's/([^-]+)-Log-([0-9]{2})-([0-9]{2})-([0-9]{4}).*\.txt/lc($1) . ".$4$3$2.log"/e' *.txt || \
rename 's/([^-]+)_Log_([0-9]{2})_([0-9]{2})_([0-9]{4}).*\.txt/lc($1) . ".$4$3$2.log"/e' *.txt

When dealing with log files in Linux, you might encounter inconsistently named files like:

System-Log-01-01-2009-NODATA.txt
Something-Log-01-01-2009-NODATA.txt

What we want is a standardized format:

system.20090101.log
something.20090101.log

We can use a combination of Bash scripting and rename (or prename) with Perl regular expressions to achieve this transformation in one command.

The most efficient way is using the Perl-based rename utility (sometimes called prename):

rename 's/(.*)-Log-(\d{2})-(\d{2})-(\d{4})-NODATA\.txt\$/lc($1) . "." . $4 . $3 . $2 . ".log"/e' *.txt

Let's break down this command:

s/                          # Start substitution
(.*)                        # Capture the prefix (System/Something)
-Log-                       # Match literal text
(\d{2})-(\d{2})-(\d{4})     # Capture day, month, year
-NODATA\.txt\$              # Match ending
/                           # Replacement starts
lc($1)                      # Lowercase the first capture group
. "." . $4 . $3 . $2        # Reorder date to YYYYMMDD
. ".log"                    # New extension
/e                          # Evaluate replacement as Perl code

If you don't have rename/prename available, here's a pure Bash solution:

for file in *-NODATA.txt; do
    prefix=${file%%-Log-*}
    date_part=${file#*-Log-}
    day=${date_part:0:2}
    month=${date_part:3:2}
    year=${date_part:6:4}
    newname=$(echo "$prefix" | tr '[:upper:]' '[:lower:]')."$year$month$day".log
    mv "$file" "$newname"
done

For more complex scenarios where filenames might vary, consider adding validation:

for file in *.txt; do
    if [[ $file =~ ^(.*)-Log-([0-9]{2})-([0-9]{2})-([0-9]{4})-NODATA\.txt$ ]]; then
        prefix=${BASH_REMATCH[1]}
        day=${BASH_REMATCH[2]}
        month=${BASH_REMATCH[3]}
        year=${BASH_REMATCH[4]}
        newname=$(echo "$prefix" | tr '[:upper:]' '[:lower:]')."$year$month$day".log
        mv -- "$file" "$newname"
    fi
done

Always test your rename operations first. With rename/prename:

rename -n 's/.../' *.txt

With Bash loop, add an echo before mv:

echo mv "$file" "$newname"