3

I have a small script that I use to organizes files in directories, and now I am attempting to run it on a folder or directories. MasterDir/dir1, MasterDir/dir2, etc.

Running the following code on MasterDir results in an error as the $dir is only a relative path, but I can't figure out how to get the full path of $dir in this case

for dir in */; do
    echo $dir
    cd $dir
    cwd="$PWD"
    mkdir -p "VSI"
    mv -v *.vsi "$cwd/VSI"
    mv -v _*_ "$cwd/VSI"
done
Sparky_47
  • 181
  • 12

3 Answers3

3

The problem you are having is that after you cd "$dir" the first time, you are one directory below where you generated your list of directories with for dir in */. So the next time you call cd "$dir" it fails because you are still in the first subdirectory you cd'ed into and the next "$dir" in your list is one level above.

There are several ways to handle this. One simple one is to use pushd to change to the directory instead of cd, so you can popd and return to your original directory. (though in this case you could simply add cd .. to change back to the parent directory since you are only one-level deep)

Using pushd/popd you could do:

for dir in */; do
    echo $dir
    pushd "$dir" &>/dev/null || {
        printf "error: failed to change to %s\n" "$dir" >&2
        continue
    }
    cwd="$PWD"
    mkdir -p "VSI" || {
        printf "error: failed to create %s\n" "$cwd/VSI" >&2
        continue
    }
    mv -v *.vsi "$cwd/VSI"
    mv -v _*_ "$cwd/VSI"
    popd &>/dev/null || {
        printf "error: failed to return to parent dir\n" >&2
        break
    }
done

(note: the || tests validate the return of pushd, mkdir, popd causing the loop to either continue to the next dir or break the loop if you can't return to the original directory. Also note the &>/dev/null just suppresses the normal output of pushd/popd, and redirection of output to >&2 sends the output to stderr instead of stdout)

As mentioned in the comment, you can always use readlink -f "$dir" to generate the absolute path to "$dir" -- though it's not really needed here.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • Never heard of this until now. Thanks alot! Really fixed my issue with `cd` in my for loop! – kyrlon Nov 26 '22 at 04:10
  • 1
    Glad it helped. There are many, many tools in even basic POSIX shells that cover virtually all the needs you can have in running the system. Good luck with your scripting. – David C. Rankin Nov 26 '22 at 07:39
3

I'd suggest using parentheses to run the loop body in a subshell:

for dir in */; do
    (
        echo $dir
        cd $dir
        cwd="$PWD"
        mkdir -p "VSI"
        mv -v *.vsi "$cwd/VSI"
        mv -v _*_ "$cwd/VSI"
    )
done

Since that's running in a subshell, the cd command won't affect the parent shell executing the script.

glenn jackman
  • 238,783
  • 38
  • 220
  • 352
1

This is one of the reasons I tend to avoid using cd in shell scripts -- all relative file paths change meaning when you cd, and if you aren't very careful about that you can get into trouble. The other is that cd can fail (e.g. because of a permissions error), in which case you'd better have an error check & handler in place, or something even weirder will happen.

IMO it's much safer to just use explicit paths to refer to files in other directories. That is, instead of cd somedir; mkdir -p "VSI", use `mkdir -p "somedir/VSI". Here's a rewrite of your loop using this approach:

for dir in */; do
    echo $dir
    mkdir -p "${dir}/VSI"
    mv -v "${dir}"/*.vsi "${dir}/VSI"
    mv -v "${dir}"/_*_ "${dir}/VSI"
done

Note: the values of $dir will end with a slash, so using e.g. ${dir}/VSI will give results like "somedir//VSI". The extra slash is redundant, but shouldn't cause trouble; I prefer to use it for clarity. If it's annoying (e.g. in the output of mv -v), you can leave off the extra slash.

Gordon Davisson
  • 118,432
  • 16
  • 123
  • 151