Checking from shell script if a directory contains files - bash

From a shell script, how do I check if a directory contains files?
Something similar to this
if [ -e /some/dir/* ]; then echo "huzzah"; fi;
but which works if the directory contains one or several files (the above one only works with exactly 0 or 1 files).

Three best tricks
shopt -s nullglob dotglob; f=your/dir/*; ((${#f}))
This trick is 100% bash and invokes (spawns) a sub-shell. The idea is from Bruno De Fraine and improved by teambob's comment.
files=$(shopt -s nullglob dotglob; echo your/dir/*)
if (( ${#files} ))
then
echo "contains files"
else
echo "empty (or does not exist or is a file)"
fi
Note: no difference between an empty directory and a non-existing one (and even when the provided path is a file).
There is a similar alternative and more details (and more examples) on the 'official' FAQ for #bash IRC channel:
if (shopt -s nullglob dotglob; f=(*); ((${#f[#]})))
then
echo "contains files"
else
echo "empty (or does not exist, or is a file)"
fi
[ -n "$(ls -A your/dir)" ]
This trick is inspired from nixCraft's article posted in 2007. Add 2>/dev/null to suppress the output error "No such file or directory".
See also Andrew Taylor's answer (2008) and gr8can8dian's answer (2011).
if [ -n "$(ls -A your/dir 2>/dev/null)" ]
then
echo "contains files (or is a file)"
else
echo "empty (or does not exist)"
fi
or the one-line bashism version:
[[ $(ls -A your/dir) ]] && echo "contains files" || echo "empty"
Note: ls returns $?=2 when the directory does not exist. But no difference between a file and an empty directory.
[ -n "$(find your/dir -prune -empty)" ]
This last trick is inspired from gravstar's answer where -maxdepth 0 is replaced by -prune and improved by phils's comment.
if [ -n "$(find your/dir -prune -empty 2>/dev/null)" ]
then
echo "empty (directory or file)"
else
echo "contains files (or does not exist)"
fi
a variation using -type d:
if [ -n "$(find your/dir -prune -empty -type d 2>/dev/null)" ]
then
echo "empty directory"
else
echo "contains files (or does not exist or is not a directory)"
fi
Explanation:
find -prune is similar than find -maxdepth 0 using less characters
find -empty prints the empty directories and files
find -type d prints directories only
Note: You could also replace [ -n "$(find your/dir -prune -empty)" ] by just the shorten version below:
if [ `find your/dir -prune -empty 2>/dev/null` ]
then
echo "empty (directory or file)"
else
echo "contains files (or does not exist)"
fi
This last code works most of the cases but be aware that malicious paths could express a command...

The solutions so far use ls. Here's an all bash solution:
#!/bin/bash
shopt -s nullglob dotglob # To include hidden files
files=(/some/dir/*)
if [ ${#files[#]} -gt 0 ]; then echo "huzzah"; fi

How about the following:
if find /some/dir/ -maxdepth 0 -empty | read v; then echo "Empty dir"; fi
This way there is no need for generating a complete listing of the contents of the directory. The read is both to discard the output and make the expression evaluate to true only when something is read (i.e. /some/dir/ is found empty by find).

Try:
if [ ! -z `ls /some/dir/*` ]; then echo "huzzah"; fi

Take care with directories with a lot of files! It could take a some time to evaluate the ls command.
IMO the best solution is the one that uses
find /some/dir/ -maxdepth 0 -empty

# Works on hidden files, directories and regular files
### isEmpty()
# This function takes one parameter:
# $1 is the directory to check
# Echoes "huzzah" if the directory has files
function isEmpty(){
if [ "$(ls -A $1)" ]; then
echo "huzzah"
else
echo "has no files"
fi
}

DIR="/some/dir"
if [ "$(ls -A $DIR)" ]; then
echo 'There is something alive in here'
fi

Could you compare the output of this?
ls -A /some/dir | wc -l

This may be a really late response but here is a solution that works. This line only recognizes th existance of files! It will not give you a false positive if directories exist.
if find /path/to/check/* -maxdepth 0 -type f | read
then echo "Files Exist"
fi

# Checks whether a directory contains any nonhidden files.
#
# usage: if isempty "$HOME"; then echo "Welcome home"; fi
#
isempty() {
for _ief in $1/*; do
if [ -e "$_ief" ]; then
return 1
fi
done
return 0
}
Some implementation notes:
The for loop avoids a call to an external ls process. It still reads all the directory entries once. This can only be optimized away by writing a C program that uses readdir() explicitly.
The test -e inside the loop catches the case of an empty directory, in which case the variable _ief would be assigned the value "somedir/*". Only if that file exists will the function return "nonempty"
This function will work in all POSIX implementations. But be aware that the Solaris /bin/sh doesn't fall into that category. Its test implementation doesn't support the -e flag.

This tells me if the directory is empty or if it's not, the number of files it contains.
directory="/some/dir"
number_of_files=$(ls -A $directory | wc -l)
if [ "$number_of_files" == "0" ]; then
echo "directory $directory is empty"
else
echo "directory $directory contains $number_of_files files"
fi

ZSH
I know the question was marked for bash; but, just for reference, for zsh users:
Test for non-empty directory
To check if foo is non-empty:
$ for i in foo(NF) ; do ... ; done
where, if foo is non-empty, the code in the for block will be executed.
Test for empty directory
To check if foo is empty:
$ for i in foo(N/^F) ; do ... ; done
where, if foo is empty, the code in the for block will be executed.
Notes
We did not need to quote the directory foo above, but we can do so if we need to:
$ for i in 'some directory!'(NF) ; do ... ; done
We can also test more than one object, even if it is not a directory:
$ mkdir X # empty directory
$ touch f # regular file
$ for i in X(N/^F) f(N/^F) ; do echo $i ; done # echo empty directories
X
Anything that is not a directory will just be ignored.
Extras
Since we are globbing, we can use any glob (or brace expansion):
$ mkdir X X1 X2 Y Y1 Y2 Z
$ touch Xf # create regular file
$ touch X1/f # directory X1 is not empty
$ touch Y1/.f # directory Y1 is not empty
$ ls -F # list all objects
X/ X1/ X2/ Xf Y/ Y1/ Y2/ Z/
$ for i in {X,Y}*(N/^F); do printf "$i "; done; echo # print empty directories
X X2 Y Y2
We can also examine objects that are placed in an array. With the directories as above, for example:
$ ls -F # list all objects
X/ X1/ X2/ Xf Y/ Y1/ Y2/ Z/
$ arr=(*) # place objects into array "arr"
$ for i in ${^arr}(N/^F); do printf "$i "; done; echo
X X2 Y Y2 Z
Thus, we can test objects that may already be set in an array parameter.
Note that the code in the for block is, obviously, executed on every directory in turn. If this is not desirable then you can simply populate an array parameter and then operate on that parameter:
$ for i in *(NF) ; do full_directories+=($i) ; done
$ do_something $full_directories
Explanation
For zsh users there is the (F) glob qualifier (see man zshexpn), which matches "full" (non-empty) directories:
$ mkdir X Y
$ touch Y/.f # Y is now not empty
$ touch f # create a regular file
$ ls -dF * # list everything in the current directory
f X/ Y/
$ ls -dF *(F) # will list only "full" directories
Y/
The qualifier (F) lists objects that match: is a directory AND is not empty. So, (^F) matches: not a directory OR is empty. Thus, (^F) alone would also list regular files, for example. Thus, as explained on the zshexp man page, we also need the (/) glob qualifier, which lists only directories:
$ mkdir X Y Z
$ touch X/f Y/.f # directories X and Y now not empty
$ for i in *(/^F) ; do echo $i ; done
Z
Thus, to check if a given directory is empty, you can therefore run:
$ mkdir X
$ for i in X(/^F) ; do echo $i ; done ; echo "finished"
X
finished
and just to be sure that a non-empty directory would not be captured:
$ mkdir Y
$ touch Y/.f
$ for i in Y(/^F) ; do echo $i ; done ; echo "finished"
zsh: no matches found: Y(/^F)
finished
Oops! Since Y is not empty, zsh finds no matches for (/^F) ("directories that are empty") and thus spits out an error message saying that no matches for the glob were found. We therefore need to suppress these possible error messages with the (N) glob qualifier:
$ mkdir Y
$ touch Y/.f
$ for i in Y(N/^F) ; do echo $i ; done ; echo "finished"
finished
Thus, for empty directories we need the qualifier (N/^F), which you can read as: "don't warn me about failures, directories that are not full".
Similarly, for non-empty directories we need the qualifier (NF), which we can likewise read as: "don't warn me about failures, full directories".

dir_is_empty() {
[ "${1##*/}" = "*" ]
}
if dir_is_empty /some/dir/* ; then
echo "huzzah"
fi
Assume you don't have a file named * into /any/dir/you/check, it should work on bash dash posh busybox sh and zsh but (for zsh) require unsetopt nomatch.
Performances should be comparable to any ls which use *(glob), I guess will be slow on directories with many nodes (my /usr/bin with 3000+ files went not that slow), will use at least memory enough to allocate all dirs/filenames (and more) as they are all passed (resolved) to the function as arguments, some shell probably have limits on number of arguments and/or length of arguments.
A portable fast O(1) zero resources way to check if a directory is empty would be nice to have.
update
The version above doesn't account for hidden files/dirs, in case some more test is required, like the is_empty from Rich’s sh (POSIX shell) tricks:
is_empty () (
cd "$1"
set -- .[!.]* ; test -f "$1" && return 1
set -- ..?* ; test -f "$1" && return 1
set -- * ; test -f "$1" && return 1
return 0 )
But, instead, I'm thinking about something like this:
dir_is_empty() {
[ "$(find "$1" -name "?*" | dd bs=$((${#1}+3)) count=1 2>/dev/null)" = "$1" ]
}
Some concern about trailing slashes differences from the argument and the find output when the dir is empty, and trailing newlines (but this should be easy to handle), sadly on my busybox sh show what is probably a bug on the find -> dd pipe with the output truncated randomically (if I used cat the output is always the same, seems to be dd with the argument count).

Taking a hint (or several) from olibre's answer, I like a Bash function:
function isEmptyDir {
[ -d $1 -a -n "$( find $1 -prune -empty 2>/dev/null )" ]
}
Because while it creates one subshell, it's as close to an O(1) solution as I can imagine and giving it a name makes it readable. I can then write
if isEmptyDir somedir
then
echo somedir is an empty directory
else
echo somedir does not exist, is not a dir, is unreadable, or is not empty
fi
As for O(1) there are outlier cases: if a large directory has had all or all but the last entry deleted, "find" may have to read the whole thing to determine whether it's empty. I believe that expected performance is O(1) but worst-case is linear in the directory size. I have not measured this.

I am surprised the wooledge guide on empty directories hasn't been mentioned. This guide, and all of wooledge really, is a must read for shell type questions.
Of note from that page:
Never try to parse ls output. Even ls -A solutions can break (e.g. on HP-UX, if you are root, ls -A does
the exact opposite of what it does if you're not root -- and no, I can't make up something that
incredibly stupid).
In fact, one may wish to avoid the direct question altogether. Usually people want to know whether a
directory is empty because they want to do something involving the files therein, etc. Look to the larger
question. For example, one of these find-based examples may be an appropriate solution:
# Bourne
find "$somedir" -type f -exec echo Found unexpected file {} \;
find "$somedir" -maxdepth 0 -empty -exec echo {} is empty. \; # GNU/BSD
find "$somedir" -type d -empty -exec cp /my/configfile {} \; # GNU/BSD
Most commonly, all that's really needed is something like this:
# Bourne
for f in ./*.mpg; do
test -f "$f" || continue
mympgviewer "$f"
done
In other words, the person asking the question may have thought an explicit empty-directory test was
needed to avoid an error message like mympgviewer: ./*.mpg: No such file or directory when in fact no
such test is required.

Small variation of Bruno's answer:
files=$(ls -1 /some/dir| wc -l)
if [ $files -gt 0 ]
then
echo "Contains files"
else
echo "Empty"
fi
It works for me

With some workaround I could find a simple way to find out whether there are files in a directory. This can extend with more with grep commands to check specifically .xml or .txt files etc. Ex : ls /some/dir | grep xml | wc -l | grep -w "0"
#!/bin/bash
if ([ $(ls /some/dir | wc -l | grep -w "0") ])
then
echo 'No files'
else
echo 'Found files'
fi

if [[ -s somedir ]]; then
echo "Files present"
fi
In my testing with bash 5.0.17, [[ -s somedir ]] will return true if somedir has any children. The same is true of [ -s somedir ]. Note that this will also return true if there are hidden files or subdirectories. It may also be filesystem-dependent.

It really feels like there should be an option to test for an empty directory.
I'll leave that editorial comment as a suggestion to the maintainers of the test command, but the counterpart exists for empty files.
In the trivial use case that brought me here, I'm not worried about looping through a huge number of files, nor am I worried about .files. I was hoping to find the aforementioned "missing" operand to test. C'est la guerre.
In the example below directory empty is empty, and full has files.
$ for f in empty/*; do test -e $f; done
$ echo $?
1
$ for f in full/*; do test -e $f; done
$ echo $?
0
Or, shorter and uglier still, but again only for relatively trivial use cases:
$ echo empty/*| grep \*
$ echo $?
1
$ echo full/* | grep \*
$ echo $?
0

So far I haven't seen an answer that uses grep which I think would give a simpler answer (with not too many weird symbols!). Here is how I would
check if any files exist in the directory using bourne shell:
this returns the number of files in a directory:
ls -l <directory> | egrep -c "^-"
you can fill in the directory path in where directory is written. The first half of the pipe ensures that the first character of output is "-" for each file. egrep then counts the number of line that start with that
symbol using regular expressions. now all you have to do is store the number you obtain and compare it using backquotes like:
#!/bin/sh
fileNum=`ls -l <directory> | egrep -c "^-"`
if [ $fileNum == x ]
then
#do what you want to do
fi
x is a variable of your choice.

Mixing prune things and last answers, I got to
find "$some_dir" -prune -empty -type d | read && echo empty || echo "not empty"
that works for paths with spaces too

Simple answer with bash:
if [[ $(ls /some/dir/) ]]; then echo "huzzah"; fi;

I would go for find:
if [ -z "$(find $dir -maxdepth 1 -type f)" ]; then
echo "$dir has NO files"
else
echo "$dir has files"
This checks the output of looking for just files in the directory, without going through the subdirectories. Then it checks the output using the -z option taken from man test:
-z STRING
the length of STRING is zero
See some outcomes:
$ mkdir aaa
$ dir="aaa"
Empty dir:
$ [ -z "$(find aaa/ -maxdepth 1 -type f)" ] && echo "empty"
empty
Just dirs in it:
$ mkdir aaa/bbb
$ [ -z "$(find aaa/ -maxdepth 1 -type f)" ] && echo "empty"
empty
A file in the directory:
$ touch aaa/myfile
$ [ -z "$(find aaa/ -maxdepth 1 -type f)" ] && echo "empty"
$ rm aaa/myfile
A file in a subdirectory:
$ touch aaa/bbb/another_file
$ [ -z "$(find aaa/ -maxdepth 1 -type f)" ] && echo "empty"
empty

Without calling utils like ls, find, etc.:
POSIX safe, i.e. not dependent on your Bash / xyz shell / ls / etc. version:
dir="/some/dir"
[ "$(echo $dir/*)x" != "$dir/*x" ] || [ "$(echo $dir/.[^.]*)x" != "$dir/.[^.]*x" ] || echo "empty dir"
The idea:
echo * lists non-dot files
echo .[^.]* lists dot files except of "." and ".."
if echo finds no matches, it returns the search expression, i.e. here * or .[^.]* - which both are no real strings and have to be concatenated with e.g. a letter to coerce a string
|| alternates the possibilities in a short circuit: there is at least one non-dot file or dir OR at least one dot file or dir OR the directory is empty - on execution level: "if first possibility fails, try next one, if this fails, try next one"; here technically Bash "tries to execute" echo "empty dir", put your action for empty dirs here (eg. exit).
Checked with symlinks, yet to check with more exotic possible file types.

In another thread How to test if a directory is empty with find i proposed this
[ "$(cd $dir;echo *)" = "*" ] && echo empty || echo non-empty
With the rationale that, $dir do exist because the question is "Checking from shell script if a directory contains files", and that * even on big dir is not that big, on my system /usr/bin/* is just 12Kb.
Update: Thanx #hh skladby, the fixed one.
[ "$(cd $dir;echo .* *)" = ". .. *" ] && echo empty || echo non-empty

if ls /some/dir/* >/dev/null 2>&1 ; then echo "huzzah"; fi;

to test a specific target directory
if [ -d $target_dir ]; then
ls_contents=$(ls -1 $target_dir | xargs);
if [ ! -z "$ls_contents" -a "$ls_contents" != "" ]; then
echo "is not empty";
else
echo "is empty";
fi;
else
echo "directory does not exist";
fi;

Try with command find.
Specify the directory hardcoded or as argument.
Then initiate find to search all files inside the directory.
Check if return of find is null.
Echo the data of find
#!/bin/bash
_DIR="/home/user/test/"
#_DIR=$1
_FIND=$(find $_DIR -type f )
if [ -n "$_FIND" ]
then
echo -e "$_DIR contains files or subdirs with files \n\n "
echo "$_FIND"
else
echo "empty (or does not exist)"
fi

I dislike the ls - A solutions posted. Most likely you wish to test if the directory is empty because you don't wish to delete it. The following does that. If however you just wish to log an empty file, surely deleting and recreating it is quicker then listing possibly infinite files?
This should work...
if ! rmdir ${target}
then
echo "not empty"
else
echo "empty"
mkdir ${target}
fi

Works well for me this (when dir exist):
some_dir="/some/dir with whitespace & other characters/"
if find "`echo "$some_dir"`" -maxdepth 0 -empty | read v; then echo "Empty dir"; fi
With full check:
if [ -d "$some_dir" ]; then
if find "`echo "$some_dir"`" -maxdepth 0 -empty | read v; then echo "Empty dir"; else "Dir is NOT empty" fi
fi

Related

How to delete specific sym links in $HOME given a list of their coordinating files in O(n lg n)?

I've made a manager for my dot files that creates sym links between each of the dot files in my dot_files/ dir and my $HOME.
I'm trying to write a script that deletes all of these specific sym links and no others.
Here's my O(n^2) solution that does the job fine.
delete_sym_links () {
# Include dot (.) files while looping
shopt -s dotglob
for DOT_FILE in ~/dot_files/*;
do
if [ ! -d "$DOT_FILE" ];
then
for FILE in ~/*;
do
if [ -h "$FILE" ] && [ $(basename $DOT_FILE) = $(basename $FILE) ];
then
rm "$FILE"
echo deleted "$FILE"
fi
done
fi
done
}
I'm trying to get the runtime down to O(n lg n). The bash is really tripping me up though.
Something like...
delete_sym_links () {
SYM_LINKS=($(find $HOME -maxdepth 1 -type l -ls | sort -n))
NUM_SYM_LINKS=$(find $HOME -maxdepth 1 -type l -ls | wc -l)
DOT_FILES=$(find $HOME/dot_files/ -maxdepth 1 -name ".*" -type f | sort -n)
NUM_DOT_FILES=$(find $HOME/dot_files/ -maxdepth 1 -name ".*" -type f | wc -l)
i=0
j=0
while (("$i" < "$NUM_SYM_LINKS")) && (("$j" < "$NUM_DOT_FILES"));
do
if [ $(basename ${SYM_LINKS[$i]}) = $(basename ${DOT_FILES[$j]}) ];
then
echo removing sym link ${SYM_LINKS[$i]}
rm ${SYM_LINKS[$i]}
((j++))
else
((i++))
fi
done
echo "All dot_files sym links removed"
}
Try this Shellcheck-clean, O(n), code:
function delete_symlinks
{
local df_path df_name home_df_path
for df_path in ~/dot_files/.* ; do
[[ -d $df_path ]] && continue
df_name=${df_path##*/}
home_df_path=~/$df_name
if [[ -L $home_df_path ]] ; then
rm -- "$home_df_path"
printf 'deleted %s\n' "$home_df_path"
fi
done
return 0
}
It uses a ksh-style function definition (a personal preference).
All variables are localized to avoid clashes with global variables.
Lowercase is used for all variable names. See Correct Bash and shell script variable capitalization.
It uses [[ instead of [ for tests. See Is double square brackets [[ ]] preferable over single square brackets [ ] in Bash?
${var##*/} is used instead of $(basename $var) for safety and speed. See Removing part of a string (BashFAQ/100 (How do I do string manipulation in bash?)).
printf is used instead of echo for safety. See the accepted, and excellent, answer to Why is printf better than echo?.
As in the code in the question, no attempt is made to check that the symlink being deleted actually links to the corresponding file under 'dotfiles'. I would fix this in a real program because it might remove a manually-created symlink.
bash has associative arrays. We can store a unique identifier for each dotfile and then check the home directory files for a match.
I believe this is O(m+n) compared to your original O(m*n).
Here's an example using inode:
delete_sym_links () (
declare -A inode
shopt -s dotglob nullglob
ls -diL ~/dot_files/* | (
while read i name; do
[ ! -f "$name" ] && continue
inode2name[$i]="$name"
done
for name in ~/*; do
[ ! -h "$name" ] && continue
read i junk <<<$( ls -diL "$name" )
if [ "$junk" != "$name" ]; then
echo 1>&2 "ls parse error! ($name)"
fi
if [ -n "${inode2name[i]}" ]; then
echo "found: $name -> ${inode2name[$i]}"
echo "rm \"$name\""
fi
done
)
)
As ~/dot_files/ is a subdirectory of ~/, I have assumed they are on the same filesystem so that inode can be used as the unique id.
Parsing ls (in the while loop) is frowned upon but is a portable way to extract the inode as long as we don't have strange filenames (eg. with embedded newlines). We only need POSIX options.
We call ls rather a lot in the for loop so it could have a big effect on the constant factor of the complexity, but it's only on possible matches so perhaps it's not so bad.
Using fn()() instead of more common fn(){} localises the shopt
If we don't like parsing ls but we have realpath, we can do:
delete_sym_links () (
declare -A real
shopt -s dotglob nullglob
for name in ~/dot_files/*; do
[ ! -f "name" ] && continue
real="$(realpath "$name")"
real2name["$real"]="$name"
done
for name in ~/*; do
[ ! -h "$name" ] && continue
real="$(realpath "$name")"
if [ -n "${real2name["$real"]}" ]; then
echo 1>&2 "found: $name -> ${real2name["$real"]}"
echo "rm \"$name\""
fi
done
)

Check if folder contains glob

On my Mac I am trying to figure out a way to check a mounted volume to a server to see if the directory receives a log file through a shell script that will be used in launchd set to a time interval.
From my searches and historically I've used:
$DIR="/path/to/file"
THEFILES=(`find ./ -maxdepth 1 -name "*.log"`)
if [ ${#THEFILES[#]} -gt 0 ]; then
echo "exists"
else
echo "nothing"
fi
If the shell script is placed inside that particular directory and the files are present. However, when I move the script outside of that directory and try:
THEFILES=(`find ./ -maxdepth 1 -name "*.log"`)
cd $DIR
if [ ${#THEFILES[#]} -gt 0 ]; then
echo "exists"
else
echo "nothing"
fi
I get a constant return of nothing. I thought it might be in regards to the depth so I changed -maxdepth 1 to -maxdepth 0 but I still get nothing. Through searching I ran across "Check whether a certain file type/extension exists in directory" and tried:
THEFILES=$(ls "$DIR/.log" 2> /dev/null | wc -l)
echo $THEFILES
but I'm returned a constant 0. When I searched further I ran across "Checking from shell script if a directory contains files" and tried a variation using find with:
THEFILES=$(find "$DIR" -type f -regex '*.log')
cd $DIR
if [ ${#THEFILES[#]} -gt 0 ]; then
echo "exists"
else
echo "nothing"
fi
with a blank return. When I try:
if [ -n "$(ls -A $DIR)" ]; then
echo "exists"
else
echo "nothing"
fi
I get a blank terminal returned. On this answer I dont have prune or shopt on my Mac. So how I can I check a mounted server's directory to see if a particular file with a specific extension exists that will not give a false return from hidden files?
EDIT:
Per comment I tried removing the depth with:
THEFILES=$(find ./ -name "*.log")
but I get a blank return but if I drop a .log file in there it runs but I don't understand why else isn't returning nothing unless it's considering the hidden files. Thanks to l'L'l I learned -prune was in find's utility but when I try:
if [ -n "$(find $DIR -prune -empty -type d)" ]; then
I get a constant return of nothing when there is a LOG file present.
The Bash builtin compgen "displays possible completions based on the options" and is typically used for autocomplete scripts, but can be used for this purpose also. By default it outputs the completion list, so redirect it to /dev/null.
#!/bin/bash
dir=/some/log/dir
if compgen -G "$dir/*.log" > /dev/null; then
# do stuff
fi
Each of your proposed answer is really close to working!
I didn't use the -maxdepth option, if it is important to you to only check the files under $DIR but not its subdirectories, feel free to add -maxdepth 1 to any call to find.
Use lowercase variables as pointed by mklement0 to avoid conflicts with environment variables.
Answer 1
You set dir, but use ./ instead...
dir="/path/to/dir"
# Create an array of filenames
files=( $(find "$dir" -name "*.log") )
if [ ${#files[#]} -gt 0 ]; then # if the length of the array is more than 0
echo "exists"
else
echo "nothing"
Answer 2
You use cd $dir after running find...
dir="path/to/dir"
cd "$dir"
files=( $(find -name "*.log") )
if [ ${#files[#]} -gt 0 ]; then
echo "exists"
else
echo "nothing"
fi
Answer 3
you forgot a '*' before the '.log'... Have a look a the documentation for globbing
dir="/path/to/dir"
# print all the files in "$DIR" that end with ".log"
# and count the number of lines
nbfiles=$(ls "$dir/"*.log 2> /dev/null | wc -l) # nbfiles is an integer
if [ $nbfiles -gt 0 ]; then
echo "exists"
else
echo "nothing
fi
Answer 4
The macOS man page of find suggests that regex uses Basic Regular Expressions by default:
-E
Interpret regular expressions followed by -regex and -iregex
options as extended (modern) regular expressions rather than
basic regular expressions (BRE's).
You are missing a . before the * ; the cd $dir is not necessary since you pass $dir as an argument to find ; and you don't store the output of find in an array, so you can't check the length of the array.
# The output of find is stored as a string
files=$(find "/path/to/dir" -type f -regex '.*\.log')
if [ -n "$files" ]; then # Test that the string $files is not empty
echo "exists"
else
echo "nothing"
fi

bash string length in a loop

I am looping through a folder and depending on the length of files do certain condition. I seem not to come right with that. I evaluate and output the length of a string in the terminal.
echo $file|wc -c gives me the answer of all files in the terminal.
But incorporating this into a loop is impossible
for file in `*.zip`; do
if [[ echo $file|wc -c ==9]]; then
some commands
where I want to operate on files that have a length of nine characters
Try this one:
for file in *.zip ; do
wcout=$(wc -c "$file")
if [[ ${wcout%% *} -eq 9 ]] ; then
# some commands
fi
done
The %% operator in variable expansion deletes everything that match the pattern after it. This is glob pattern, not regular expression.
Opposite to natural good sense of typical programmers the == operator in BASH compares strings, not numbers.
Alternatively (following the comment) you can:
for file in *.zip ; do
wcout=$(wc -c < "$file")
if [[ ${wcout} -eq 9 ]] ; then
# some commands
fi
done
Additional observation is that if BASH cannot expand *.zip as there is no ZIP files in the current directory it will pass "*.zip" into $file and let single iteration of the loop. That leads to the error reported by wc command. So it would be recommended to add:
if [[ -e ${file} ]] ; then ...
as a prevention mechanism.
Comments leads to another form of this solution (plus I added my safety mechanism):
for file in *.zip ; do
if [[ -e "$file" && (( $(wc -c < "$file") == 9 )) ]] ; then
# some commands
fi
done
using filter outside the loop
ls -1 *.zip \
| grep -E '^.{9}$' \
| while read FileName
do
# Your action
done
using filter inside loop
ls -1 *.zip \
| while read FileName
do
if [ ${#FileName} -eq 9 ]
then
# Your action
fi
done
alternative to ls -1 that is always a bit dangereous, find . -name '*.zip' -print [ but you neet to add 2 char length or filter the name form headin ./ and maybe limit to current folder depth ]

How do I check if a directory has child directories?

Using bash, how do I write an if statement that checks if a certain directory, stored in the a script variable named "$DIR", contains child directories that are not "." or ".."?
Thanks, - Dave
As the comments have pointed out, things have changed in the last 9 years! The dot dirs are no longer returned as part of find and instead the directory specified in the find command is.
So, if you want to stay with this approach:
#!/bin/bash
subdircount=$(find /tmp/test -maxdepth 1 -type d | wc -l)
if [[ "$subdircount" -eq 1 ]]
then
echo "none of interest"
else
echo "something is in there"
fi
(originally accepted answer from 2011)
#!/usr/bin/bash
subdircount=`find /d/temp/ -maxdepth 1 -type d | wc -l`
if [ $subdircount -eq 2 ]
then
echo "none of interest"
else
echo "something is in there"
fi
Here's a more minimalist solution that will perform the test in a single line..
ls $DIR/*/ >/dev/null 2>&1 ;
if [ $? == 0 ];
then
echo Subdirs
else
echo No-subdirs
fi
By putting / after the * wildcard you select only directories, so if there is no directories then ls returns error-status 2 and prints the message ls: cannot access <dir>/*/: No such file or directory. The 2>&1 captures stderr and pipes it into stdout and then the whole lot gets piped to null (which gets rid of the regular ls output too, when there is files).
I'm not really sure what you trying to do here, but you can use find:
find /path/to/root/directory -type d
If you want to script it:
find $DIR/* -type d
should do the trick.
Try this as the condition you test against:
subdirs=$(ls -d $DIR/.*/ | grep -v "/./\|/../")
subdirs will be empty if there are no subdirectories
A solution in pure bash, without needing any other program execution. This is not the most compact solution, but if run in a loop, it could be more efficient because no process creation is needed. If there is a lot of files in '$dir', the filename expansion could break though.
shopt -s dotglob # To include directories beginning by '.' in file expansion.
nbdir=0
for f in $dir/*
do
if [ -d $f ]
then
nbdir=$((nbdir+1))
fi
done
if [ nbdir -gt 0 ]
then
echo "Subdirs"
else
echo "No-Subdirs"
fi
in my case it doesn't work as #AIG wrote - for empty dir I got subdircount=1 (find returns only dir itself).
what works for me:
#!/usr/bin/bash
subdircount=`find /d/temp/ -maxdepth 1 -type d | wc -l`
if [ $subdircount -ge 2 ]
then
echo "not empty"
else
echo "empty"
fi
I'd like to know if a directory has any subdirectories. For this purpose I don't need recursion, and I don't care what they are. So:
(find -L . -mindepth 1 -maxdepth 1 -type d | wc -l)
How about:
num_child=`ls -al $DIR | grep -c -v ^d`
If $num_child > 2, then you have child directories. If you do not want hidden directories, replace ls -al with ls -l.
if [ $num_child -gt 2 ]
then
echo "$num_child child directories!"
fi
Here is the best answer you will ever see on this issue.
function hasDirs ()
{
declare $targetDir="$1" # Where targetDir ends in a forward slash /
ls -ld ${targetDir}*/
}
If you are already in the target directory:
if hasDirs ./
then
fi
If you want to know about another directory:
if hasDirs /var/local/ # Notice the ending slash
then
fi

How to test filename expansion result in bash?

I want to check whether a directory has files or not in bash.
My code is here.
for d in {,/usr/local}/etc/bash_completion.d ~/.bash/completion.d
do
[ -d "$d" ] && [ -n "${d}/*" ] &&
for f in $d/*; do
[ -f "$f" ] && echo "$f" && . "$f"
done
done
The problem is that "~/.bash/completion.d" has no file.
So, $d/* is regarded as simple string "~/.bash/completion.d/*", not empty string which is result of filename expansion.
As a result of that code, bash tries to run
. "~/.bash/completion.d/*"
and of course, it generates error message.
Can anybody help me?
If you set the nullglob bash option, through
shopt -s nullglob
then globbing will drop patterns that don't match any file.
# NOTE: using only bash builtins
# Assuming $d contains directory path
shopt -s nullglob
# Assign matching files to array
files=( "$d"/* )
if [ ${#files[#]} -eq 0 ]; then
echo 'No files found.'
else
# Whatever
fi
Assignment to an array has other benefits, including desirable (correct!) handling of filenames/paths containing white-space, and simple iteration without using a sub-shell, as the following code does:
find "$d" -type f |
while read; do
# Process $REPLY
done
Instead, you can use:
for file in "${files[#]}"; do
# Process $file
done
with the benefit that the loop is run by the main shell, meaning that side-effects (such as variable assignment, say) made within the loop are visible for the remainder of script. Of course, it's also way faster, if performance is an issue.
Finally, an array can also be inserted in command line arguments (without splitting arguments containing white-space):
$ md5sum fileA "${files[#]}" fileZ
You should always attempt to correctly handle files/paths containing white-space, because one day, they will happen!
You could use find directly in the following way:
for f in $(find {,/usr/local}/etc/bash_completion.d ~/.bash/completion.d -maxdepth 1 -type f);
do echo $f; . $f;
done
But find will print a warning if some of the directory isn't found, you can either put a 2> /dev/null or put the find call after testing if the directories exist (like in your code).
find() {
for files in "$1"/*;do
if [ -d "$files" ];then
numfile=$(ls $files|wc -l)
if [ "$numfile" -eq 0 ];then
echo "dir: $files has no files"
continue
fi
recurse "$files"
elif [ -f "$files" ];then
echo "file: $files";
:
fi
done
}
find /path
Another approach
# prelim stuff to set up d
files=`/bin/ls $d`
if [ ${#files} -eq 0 ]
then
echo "No files were found"
else
# do processing
fi

Resources