Help with Shell Script displaying Directories

I am new to shell programming and have an assignment question which requires me to list the contents of the present working directory in 4 column format and highlight any subdirectories. It then requires me to develop the shell script to accept a directory name as a positional parameter (if no parameter use the present working directory); then adapt is to accept more than one directory as positional parameters; and finally adapt it to display the name of the sub directory and ask whether to: descend and list the contents, list the contents without changing the present working directory, or ignore the directory. (it was suggested that a recursive call would be handy)

I am struggling to write a shell script which performs this task. I found the following script in a forum which i think was written for old version shell (SH). I have tried to reuse this shell and adapt it to the BASH shell. If anyone is able to help me either adapt the below code or have any suggestions on how i can go about this question. Please help. Major thanks in anticipation.

#!/bin/sh

if [ -n "$1" ]
then
 WORKDIR=$1
else
 WORKDIR=`pwd`
fi

WORKDIR=`echo $WORKDIR | awk \
'{
  if (substr($0,length($0),1)=="/")
  {
   print substr($0,1,length($0)-1)
  } else {
   print $0
  }
 }'`

echo $WORKDIR

for DIR in `ls -l $WORKDIR | grep "^d" | awk '{ print $NF }'`
do
 echo "Found directory $DIR, Decend, List, Ignore (D,L,I) ?"
 read DLI
 DLI=`echo $DLI | tr "dli" "DLI"`
 case $DLI in
  "D" ) $0 $WORKDIR/$DIR;;
  "L" ) ls -l $WORKDIR/$DIR | grep -v "^d" | awk -v COUNTER=0 \
'{
  printf "%s ",$NF
  COUNTER++
  if (COUNTER>=5)
  {
   printf "\n"
   COUNTER=0
  }
 }' | column -t;;
  "I" ) echo "Ignored directory $DIR";;
 esac
done
 
ls -l $WORKDIR | grep -v "^d" | awk -v COUNTER=0 \
'{
  printf "%s ",$NF
  COUNTER++
  if (COUNTER>=5)
  {
   printf "\n"
   COUNTER=0
  }
 }' | column -t  

regards Clinton

Homework assignments are not permitted here, but maybe this will get you started:

unset files
n=0
for file in *
do
  [ -d "$file" ] &&
     files[${#files[@]}]=$file/ ||
     files[${#files[@]}]=$file
done
w=$(( $COLUMNS / 4 - 1 ))
printf "%-$w.${w}s %-$w.${w}s %-$w.${w}s %-$w.${w}s\n" "${files[@]}"

With very few exceptions, scripts written for sh will run without modification in bash.

[indent]
Please put code inside

 tags.



#!/bin/sh

if [ -n "$1" ]
then
 WORKDIR=$1
else
 WORKDIR=`pwd`
fi

WORKDIR=`echo $WORKDIR | awk \
'{
  if (substr($0,length($0),1)=="/")
  {
   print substr($0,1,length($0)-1)
  } else {
   print $0
  }
 }'`

echo $WORKDIR

for DIR in `ls -l $WORKDIR | grep "^d" | awk '{ print $NF }'`
do
 echo "Found directory $DIR, Decend, List, Ignore (D,L,I) ?"
 read DLI
 DLI=`echo $DLI | tr "dli" "DLI"`
 case $DLI in
  "D" ) $0 $WORKDIR/$DIR;;
  "L" ) ls -l $WORKDIR/$DIR | grep -v "^d" | awk -v COUNTER=0 \
'{
  printf "%s ",$NF
  COUNTER++
  if (COUNTER>=5)
  {
   printf "\n"
   COUNTER=0
  }
 }' | column -t;;
  "I" ) echo "Ignored directory $DIR";;
 esac
done

ls -l $WORKDIR | grep -v "^d" | awk -v COUNTER=0 \
'{
  printf "%s ",$NF
  COUNTER++
  if (COUNTER>=5)
  {
   printf "\n"
   COUNTER=0
  }
 }' | column -t

[/quote]

Thank - you very much for your reply and thanks for the starting point. Could you please just briefly explain what the following lines are doing just so i fully understand.

for file in *
do
  [ -d "$file" ] &&
     files[${#files[@]}]=$file/ ||
     files[${#files[@]}]=$file
done

I have got the following segment of code. It is going through each file/directory in the present working directory and asking whether it should descend into the directory. If deciding to descend it recalls the shell script with the new directory as a parameter. This is probably not the most efficient way of doing/writing this code but it seems to work. What i want to do is though, i want to only execute this code if it is a directory not if its a file. How do i test for if its a directory before descending into the code.

echo "The present working directory is: $WORKDIR "
cd $WORKDIR

for directory in *
do
    (I am thinking the test will go in here to test the value of $directory)
    echo "Found directory $directory; do you want to Descend, List or Ignore:"
    read answer
    case $answer in
    "D") WORKDIR=$WORKDIR/$directory
           echo $WORKDIR
           exec ./tryc $WORKDIR;;
     "L") (this will call a function to list the contents of the directory);;
     "I") echo "The directory was ignored" ;;
     esac
done

[/quote]

Use test -d.

if [ -d "$directory" }
then
   echo "$directory is a directory"
else
   echo "$directory is not a directory"
fi

Or you can bypass the test altogether; this will only list directories:

for directory in */

I have got my coding to the following. So far the code calls a function which is used to go through the contents of the directory and ask whether to descend (which recalls the function with the new directory), list the contents of the directory (which calls the another function designed to list the contents).
My problem is with the listing of the contents. Displaying the contents of the first directory is fine. When i go to display the contents of the second directory, what is displayed is the contents of the first directory with the contents of the second directory appended to the end. I do not want this to happen, i only want it to display the contents of the current directory only.
What have i done wrong to make it do this.

Also at present my shell is only set up to accept one parameter, how would i go about setting it up to accept multiple parameters. Use a while loop maybe around the calling functions incrementing the parameter number??

#!/bin/bash



displaydir()

{
	n=0

	local alist=$( ls $1 )

	for file in $alist

	do

	[ -d "$file" ] &&

		files[${#files[@]}]=$file/ ||

		files[${#files[@]}]=$file

	done

	w=10
 #$(($COLUMNS/4-1))

	printf "%-$w.${w}s %-$w.${w}s %-$w.${w}s %-$w.${w}s\n" "${files[@]}"
	
}


#unset files

#unset answer



descenddir()

{
	local BASEDIR=$1

	local LOCALDIR="$PWD"


	echo "The present working directory is: $BASEDIR "

	cd $BASEDIR

	echo "Do you want to display the contents of this directory?"

	read answer

	case $answer in

	"Y") displaydir "$BASEDIR";;

	"N") ;;

	esac



	local dirlist=$( ls )

	for directory in $dirlist

	do

		if [ -d $directory ]
 
		then {

			echo "Found directory $directory; do you want to Descend, List or Ignore (D,L, I): "

			read answer

			case $answer in

			"D") descenddir "$BASEDIR/$directory/";;

			"L") displaydir "$BASEDIR/$directory/";;

			"I") echo "The directory was ignored" ;;

			esac
 
		}

		fi

	done

	cd $LOCALDIR

}



if [ $1 ]
 
   then descenddir $1
 
   else descenddir "$PWD"

fi

Which line is giving you the problem?

