5

I'm working on a project that requires batch processing of a large number of image files. To make things easier, I've written a script that will create n directories and move m files to them based on user input.

My issue is to now understand directory traversal via shell script.

I've added this snippet at the end of the sort script described above

dirlist=$(find $1 -mindepth 1 -maxdepth 1 -type d)

for dir in $dirlist
do
  cd $dir
  echo $dir
  ls
done

When I ran it inside a Pano2 folder, whcih contains two inner folders, I always got an error

./dirTravel: line 9: cd: Pano2/05-15-2012-2: No such file or directory

However, after that, I get the file listing from specified directory.

What is the reason behind the warning? If I add cd ../ after ls I get the listing of the folders inside Pano2/, but not the files themselves.

Jason
  • 11,263
  • 21
  • 87
  • 181
  • 2
    if there are spaces in you dir names, your solution will blow up. This sort of question gets asked here 2-3x per week. Take a little time and search for `[bash] find while` (don't use for loops). Good luck. – shellter May 16 '12 at 01:49
  • @shellter, the folder names are being programmatically generated by a script and have no filenames. While your point would be valid otherwise, it really doesn't come into play in this situation. – Jason May 16 '12 at 02:05
  • @shelter - using while will prevent it from blowing up if there is a space in the filename, but not if there is a newline in the name (which is valid). – jordanm May 16 '12 at 02:51
  • @jason - it's the wrong way to do it regardless. – jordanm May 16 '12 at 02:52
  • @jordanm: you can solve the newline problem with `find ... -print0 | while IFS= read -d $'\0' -r file` -- see [this previous answer](http://stackoverflow.com/questions/7039130/bash-iterate-over-list-of-files-with-spaces/7039579#7039579) – Gordon Davisson May 16 '12 at 04:08
  • @GordonDavisson -print0 and -printf are not specified by POSIX, and may not be available. It does get the job done properly though. – jordanm May 16 '12 at 17:34

3 Answers3

6

I recommend using a sub-shell to switch directories:

dirlist=$(find $1 -mindepth 1 -maxdepth 1 -type d)

for dir in $dirlist
do
  (
  cd $dir
  echo $dir
  ls
  )
done

The code inside the sub-shell won't affect the parent shell, so it can change directory anywhere without giving you any issues about 'how to get back'.

This comes with the standard caveat that the whole process is fraught if your directory names can contain spaces, newlines or other unexpected and awkward characters. If you keep with the portable filename character set ([-_.A-Za-z0-9]) you won't run into problems as written.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
4

My guess is that you're going in two levels deep, but only coming back up one level. Try adding a cd ../.. after the ls, or use pushd and popd instead.

For example:

for dir in $dirlist
do
  pushd $dir
  echo $dir
  ls
  popd
done

As @shellter points out, if those directories have spaces, then something like this might work better:

find $1 -mindepth 1 -maxdepth 1 -type d | while read -r dir
do
  pushd "$dir"  # note the quotes, which encapsulate whitespace
  echo $dir
  ls
  popd
done
Nate Kohl
  • 35,264
  • 10
  • 43
  • 55
2

There really isn't any reason to cd to the directory to run the ls command. The following should work:

find "$1" -mindepth 1 -maxdepth 1 -type d -exec ls {} \;

If that was just an example, and you really run other commands that depend on the working directory, you can use bash -c with -exec:

find "$1" -mindepth 1 -maxdepth 1 -type d -exec bash -c 'cd "$1"; echo "$1"; ls' -- {} \;

The bash -c call spawns a subshell, so you don't need to worry about it changing the directory of the current shell.

jordanm
  • 33,009
  • 7
  • 61
  • 76