I have the following file structure:

  • /home/data/C01/dir_wwe_346/file1.txt pic.jpg random.sh
  • /home/data/C02/dir_wwe_644/file1.txt pic.jpg random.sh
  • /home/data/C03/dir_wwe_324/file1.txt pic.jpg random.sh

I want them to be this way:

  • home/data/C01/file1.txt pic.jpg random.sh
  • home/data/C02/file1.txt pic.jpg random.sh
  • home/data/C03/file1.txt pic.jpg random.sh

It's basically moving multiple files one directory up, I have tried this script without success:

#!/bin/bash for i in */ do cd i/*/ mv * ../ done

After posting this (but before reading grawity response below), I realised I did not use the proper variable expansion, and run this:

#!/bin/bash for i in */ do cd ${i}*wwe* mv * ../ done

The script is moving all the subdirectories to the data folder, so I am ending up with this:

  • /home/C01/dir_wwe_346/file1.txt pic.jpg random.sh
  • /home/C02/dir_wwe_644/file1.txt pic.jpg random.sh
  • /home/C03/dir_wwe_324/file1.txt pic.jpg random.sh

    What am I doing wrong?

PS. I edited the post so that it reveals the process I went through, and so that responses make more sense for new comers that might benefit from these responses. Thanks grawity, I learned something new. I have just started learning bash a week ago and I feel I am progressing.

  • 1
    Does each top-level directory have exactly one subdirectory? – user1686 Aug 23 '19 at 7:13
  • Yeah, only one each. – Carlos Aug 23 '19 at 7:17
  1. Variables are only recognized and expanded with the $ prefix. If the loop defines a variable 'i', you need to use $i whenever you want its value:

    for i in */; do
        cd $i/*/
  2. The "current directory" is process-wide and does not automatically reset every loop iteration. You need either to manually return to the parent directory afterwards:

    cd $i/*/; mv * ../; cd ../../

    or use a "subshell" to scope the directory change:

    (cd $i/*/; mv * ../)
  3. You're not doing any error checking. If the cd ever fails, the subsequent mv can do unpleasant things. Either join commands using && or do the opposite check and use || exit (or || break), whichever looks best:

    for i in */; do
        cd $i/*/ && {
            mv * ../;
            cd ../../
    for i in */; do
        cd $i/*/ || exit
        mv * ../
        cd ../../
    for i in */; do
        (cd $i/*/ && mv * ../)
  4. Improvement: You could move the subdirectory wildcard to mv, allowing it to work even in case there are multiple subdirectories:

    cd $i/ || exit
    mv */* .
    cd ../
  5. Improvement: You could do this without needing cd at all:

    mv $i/*/* $i/

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.