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

What I am trying to do is rename folders that are anywhere in the current tree, not neccessarily directly in the working directory. What I need to do is:

  • "A 1" becomes "A 01"
  • "A 2" becomes "A 02"
  • "A 09" is ignored

I ended up with this:

find . -type d -regex '.*A [0-9]$' -execdir mv "{}" "$(echo '{}' | sed 's_[0-9]_0&_')" \;

In order to test this I reduced the complexity a bit:

find . -type d -regex '.*A [0-9]$' -execdir echo "$(echo '{}' | sed 's_[0-9]_0&_')" \;

find lists all folders I want but the pattern in sed does not seem to find a match in the folder names. It works if it is executed on its own, so I guess there is a problem with characters that need to be escaped.

share|improve this question
up vote 1 down vote accepted

Update: Added a version which can handle any/all valid filename characters, except for \x01, which is used as sed's delimiter.... I have used A.* as the pattern in the updated examples, because I was testing for A"'. Of course, just change to whatever you need.

findx="\(.*/\)\(A.* \)\([0-9]\)$"
replx="&\x00\1\20\3\x00"; I=$'\x01'
find . -depth -type d -regex "$findx" | 
  sed -n "s$I$findx$I$replx$I p" |
    tr -d '\n' | 
      xargs --verbose -0n2 mv 

Here is an example, using perl instead of sed.
Note that perl's regualar expressions PCRE are not entirely the same as posix-extended ERE as used by find in this example. They do, however, share enough in common that the same regex pattern can be used in this case.

export findx='(.*/)(A.* )([0-9])$'
find . -depth -type d -regextype posix-extended -regex "$findx" |
  perl -l000pe 's/$ENV{'findx'}/$_\000$1${2}0$3/' |
    xargs --verbose -0n2 mv 

These next versions cannot handle single-quotes ' in filenames.

Using standard regex patterns

regex='\(.*/\)\(A \)\([0-9]\)$'
find . -depth -type d -regex "$regex" |
  sed -n "s|$regex|'&' '\1\20\3'|p" |
    xargs --verbose -n2 mv 

To use extended regex paterns

regex='(.*/)(A )([0-9])$'
find . -depth -regextype posix-extended -type d -regex "$regex" |
  sed -nr "s|$regex|'&' '\1\20\3'|p" |
    xargs --verbose -n2 mv 

The -depth option is needed so that more deeply nested directories are renamed before any parent directory is renamed.

Note that the script does not test if a directory already exists with the same name as the new intended name.

share|improve this answer
Thank you very much, this works well except in a case where a ' (single quote) character is in the name. Any idea how to tell sed or xargs to escape critical characters? You don't happen to spot the error in my command using -execdir? Using xargs like you did looks much easier and clearer, but I would like to understand what's going on. – slosd Aug 12 '12 at 9:58
Your regex was basically on track, but when you start needing to echo into an echo, I find there is often another way by using a straightt pipe-line. I have added a version which caters for single and/or double quotes and any other valid character other than \x01, which is used as sed's delimiter, and \x00 which is not valid in *nix filenames. Quoting the file paths is nott needed if you use xargs with the -0, --null option as an arg-separator.. I've added an extra tr step to remove the spurious \n which sed needs. If you use perl instead of sed you could avoid this step. – Peter.O Aug 12 '12 at 18:29
Note that -execdir changes the working directory to the parent directory for each path being processed. This means that you do not need to use fully-qualified paths. But, on its own, it does not produce a -depth listing with deeper level directories before their parents. If you have /abc/A 1/B 2/ and /abc/A 1 is renamed first, then depending on how find handles its buffering, it may try to rename /abc/A 1/B 2, but that no longer exists! ... so, for this situation use -depth. – Peter.O Aug 13 '12 at 9:42
Thanks for the great effort. I must say the shell is a minefield when it comes to file renaming stuff like this. Next time I will use some higher-level language... ;) – slosd Aug 14 '12 at 17:24

You must log in to answer this question.

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