1

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.

5 Answers5

2

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/.

Lekensteyn
  • 20,830
2

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
0

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.

Matteo
  • 9,796
  • 4
  • 51
  • 66
0

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
eppesuig
  • 3,318
  • what is the -1 doing? – DᴀʀᴛʜVᴀᴅᴇʀ 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 'SO- stop being evil' 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 09: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 'SO- stop being evil' Dec 14 '12 at 19:14
0

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"
laebshade
  • 2,176