Find Copy and Rename Files in a Bash Script

The find command is very powerful, especially when combined with the -exec option, you can find and copy files to different locations, even renaming them at the same time. The difficulty comes however, when trying to alter the filename (retaining the original filename), for example to add a new prefix or suffix.

Script Base

I ran in to this problem when designing and developing a script to gather logs across multiple LPARs, finding log files based on a time argument; the number of hours of which to capture logs. The initial setup of the script was as follows (we’ll name this “gatherLogs.sh”):

#!/bin/bash

_hours=24
_outputDir=~/temp/
_logDir=~/logs/
_numeric='^[0-9]+$'

_date=`date "+%Y%m%d-%H%M"`

function usage () {
  echo "Usage: gatherLogs.sh [-h ##]"
  echo "  h - Hours Retention, Optional,  Must Be 1-72 (DEFAULT:24)"
  exit 2
}

while getopts ":h:" opt; do
  case $opt in
    h)
      _hours=$OPTARG
      if ! [[ $_hours =~ $_numeric ]]
      then
        echo "ERROR: Hours Retention Must Be Valid Number" >&2
        usage
      else
        if [[ $_hours < 1 || $_hours > 72 ]]
        then
          echo "ERROR: Hours Retention Must Be Between 1 and 72" >&2
          usage
        fi
      fi
      ;;
    \?)
      echo "ERROR: Invalid Option: -$OPTARG" >&2
      usage
      ;;
    :)
      echo "ERROR: Option -$OPTARG Requires An Argument." >&2
      usage
      ;;
  esac
done

_minutes=$((${_hours}*60))
 

Basic Find and Copy

The key attributes we need for the find command are the search location, the duration in minutes, and the destination location. From this base setup we get those items:

  • ${_logDir}    – search location
  • ${_minutes}   – duration in minutes
  • ${_outputDir} – destination location
find ${_logDir} -type f -mmin -${_minutes} -exec cp -p {} ${_outputDir} \; 2> /dev/null

Breaking this command down:

Command Action
find ${_logDir} Declares the search location
-type f Defines the type of item to search for (file)
-mmin -${_minutes} Defines the time period in minutes (references negative ${_minutes})
-exec cp -p {} ${_outputDir}
  1. Executes the copy command (-exec cp)
  2. Retains last modified properties (-p)
  3. And copies the result of the search ({}) to the destination directory (${_outputDir})
\; For every result
2> /dev/null Outputs to /dev/null

This command works for the majority of cases, however what if we want to rename the file?

Basic Find Copy and Rename

A basic rename can be worked in to the above script, for example:

find ${_logDir} -type f -mmin -${_minutes} -exec cp -p {} ${_outputDir}NEWFILENAME \; 2> /dev/null

Where “NEWFILENAME” is the new name of the found file, however this will only work for a single file. If we’re finding multiple files, we need to rename them accordingly. The problem is however, that the -exec cp command can’t have a second dynamic argument based on the found filename. The {} denotes the found file, and can only be used once. Ideally, we would want something like:

find ${_logDir} -type f -mmin -${_minutes} -exec cp -p {} ${_outputDir}$(basename {})-SUFFIX \; 2> /dev/null

Where “-SUFFIX” is an additional suffix added on to the basename of the original found file. This however doesn’t work, as the {} output can only be referenced once by the find command.

Advanced Find Copy and Rename

The way to solve this problem is to create a separate basic script that will perform the copy and rename action for us, that we can execute by the find command instead of the copy command. This script we can call “gatherLogsCopy.sh”, and it simply needs to contain a single copy command with the source and destination as arguments:

#!/bin/bash

cp -p ${1} ${2}$(basename ${1})

This simply takes two arguments, the first is the source file, the second is the destination location combined with the basename of the source file. The way we incorporate this in to our main script is as follows:

find ${_logDir} -type f -mmin -${_minutes} -exec gatherLogsCopy.sh {} ${_outputDir}/PREFIX- \; 2> /dev/null

Where “PREFIX-” is some prefix added to the front of the found file, this will find any files that match the criteria, and copy them to the destination folder adding the given prefix. Note that this prefix can also be a variable or combination of variables.

In the original problem, the script was used to gather logs across a number of Tomcat log folders with different ports across multiple LPARs, to the PREFIX in this situation was made up of the LPAR and PORT information:

find ${_logDir} -type f -mmin -${_minutes} -exec gatherLogsCopy.sh {} ${_outputDir}/${_lpar}-${_port}- \; 2> /dev/null

In the end, this copied multiple files of the same name (“catalina.out“) to a single output directory, with multiple prefixed filenames:

  • HOST1-7510-catalina.out
  • HOST1-7520-catalina.out
  • HOST2-7510-catalina.out
  • HOST2-7520-catalina.out

A simple case of splitting the copy command out in to a separate script, and we’re done.

Leave a Comment

Your email address will not be published. Required fields are marked *