4

My assignment is to write a bash script that reads a directory and returns the file type of each file within, including all subdirectories. Using the find command is not allowed. I've tried to implement this using essentially two for loops, but I'm getting a segmentation fault. I found that the script does work however when I pass a directory without subdirectories. Would someone be willing to look over a noob's code and tell me what's wrong? Thank you very much.

#!/bin/bash
func () {
for name in $1
do
  if [ -f "$name" ]
  then
    file "$name"
  elif [ -d "$name" ]
  then
    func "$1"
  fi
done
}

directory="$1/*"
for name in $directory
do
  if [ -f "$name" ]
  then
    file "$name"
  elif [ -d "$name" ]
  then
    func "$1"
  fi
done
9
  • 3
    The code looks blatantly circular. func calls func with the same argument if the elif condition is met. That'll generate an infinite loop. I suspect you can do everything this script is trying to do with a one-liner: find dir/ -type f -exec file '{}' \;.
    – frabjous
    May 16 at 16:03
  • 1
    @frabjous the OP cannot use find, that's mentioned in the question.
    – terdon
    May 16 at 16:05
  • 1
    @frabjous please read the question. Yes, it is homework, again, that is mentioned in the question.
    – terdon
    May 16 at 16:11
  • 1
    Do you mean func "$name" instead of func "$1"?
    – Bodo
    May 16 at 16:12
  • 7
    @frabjous we don't have any rule against homework questions, and this is actually a great example of a good homework question: the OP clearly stated it was homework, they explained the limitations involved, and most importantly they have tried to do it themselves, they didn't just come here so we can do it for them. They tried, but are having trouble with it, so asking here is absolutely fine. The annoying ones are where the asker just dumps the assignment text and doesn't even try to solve it. Here, the OP has done everything right!
    – terdon
    May 16 at 16:35

2 Answers 2

12

If the "$name" it's processing is a directory, you need to call func on its contents, and not the original argument, or you get an infinite loop, and hence the segfault.

Your code can be greatly reduced by using a function on the original argument, and have the function apply to each item separately. Right now you're repeating most of what happens in the function in the main body anyway.

#!/bin/bash
func () {
    local arg="$1"
    if [[ -f "$arg" ]] ; then
        file -- "$arg"
        return
    fi
    if [[ -d "$arg" ]] ; then
        for file in "$arg"/* ; do
            func "$file"
        done
    fi
}

func "$1"
0
2

The simplest approach, if you can use bash features, is to enable globstar and use that:

shopt -s globstar
for f in "$1"/**; do 
    if [ -f "$f" ]; then 
        file -- "$f"
    fi
done

Using the logic in your original script, you just need to give it the right input:

#!/bin/bash
func () {
for name in "$1"/*
do
  if [ -f "$name" ]
  then
    file "$name"
  elif [ -d "$name" ]
  then
    func "$name"
  fi
done
}

directory="$1"
for name in "$directory"/*
do
  if [ -f "$name" ]
  then
    file "$name"
  elif [ -d "$name" ]
  then
    func "$name"
  fi
done

You were using func $1 instead of func $name so you were entering an endless loop which led to the segfault. Next time, just add an echo to see what you are processing, that would have shown you the error immediately.

Also, you really don't need to repeat code like that. The whole point of functions is to avoid such repetition, so you could simply do:

#!/bin/bash
func () {
for name in "$1"/*
do
  echo "FF $name"
  if [ -f "$name" ]
  then
    file "$name"
  elif [ -d "$name" ]
  then
    func "$name"
  fi
done
}

directory="$1"
for name in "$directory"/*
do
  func "$name"
done
4
  • 2
    Hint to the OP for improvement: The "main" part of the script is nearly the same as the body of func. The whole block could be replaced with a call to func.
    – Bodo
    May 16 at 16:19
  • @Bodo of course, I should have added that. Thanks, fixed.
    – terdon
    May 16 at 16:31
  • A simple func $1 would be sufficient. As this looks like a homework question I originally wrote it only as a hint instead of showing the code.
    – Bodo
    May 16 at 16:45
  • Ok, so everything's working now as it should. One follow-up question: Why do you have to append /* to the argument outside of its quotes? For example, if I replace "$"/* with "$/*" in line 3 of your second/third code snippet the script doesn't execute. I'm just confused since our prof showed us the latter variant. May 18 at 17:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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