简体   繁体   中英

How to rename a consistently-named subdirectory across multiple directories?

I'm wanting to rename

123/1/ -> 123/v1/
foo/1/ -> foo/v1/
bar/1/ -> bar/v1/
345/1/ -> 345/v1/

I've searched and found a few related solutions, but not quite sure what is best in this instance. eg,

find . -wholename "*/1" -exec echo '{}' \;

successfully prints out all the paths relative to . , but {} expands to ./foo/1/ , so I can't move from {} to {}/v1 for instance. I also tried

find . -wholename "*/1" -exec mv '{}' $(echo '{}' | sed 's/1/v1/') \;

with the idea that I would be invoking mv./foo/1./foo/v1 , but apparently it tries to move .foo/1/ to a subdirectory of itself.

Anyhow, just looking for the simplest way to do this bulk renaming. To be clear, I'm trying to move the literal subdirectory 1 to v1 , not also 2 to v2 .

Something like this, untested, is IMHO the simplest way to do it using bash builtins and mandatory POSIX utils as long as your paths don't contain newlines:

while IFS= read -r old; do
    new="${old##*/}v1"
    echo mv -- "$old" $new"
done < <(find . -type d -name 1)

Remove echo once you're happy with the output from initial testing.

Like this, using perl rename (which may be different than the rename already existing on your system, use rename --version to check):

rename -n 's|([^/]+/)(1)|$1v$2|' */1/ 

remove -n (dry-run) when the outputs is ok for you.

(note that you can use globstar on bash or something similar on other shells to recurse into deeper sub-directories)

This was tagged with fish. A solution using fish shell:

for file in **/1/; mv $file (dirname $file)/v1; end

Try

find . -depth -type d -name 1 -execdir mv 1 v1 \;
  • The -execdir option to find is not POSIX, but it is widely supported by find implementations.
  • See What are the security issues and race conditions in using find -exec ? for useful information about the -execdir option.
  • The -depth option is necessary both to support paths like foo/1/1/1 and to avoid warning messages when find can't traverse the newly-renamed 1 directories.

I would do it in straight Bash.

Given:

$ tree .
.
├── 123
│   └── 1
├── 345
│   └── 1
│       └── 1        # this is a file named '1'
├── bar
│   └── 1
└── foo
    └── 1
        └── file
8 directories, 2 files

You can do:

#!/bin/bash

shopt -s globstar
new_1="v1"

for src in **/*/1; do      # will RECURSIVELY find any directory '1'
                           # if just next level (as in example)
                           # you can just do */1
    [ -d "$src" ] || continue 
    tgt="${src%%/1}/$new_1"
    echo "./$src/ => ./$tgt/"
    mv  "./$src" "./$tgt/"
done 

Result:

$ tree .
.
├── 123
│   └── v1
├── 345
│   └── v1
│       └── 1
├── bar
│   └── v1
└── foo
    └── v1
        └── file
 8 directories, 2 files

If you want to insert parent directories, add one statement to create them:

shopt -s globstar
new_1="a parent/v1"         # the replacement path has a parent

for src in **/*/1; do 
    [ -d "$src" ] || continue 
    tgt="${src%%/1}/$new_1"
    [[ "/" == *"$new_1"* ]] || mkdir -p "${tgt%/*}"  # create parents if any
    echo "./$src/ => ./$tgt/"
    mv  "./$src" "./$tgt/"
done 

Result:

$ tree .
.
├── 123
│   └── a parent
│       └── v1
├── 345
│   └── a parent
│       └── v1
│           └── 1
├── bar
│   └── a parent
│       └── v1
└── foo
    └── a parent
        └── v1
            └── file
 12 directories, 2 files

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM