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


3 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"