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? Aug 23, 2019 at 7:13
  • Yeah, only one each.
    – Carlos
    Aug 23, 2019 at 7:17

1 Answer 1

  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/

You must log in to answer this question.

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