views:

58

answers:

5

While trying to make a conditional statement to check the amount of files in a directory, I have stumbled upon a problem.

My initial way of writing this script:

ELEM=`ls -l $DIR | wc -l` 
if [ $ELEM -lt 5 ] ; then

Which works. However I want to move my $ELEM into the conditional parameter block so it can be interpreted when I reach that if statement. I have tried playing around with different combinations of single quotes, double quotes, back ticks, and parenthese. Does anyone know how to solve this problem?

A: 

For some reason it didn't occur to me until just now to use this:

ELEM="`ls -l $DIR | wc -l`"

Thanks for your time.

Melissa
I'm not sure how that solves your problem; the expression is still evaluated at that line, the result is just quoted
Michael Mrozek
Is there anyway to fix this problem?
Melissa
Plus that the quoting is wrong (`$DIR`) is unquoted. Also I don't see the problem at all: it is completely equivalent whether the counting happens as part of an assignment or inside a test.
Philipp
My intention was to completely avoid assigning the variable if I could. Thank you for your help.
Melissa
+2  A: 

Never use ls in batch mode, use globbing instead. Also avoid backquotes, unquoted variables, and the [ builtin:

shopt -s nullglob  # expand to an empty array if directory is empty
shopt -s dotglob   # also glob dotfiles
files=("$DIR"/*)
count=${#files[@]}
if ((count > 5))
then
  ...
fi
Philipp
Why is it bad to use 'ls' in in shell scripts? Why do I need to avoid using back ticks, unquoted variables, and brackets? I thought globbing was used primarily for regex manipulations.
Melissa
See http://mywiki.wooledge.org/BashPitfalls, http://mywiki.wooledge.org/BashFAQ/082, http://mywiki.wooledge.org/BashFAQ/031, http://mywiki.wooledge.org/BashGuide/Patterns, http://mywiki.wooledge.org/BashGuide/Practices. Bash programming has so many pitfalls that you should really read all pages in Greg's wiki related to Bash before writing any scripts.
Philipp
Thanks for your help.
Melissa
@Melissa: Globs are not regular expressions.
Dennis Williamson
Heh, I didn't say they were regular expressions.
Melissa
@Melissa: You said "I thought globbing was used primarily for regex manipulations."
Dennis Williamson
Maybe you misunderstood me. I was making a statement about how globbing recognizes and intreprets wild cards. Thus globbing is a tool used with regex and are not themelves regular expressions. Edit: Unless you're trying to implicitly state that wildcards are distinctly different from the term regex. Then sorry about the confusion.
Melissa
The matter is made complicated because bash supports both globbing *and* regexes, with superficially similar syntaxes. To get a reliable file count, set the shell options `nullglob` and `dotglob`.
Philipp
@Melissa: Yes, that's what I was trying to say.
Dennis Williamson
A: 

What you seem to want to be able to do is something that C allows:

if (elem = countfiles(dir) < 5)

Bash can do it like this:

if (( (elem = $(ls "$DIR" 2>/dev/null | wc -l) ) < 5))

Or you could create a function called countfiles and use it like this:

if (( ( elem = $(countfile "$DIR") ) < 5))

where the function might be something like:

countfiles() {
    find "$1" -maxdepth 1 -type f | wc -l
}
Dennis Williamson
The fourth version won't work on file names containing newlines, and the second one will probably be nonportable unless the behavior of `ls` w.r.t. file names with newlines is standardized. Why calling two external processes for something that Bash can do without external help?
Philipp
@Philipp: The point of my answer was not to address the issues with using `ls` inappropriately or other pitfalls of Bash. You covered those well in your comment attached to your answer. I should have made reference to that in my answer for clarity. The point of my answer was to address the evidence that I saw in the OP's question and various comments that there seemed to be a desire to use C-style side effects to set a variable and test it at the same time. Speaking of filenames, [this](http://www.dwheeler.com/essays/fixing-unix-linux-filenames.html) is required reading.
Dennis Williamson
A: 

Most of the solutions posted so far are (IMHO) overcomplicated. As long as the ls command on your system supports the -B option (display special characters as octal escape sequences), you can just use:

if [[ $(ls -B "$DIR" | wc -l) -lt 5 ]]; then

Note that while parsing ls is generally a bad idea, in this case all we're trying to do is find the number of listings, so there's a lot less to go wrong than usual. In fact, the only thing that wc -l cares about is the number of lines, so the only thing that could go wrong is that a filename might have a newline in it, and hence be mistaken for two filenames; using the -B option to ls protects against this, so it winds up being safe (at least as far as I can see).

Gordon Davisson
A: 

Improved countfiles function (which doesn't get confused by newlines in file names):

countfiles() {
   find "$1" -maxdepth 1 -type f -print0 | tr -dc '\0' | wc -c
}
tilo