Super User is a question and answer site for computer enthusiasts and power users. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

I have a file structure like this:

  • 00000010
    • 000000001.file1
    • 000000001.file2
  • 00000020
    • 00000003.file1
    • 00000003.file2
    • 00000003.file3
  • ...

So there are folders with 8-digit names containing one ore more files with name starting with 8-digit numbers. But theses filenames are – let's say – out of sync. So Now I try to rename them recursively in bash to archive:

  • 00000010
    • 000000010.file1
    • 000000010.file2
  • 00000020
    • 00000020.file1
    • 00000020.file2
    • 00000020.file3
  • ...

My script does look like:

#! /bin/bash

find * -maxdepth 1 -name "*" -type d | while read -r dir
        rename 's/$dir\/[0-9]{8}/$dir/' *

But this is not working and gives errors like

Global symbol "$dir" requires explicit package name at (eval 1) line 1.

How could I write it to rename the files according to their folder names?

Thank you for help!

share|improve this question
Please don't cross-post. – Dennis Williamson Feb 1 '11 at 16:00
up vote 2 down vote accepted

As from another answer I learned, that I have to use

rename "s/$dir\/[0-9]{8}/$dir\/$dir/" $dir/*

Just in case anyone has the same problem...

share|improve this answer
Don't forget to accept your own answer as soon as possible. – Daniel Beck Feb 1 '11 at 10:43

I do not really like the answer where $dir is interpolated into the pattern. It could lead to unexpected results if the directory would e.g. contain a character that gets interpreted as a special regexp token.

I would rather match the files directly in the pattern, negating the need to loop over the directories and just use a regular glob.

rename 's#([^/]+)/[^/]+(\.file[0-9]+)$#$1/$1$2#' base_directory/*/*

where the glob hits the individual files within the directories (it could even be specified to base_directory/*/*.file*, or similar, but it should not matter here).

Note that this is the full command, without need for the find construction.

  • I use # as separator instead of the "regular" / to avoid having to escape that in the patterns.
  • I capture the (greedy) group of "not a directory separator", followed by a directory separator, followed by a file name and line end. This makes the first capture group be the parent directory for each file.
  • The first portion of the file name is discarded, except for the ending to keep the ordinal suffix.

I chose to use a more general approach than to assume that the files were all 8 characters long, but the real difference is that I neither need the find construct nor to cram the $dir variable into the (possibly unsafe) regexp environment.

share|improve this answer

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .