I'm going crazy here. I have a lot of folders like this

Folder 1/
Subfolder 2/
Subfolder 3/

I want to rename all files inside the folder 1, recursively and sequentially but following the current hierarchy as shown by ls and move them to a flat structure (all files inside a single folder).


Folder 1/
Item 01
Item 02
Item 03 (previously Item 01 on Subfolder 2)
Item 04 (previously Item 02 on Subfolder 2)
Item 05 (previously Item 01 on Subfolder 3)

Is this possible on bash? Thanks so much!

  • Please check your code blocks. Currently you refer to Folder 1 (with trailing space) and Folder 1, and there are two Item.01s in the same Subfolder 2. Then, you start with Item.01 (with a period) and move to Item 01 (with a space). (You also refer to Subfolder 2 and subfolder 2.) – Sparhawk Mar 19 '18 at 5:58
  • I do want to rename them to just "Item 0X" in a single folder no matter what it was before. but this is just to illustrate my issue – Freedo Mar 19 '18 at 6:08
  1. Check out how the globstar option in bash can help you:

    shopt -s globstar ; for i in ** ; do echo "$i" ; done

    That should give you a sorted list of all items in your directory tree, but it will include entries (lines) for both files and sub-directories.

  2. Filter out the subdirectories:

    shopt -s globstar ; for i in ** ; do [ -f " $i" ] && echo "$i" ; done
  3. Introduce a counter, and remove the current file suffixes:

    shopt -s globstar
    for i in ** ; do
      [ -f "$i" ] && echo "${i%%.*}.$cnt"
  4. Replace the echo test statement with the mv of your choice:

    mv "$i" "${i%%.*}.$cnt"
  5. Optionally, unset the globstar option after you finish:

    `shopt +s globstar`
  • Does this keep extensions? – Freedo Mar 19 '18 at 7:25
  • @Freedo : If you want it to. Go through it step by step and learn what is going on so you can become a better unix/linux user. By the time you understand step three, if it isn't doing precisely what you want, it should be trivial to tweak it. Answers are for teaching, not for spoon-feeding. – user1404316 Mar 19 '18 at 7:45

It's easier with zsh:

autoload zmv # best in ~/.zshrc
cd "Folder 1"
n=0; zmv -n '**/Item.<->(#qn^/)' 'Item ${(l:2::0:)$((++n))}'

(remove -n (for dry-run) when happy).

  • <->: any sequence of decimal digits (<x-y> without boundary)
  • (#qn): for numeric sorting (so Subfolder 10 comes after Subfolder 2).
  • ^/: exclude files of type directory.
  • ${(l:2::0:)var}: left-pad (and truncate if bigger) $var with 0s to length 2.

A note of warning: while the **/ glob is meant to match any level of subdirectory, including the empty level, so match files in the current directory, in zmv it doesn't because of the double pass it does with the pattern, once to glob and the second time as simple pattern matching to extract the capture groups (and then that second **/Item when pattern-matched because of the missing /).

A work around is to use:

zmv -n '(**/)Item.<->(#qn^/)' 'Item ${(l:2::0:)$((++n))}'


zmv -wn '**/Item.<->(#qn^/)' 'Item ${(l:2::0:)$((++n))}'

but then zmv detects it's a recursive match and turns on the depth-first order (to be able to rename directories safely). If you don't want the depth-first order and match files in the current directory, you can use the long form of **/ ((*/)#):

zmv -n '(*/)#Item.<->(#qn^/)' 'Item ${(l:2::0:)$((++n))}'



├── Subfolder 1
│   ├── Item.01
│   ├── Item.02
│   └── Subsubfolder
│       └── Item.01
├── Subfolder 10
│   ├── Item.01
│   └── Item.02
└── Subfolder 2
    ├── Item.01
    └── Item.02

It gives:

mv -- Subfolder\ 1/Item.01 Item\ 01
mv -- Subfolder\ 1/Item.02 Item\ 02
mv -- Subfolder\ 1/Subsubfolder/Item.01 Item\ 03
mv -- Subfolder\ 2/Item.01 Item\ 04
mv -- Subfolder\ 2/Item.02 Item\ 05
mv -- Subfolder\ 10/Item.01 Item\ 06
mv -- Subfolder\ 10/Item.02 Item\ 07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.