2

I'm trying to rename every access.log.gz file (note: exact filename, not "beginning with" or "containing") found in /home/*/logs to date-access.log.gz in the same folder.

for file in /home/*/logs/access.log.gz
do
   mv -v "$file" /home/*/logs/old/`date +\%G-\%m-\%d`"-access.log.gz"
done

Of course I'm getting /home/*/logs/2014-09-08-access.log.gz - No such file or directory. How can I call the * paths back? Each file should be renamed and moved to /old/ subdir of the source folder. I tried with

mv -v "$file" /home/`$1`/logs/old/`date +\%G-\%m-\%d`"-access.log.gz"

... but didn't work.

3 Answers3

2

Use find (How can I use find command more efficiently?).

find /home/ -ipath */logs/access.log.gz

That should find all the files you're interested in, and print them. You could plug that into your script, but it's probably easier to let find do all the hard work.

find /home/ -ipath */logs/access.log.gz -execdir mv "{}" "old/`date +\%G-\%m-\%d`-access.log.gz" \;

The features of find we're using here are:

  • -ipath matches patterns in the found filenames. You could also use -name if you knew for sure there's only one access.log.gz file in each subpath (-name access.log.gz).
  • -execdir executes the given command on each found file, from the subdirectory containing the matched file. This is equivalent to doing cd to where each file is and then running the command there. In this case it saves a lot of path manipulation logic. The found file is substituted for {}. Notice also that "old" is given as a relative path (as you said that old/ exists in logs/)
roadmr
  • 34,222
  • 9
  • 81
  • 93
1

Simply dissect and re-assemble $file as you iterate over the matching files: ${file%/*} gives you $file with everything after the last / removed, and ${file##*/} gives you $file with everything before the last / removed; taken together ${file%/*}/old/$date-${file##*/} will give you the "new" filename corresponding to $file.

Presumably the date won't change in the course of running your script, so there's no point in invoking date for each and every file; rather, store the date in a variable once, and then use that during the loop: date=$(date '+%G-%m-%d'). (Note that modern scripts should use $(...) rather than backticks. Also note that % is not a "special" character and so doesn't need quoting or escaping, though as a matter of good practice the whole format string should be quoted.)

If you have a new version of Bash, you can avoid the subshell entirely by using printf -v date '%(%G-%m-%d)T' -1 instead.

You might also find it useful to use shopt -s globstar so that you can search to arbitrary depth in the directories, and shopt -s nullglob so that unmatched glob (wildcard) expand to "nothing" instead of the original glob pattern.

Putting these all together we get:

shopt -s globstar nullglob
date=$( date '+%G-%m-%d' )
for file in /home/*/logs/**/access.log.gz
do
    target=${file%/*}/old/$date-${file##*/}
    mv -vi "$file" "$target"
done
Martin
  • 326
0

If you have zsh installed, use zmv. In a script:

#!/bin/zsh
autoload zmv
zmv '/home/*/logs/access.log.gz' '$f:h/`date +\%G-\%m-\%d`-$f:t'

On the zsh command line, put autoload zmv in your ~/.zshrc and run that last line at the command prompt.