Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

I was trying to learn how to identify only folders within a folder on the main level. example:

  • main
  • -folder 1
  • -folder 2
  • -folder 3

I started with this script:

#!/bin/bash

LOCATION="/mnt/test/build/"

for folder in $(ls $LOCATION); do

    echo "$folder"
done

However when I run the script it will echo the entire contents and if a folder has a space within it the name will be added to two lines. If I change the echo line from "$folder" to '$folder' it will only echo folder. Any suggestive articles would be great, too.

share|improve this question

5 Answers 5

up vote 2 down vote accepted

Don't use ls for simply iterating through the contents of a directory, use globbing:

#!/bin/bash
LOCATION="/mnt/test/build/"
for folder in "$LOCATION"/*; do
    [ -d "$folder" ] || continue
    echo "$folder"
done

The [ -d "$folder" ] command tests whether $folder is a directory or not. If it isn't, the entry is skipped with continue. Note the quotes around $LOCATION and $folder, these prevent the script from breaking when $LOCATION contains whitespace.

If you really need to process the contents of ls, use something like:

ls "$LOCATION" | while read folder; do
    echo "$folder"
done

If you have the additional restriction that variables from within the loop needs to be accesible after the loop, use Bashs process substitution:

while read folder; do
    echo "$folder"
done < <(ls "$LOCATION")

The Bash manual is available at http://www.gnu.org/software/bash/manual/.

share|improve this answer
    
the top one works however when I change the echo to basename to just display the folder it will only include what is available before the space. –  Matt Dec 13 '12 at 16:48
1  
@graphicsman Be sure to include quotes as in basename "$folder". –  Lekensteyn Dec 13 '12 at 17:03
    
@graphicsman Be sure to stick to the first command. Do not parse the output of ls. See also Understanding IFS regarding while read loops, which should be written while IFS= read -r folder;. –  Gilles Dec 13 '12 at 23:07

Don't parse the output of ls. You don't need ls to list the contents of a directory: you can use shell wildcards instead.

When you write $(ls $LOCATION), the output from the ls command is split into separate words wherever it contains whitespace. That's why your command mangled file names with spaces. You can modify the IFS variable to avoid having spaces considered separators, but there's no way to distinguish between a newline that separates file names from a newline within a file name, so you can't avoid trouble completely. Furthermore, each word that results from the split is treated as a glob (i.e. a wildcard pattern) and replaced by the list of matching files, if any. A simple rule of shell scripting is: always put double quotes around variable substitutions "$foo" and command substitutions "$(foo)". The double quotes prevent the splitting and globbing.

The following snippet is equivalent to your loop, except that it doesn't mangle file names, and it prints the full path to each file.

for x in "$LOCATION"/*; do
  echo "$x"
done

If you want the path relative to $LOCATION, one way is to change to the target directory first.

cd "$LOCATION"
for x in *; do
  echo "$x"
done

Another way is to strip off the prefix from the file name.

for full_path in "$LOCATION"/*; do
  relative_name=${full_path#"$LOCATION/"}
  echo "$relative_name"
done

This prints the name of all files in the target directory. If you only want to list subdirectories (including symbolic links to directories), add a / to the glob pattern to constrain the matches.

for full_path in "$LOCATION"/*/; do
  relative_name=${full_path#"$LOCATION/"}
  relative_name=${relative_name%/}
  echo "$relative_name"
done

If you don't want symlinks to be included, make an explicit test for a directory within the loop.

for full_path in "$LOCATION"/*/; do
  if ! [ -d "$full_path" ]; then continue; fi
  relative_name=${full_path#"$LOCATION/"}
  relative_name=${relative_name%/}
  echo "$relative_name"
done
share|improve this answer

To find all the directories below the level where you are

find . -type d

if you want to limit yourself to one level

find . -type d -maxdepth 1 -mindepth 1

Edit

I missed part of your question:

SAVEIFS=$IFS
IFS=$(echo -en "\n\b")
for dir in $( find . -type d -maxdepth 1 -mindepth 1 ) ; do
  echo ${dir}
done
IFS=$SAVEIFS

But depending on what you want to do, you could also use the -exec option of find to execute a command on each file.

share|improve this answer
    
that works great but it still echoes a new line for folders with spaces in the name. ideas?? –  Matt Dec 13 '12 at 15:45
2  
Your command mangles file names containing \[?*. Add set -f. Better: use find -exec, which is easy to use reliably. –  Gilles Dec 13 '12 at 23:06

In order to avoid the problem of name with space, you may use a while with a read condition. read will read the complete line, including any intermediate space.

For selecting only directory, you should add a new test, using -d.

#!/bin/bash
LOCATION="/home/giuseppe"
ls -1 $LOCATION | while read folder; do
  [ -d "$folder" ] && echo "$folder"
done
share|improve this answer
    
what is the -1 doing? –  Matt Dec 13 '12 at 16:50
1  
@graphicsman Please execute the command man ls... –  Lekensteyn Dec 13 '12 at 17:04
    
@graphicsman Nothing (-1 only makes a difference if the output of ls goes to a terminal). eppesuig: while read mangles whitespace at the beginning and end of lines, and backslashes. Use while IFS= read -r folder; (see Understanding IFS). But this still mangles unprintable characters and newlines because of ls. Also read Why you shouldn't parse the output of ls(1). –  Gilles Dec 13 '12 at 23:03
    
Hi @Gilles, I completely agree with your comments.I would, for sure use the find command instead of ls for enumerating directory. BTW, I never found file names with newlines or trailing spaces, is it a common habit on your systems? I will for sure give a try to the IFS modification on input parsing, thaks a lot. –  eppesuig Dec 14 '12 at 9:30
    
File names containing spaces are fairly common (I don't use them but many others do). Backslashes only come up naturally if you use unixish environments on Windows such as Cygwin. Newlines only appear in file names due to errors or malice. It is good to get into the habit of coping with arbitrary file names, because sometimes the file name is chosen by a potentially hostile party (e.g. chosen by the uploader in a web form). –  Gilles Dec 14 '12 at 19:14

This command will find all directories only directly in $LOCATION. It will not list any contents of said directories.

#!/bin/bash
LOCATION="/mnt/test/build/"

find "$LOCATION" -mindepth 1 -maxdepth 1 -type d

If you want to print only the relative paths and not absolute, use this:

find "$LOCATION" -mindepth 1 -maxdepth 1 -type d -printf "%f\n"
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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