0

So I'm writing a bash script that counts the number of files in a directory and outputs a number. The function takes a directory argument as well as an optional file-type extension argument.

I am using the following lines to set the dir variable to the directory and ext variable to a regular expression that will represent all the file types to count.

dir=$1
[[ $# -eq 2 ]] && ext="*.$2" || ext="*"

The problem I am encountering occurs when I attempt to run the following line:

echo $(find $dir -maxdepth 1 -type f -name $ext | wc -l)

Running the script from the terminal works when I provide the second file-type argument but fails when I don't.

harrison@Luminous:~$ bash Documents/howmany.sh Documents/ sh
3
harrison@Luminous:~$ bash Documents/howmany.sh Documents/
find: paths must precede expression: Desktop
Usage: find [-H] [-L] [-P] [-Olevel] [-D help|tree|search|stat|rates|opt|exec] [path...] [expression]
0

I have searched for this error and I know it's an issue with the shell expanding my wildcard as explained here. I've tried experimenting with single quotes, double quotes, and backslashes to escape the asterisk but nothing seems to work. What's particularly interesting is that when I try running this directly through the terminal, it works perfectly fine.

harrison@Luminous:~$ echo $(find Documents/ -maxdepth 1 -type f -name "*" | wc -l)
6
Community
  • 1
  • 1
  • hm, that worked for me in a bash script when I switched "Documents" with one in my cwd – cianius Sep 22 '14 at 22:27
  • 3
    You need to quote `"$dir"` in the find command. Probably `"$ext"` too. Also there is **literally** no reason to wrap that `find` command in `echo $(...)`. – Etan Reisner Sep 22 '14 at 22:29
  • @jm666 If you want find to output in one line then use `-printf` to tell it to do that. Don't abuse the shell's whitespace handling for that. But yes, there are certainly times when the shell's behaviour can be used intentionally. – Etan Reisner Sep 22 '14 at 23:13
  • 1
    BTW, next time you want to know why your script isn't behaving the same way as something you'd run on the command line, run `bash -x yourscript arg1 arg2 ...`, and see what it emits. – Charles Duffy Sep 22 '14 at 23:37

2 Answers2

4

Simplified:

dir=${1:-.}           #if $1 not set use .
name=${2+*.$2}        #if $2 is set use *.$2 for name
name=${name:-*}       #if name still isnt set, use *

find "$dir" -name "$name" -print   #use quotes

or

name=${2+*.$2}        #if $2 is set use *.$2 for name
find "${1:-.}" -name "${name:-*}" -print   #use quotes

also, as @John Kugelman says, you could use:

 name=${2+*.$2}
 find "${1:-.}" ${name:+-name "$name"} -print

find . -name "*" -print is the same as find . -print, so if $name isn't set, there's no need to specify -name "*".

Tom Fenech
  • 72,334
  • 12
  • 107
  • 141
clt60
  • 62,119
  • 17
  • 107
  • 194
0

Try this:

dir="$1"
[[ $# -eq 2 ]] && ext='*.$2' || ext='*'

If that doesn't work, you can just switch to an if statement, where you use the -name pattern in a branch and you don't in the other.

A couple more points:

  • Those are not regular expressions, but rather shell patterns.
  • echo $(command) is just equivalent to command.
SukkoPera
  • 621
  • 4
  • 8
  • Shouldn't change anything since `*` is not expanded in double quotes and word-splitting does not occur on the right side of assignment. He does need quotes in the `find`/`echo` as @EtanReisner pointed out in comments. Good points at the end. – Reinstate Monica Please Sep 22 '14 at 22:48
  • Indeed, the quoting on those assignment lines is not the issue. The use of those variables is. But the other points are good. – Etan Reisner Sep 22 '14 at 23:14
  • Fine, I wasn't sure that would be effective, that's why I said "try" :). – SukkoPera Sep 23 '14 at 08:51