if [ $# -gt 0 ]
then
  for dir
  do
    descenddir "$dir"
  done
else
  descenddir "$PWD"
fi

That will break if any filenames contain spaces. Use:

for file in "$1"/*

Or cd into the directory:

cd "$1"
for file in *

It would make things simpler if you execute descenddir() in a subshell:

descenddir()
(

)

Then you don't need to worry about returning to a previous directory.

That will break if $BASEDIR contains spaces. Quote the variable and check that it succeeded:

cd "$BASEDIR" || return 1
   case $answer in
     [yY]) displaydir "$BASEDIR";;
   esac

That will break if any filenames contain spaces. Use:

for file in *

There's no need for the braces.

   case $answer in
      [dD]) descenddir "$BASEDIR/$directory/";;
      [lL]) displaydir "$BASEDIR/$directory/";;
      [Ii]) echo "The directory was ignored" ;;
   esac

That will break if $LOCALDIR contains spaces. Quote the variable and check that it succeeded:

cd "$LOCALDIR" || return 1

That will break if $1 contains spaces. Quote the variable:

   then descenddir "$1"

Thankyou for your quick reply. I have not had a chance yet to try out the changes you have said. I am not exactly sure what line though is giving me the problem. It is something in the display directory function. What happens is:

When the function is called the first thing that would happen is the contents of the directory that was passed would be assigned to the variable alist. This is all good and the contents of the directory are displayed correctly in 4 columns. The problem starts when the function is called for the second time for the next directory. This is what happens. The value that is assigned the the variable alist is just the contents of the second directory passed (which is correct that is what i want). What is displayed though is the contents of the first directory again with the contents of the second directory just added to the end. All i want to be displayed is the contents of the current directory not every directories contents aswell that has been passed to the function before the current one.
It is like the value somewhere hasn't been wiped. I don't know why it is doing this. I had tried doing one of the changes you suggested which was to change to the cd directory and use the * with the for statement (within the display function) but from memory i am pretty sure it was doing the same thing with appending all directories onto the end of each other.

One thing i forgot to ask was when i am displaying the contents in four columns how would you go about highlighting the subdirectories i.e. make them a certain colour or something that differentiates them from files.

displaydir()
(
  [ -d "$1" ] && cd "$1" || return 1
  w=$(( $COLUMNS / 4  ))
  n=3
  em='\e[31m'
  no='\e[0m'
  for file in *
  do
    n=$(( ($n + 1) % 4 ))
    [ $n -gt 0 ] && printf "\e[$(( $n * $w ))C\e[D "
    if [ -d "$file" ]
    then
       printf "$em%-$w.${w}s$no\r" "$file"
    else
       printf "%-$w.${w}s\r" "$file"
    fi
    [ $n -eq 3 ] && echo
  done
)

Thank-you Thank-you Thank-you. I can not thank you enough for all your help. I have got everything to work now thanks to your help. There is one thing though that i have noticed. When i choose to list the directories; the original directory that is passed to the display function displays perfectly but then when i either descend into the subdirectory then display or just select list the contents of the subdirectory it doesn't seem to always display the contents of the subdirectory. It just doesn't display anything it just moves onto the next subdirectory.

For example if i don't pass a directory as a paramater to the shell when i run it, it uses the present working directory which is my home directory. i just have the default folders and have put a few test txt documents in a couple of the folders. When i run through the shell it displays the home folders contents but it doesn't display anything for the subdirectories. it does not display the few text documents i have placed.

Any thoughts. My code is pretty much the same except i have implemented the changes you suggested in your previous posts.

"Pretty much the same" doesn't tell us anything. If you are still having problems, post the exact code you are now using and explain exactly what is going wrong and what you expect to happen.

Below is the code i now have. OK this is an example. If this shell situated in your home directory and it was run without any parameters it would use the present working directory which is the user/home directory. OK now i get to the stage in the code where it asks if i want to display the contents of the directory i say yes and it displays fine (in four columns highlighting the subdirectories). Now it descends into the home directory and finds the first subdirectory (on my computer it is the documents folder). It then asks you if you want to descend into the folder, list the contents or ignore. If i ask it to list the contents it doesnt display anything it just moves onto the next directory (it is like it doesnt pick up anything in the directory, all i have in the directory is a few text documents so i would have thought they would have been displayed). If i choose the descend option instead it descends into the directory correctly and then it asks the question if you want to display the contents of the directory if i say yes it doesnt display anything either.

That said if i pass a parameter to it say /etc it displays the contents of some of the subdirectories. Some may not have any contents so that is fine. And some of them i don't have permission to view so that is fine. The ones that do display however display correctly. Is it just something with the home directory? or something in the code. Anyway the code is posted below.


#!/bin/bash

#descend/display/ignore the contents of a chosen directory



#displays the contents of a passed directory parameters in four column format and highlights sub-directories


displaydir()

(
	
	[ -d "$1" ] && cd "$1" || return 1

	w=15

	n=3

	em='\e[31m'

	no='\e[0m'

	for file in *

	do

		n=$(( ($n + 1) % 4 ))

		[ $n -gt 0 ] && printf "\e[$(( $n * $w ))C\e[D "

		if [ -d "$file" ]

		then

			printf "$em%-$w.${w}s$no\r" "$file"

		else

			printf "%-$w.${w}s\r" "$file"

		fi

		[ $n -eq 3 ] && echo

	done

)



#filters through the contents of the passed directory parameter and asks whether to descend into the directory

#(which in turn recalls the function), list the contents of the directory (calls the display function), or just 

#ignore the directory and move onto the next.


descenddir()

(

	local BASEDIR="$1"

	local LOCALDIR="$PWD"



	echo "The present working directory is: $BASEDIR "

	cd "$BASEDIR" || return 1

	echo "Do you want to display the contents of this directory (Y, N)?"

	read answer

	case $answer in

		[yY]) displaydir "$BASEDIR";;

	esac


	for file in *

	do

		if [ -d $file ] 

		then

			echo "Found directory $file; do you want to Descend, List or Ignore (D,L, I): "

			read answer

			case $answer in

				[dD]) descenddir "$BASEDIR/$file/";;

				[lL]) displaydir "$BASEDIR/$file/";;

				[iI]) echo "The directory was ignored" ;;

			esac
 
		fi

	done

	cd "$LOCALDIR" || return 1

)



#goes through all passed directory parameters and calls the descend function. If no parameters use PWD.

if [ $# -gt 0 ] 

   then 

	for dir

	do

		descenddir "$dir"

	done 

   else 

	descenddir "$PWD"

fi