Contents
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} |
|
\; |
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.