sh: Test for existence of files - bash

How does one test for the existence of files in a directory using bash?
if ... ; then
echo 'Found some!'
fi
To be clear, I don't want to test for the existence of a specific file. I would like to test if a specific directory contains any files.
I went with:
(
shopt -s dotglob nullglob
existing_files=( ./* )
if [[ ${#existing_files[#]} -gt 0 ]] ; then
some_command "${existing_files[#]}"
fi
)
Using the array avoids race conditions from reading the file list twice.

From the man page:
-f file
True if file exists and is a regular file.
So:
if [ -f someFileName ]; then echo 'Found some!'; fi
Edit: I see you already got the answer, but for completeness, you can use the info in Checking from shell script if a directory contains files - and lose the dotglob option if you want hidden files ignored.

I typically just use a cheap ls -A to see if there's a response.
Pseudo-maybe-correct-syntax-example-ahoy:
if [[ $(ls -A my_directory_path_variable ) ]] then....
edit, this will work:
myDir=(./*) if [ ${#myDir[#]} -gt 1 ]; then echo "there's something down here"; fi

You can use ls in an if statement thus:
if [[ "$(ls -a1 | egrep -v '^\.$|^\.\.$')" = "" ]] ; then echo empty ; fi
or, thanks to ikegami,
if [[ "$(ls -A)" = "" ]] ; then echo empty ; fi
or, even shorter:
if [[ -z "$(ls -A)" ]] ; then echo empty ; fi
These basically list all files in the current directory (including hidden ones) that are neither . nor ...
If that list is empty, then the directory is empty.
If you want to discount hidden files, you can simplify it to:
if [[ "$(ls)" = "" ]] ; then echo empty ; fi
A bash-only solution (no invoking external programs like ls or egrep) can be done as follows:
emp=Y; for i in *; do if [[ $i != "*" ]]; then emp=N; break; fi; done; echo $emp
It's not the prettiest code in the world, it simply sets emp to Y and then, for every real file, sets it to N and breaks from the for loop for efficiency. If there were zero files, it stays as Y.

Try this
if [ -f /tmp/foo.txt ]
then
echo the file exists
fi
ref: http://tldp.org/LDP/abs/html/fto.html
you may also want to check this out: http://tldp.org/LDP/abs/html/fto.html
How about this for whether directory is empty or not
$ find "/tmp" -type f -exec echo Found file {} \;

#!/bin/bash
if [ -e $1 ]; then
echo "File exists"
else
echo "Files does not exist"
fi

I don't have a good pure sh/bash solution, but it's easy to do in Perl:
#!/usr/bin/perl
use strict;
use warnings;
die "Usage: $0 dir\n" if scalar #ARGV != 1 or not -d $ARGV[0];
opendir my $DIR, $ARGV[0] or die "$ARGV[0]: $!\n";
my #files = readdir $DIR;
closedir $DIR;
if (scalar #files == 2) { # . and ..
exit 0;
}
else {
exit 1;
}
Call it something like emptydir and put it somewhere in your $PATH, then:
if emptydir dir ; then
echo "dir is empty"
else
echo "dir is not empty"
fi
It dies with an error message if you give it no arguments, two or more arguments, or an argument that isn't a directory; it's easy enough to change if you prefer different behavior.

# tested on Linux BASH
directory=$1
if test $(stat -c %h $directory) -gt 2;
then
echo "not empty"
else
echo "empty"
fi

For fun:
if ( shopt -s nullglob ; perl -e'exit !#ARGV' ./* ) ; then
echo 'Found some!'
fi
(Doesn't check for hidden files)

Related

Bash script loop through subdirectories and write to file without using find,ls etc

Sorry for asking this question again. I have already received answer but with using find but unfortunately I need to write it without using any predefined commands.
I am trying to write a script that will loop recursively through the subdirectories in the current directory. It should check the file count in each directory. If file count is greater than 10 it should write all names of these file in file named "BigList" otherwise it should write in file "ShortList". This should look like:
---<directory name>
<filename>
<filename>
<filename>
<filename>
....
---<directory name>
<filename>
<filename>
<filename>
<filename>
....
My script only works if subdirectories don't include subdirectories in turn.
I am confused about this because it doesn't work as I expect.
Here is my script
#!/bin/bash
parent_dir=""
if [ -d "$1" ]; then
path=$1;
else
path=$(pwd)
fi
parent_dir=$path
loop_folder_recurse() {
local files_list=""
local cnt=0
for i in "$1"/*;do
if [ -d "$i" ];then
echo "dir: $i"
parent_dir=$i
echo before recursion
loop_folder_recurse "$i"
echo after recursion
if [ $cnt -ge 10 ]; then
echo -e "---"$parent_dir >> BigList
echo -e $file_list >> BigList
else
echo -e "---"$parent_dir >> ShortList
echo -e $file_list >> ShortList
fi
elif [ -f "$i" ]; then
echo file $i
if [ $cur_fol != $main_pwd ]; then
file_list+=$i'\n'
cnt=$((cnt + 1))
fi
fi
done
}
echo "Base path: $path"
loop_folder_recurse $path
How can I modify my script to produce the desired output?
This bash script produces the output that you want:
#!/bin/bash
bigfile="$PWD/BigList"
shortfile="$PWD/ShortList"
shopt -s nullglob
loop_folder_recurse() {
(
[[ -n "$1" ]] && cd "$1"
for i in */; do
[[ -d "$i" ]] && loop_folder_recurse "$i"
count=0
files=''
for j in *; do
if [[ -f "$j" ]]; then
files+="$j"$'\n'
((++count))
fi
done
if ((count > 10)); then
outfile="$bigfile"
else
outfile="$shortfile"
fi
echo "$i" >> "$outfile"
echo "$files" >> "$outfile"
done
)
}
loop_folder_recurse
Explanation
shopt -s nullglob is used so that when a directory is empty, the loop will not run. The body of the function is within ( ) so that it runs within a subshell. This is for convenience, as it means that the function returns to the previous directory when the subshell exits.
Hopefully the rest of the script is fairly self-explanatory but if not, please let me know and I will be happy to provide additional explanation.

How to test if multiple files exist using a Bash script

How can I use the test command for an arbitrary number of files, passed in using an argument with a wildcard?
For example:
test -f /var/log/apache2/access.log.* && echo "exists one or more files"
Currently, it prints
error: bash: test: too many arguments
This solution seems to me more intuitive:
if [ `ls -1 /var/log/apache2/access.log.* 2>/dev/null | wc -l ` -gt 0 ];
then
echo "ok"
else
echo "ko"
fi
To avoid "too many arguments error", you need xargs. Unfortunately, test -f doesn't support multiple files. The following one-liner should work:
for i in /var/log/apache2/access.log.*; do test -f "$i" && echo "exists one or more files" && break; done
By the way, /var/log/apache2/access.log.* is called shell-globbing, not regexp. Please see Confusion with shell-globbing wildcards and Regex for more information.
First, store files in the directory as an array:
logfiles=(/var/log/apache2/access.log.*)
Then perform a test on the count of the array:
if [[ ${#logfiles[#]} -gt 0 ]]; then
echo 'At least one file found'
fi
This one is suitable for use with the Unofficial Bash Strict Mode, no has non-zero exit status when no files are found.
The array logfiles=(/var/log/apache2/access.log.*) will always contain at least the unexpanded glob, so one can simply test for existence of the first element:
logfiles=(/var/log/apache2/access.log.*)
if [[ -f ${logfiles[0]} ]]
then
echo 'At least one file found'
else
echo 'No file found'
fi
If you wanted a list of files to process as a batch, as opposed to doing a separate action for each file, you could use find, store the results in a variable, and then check if the variable was not empty. For example, I use the following to compile all the .java files in a source directory.
SRC=`find src -name "*.java"`
if [ ! -z $SRC ]; then
javac -classpath $CLASSPATH -d obj $SRC
# stop if compilation fails
if [ $? != 0 ]; then exit; fi
fi
You just need to test if ls has something to list:
ls /var/log/apache2/access.log.* >/dev/null 2>&1 && echo "exists one or more files"
Variation on a theme:
if ls /var/log/apache2/access.log.* >/dev/null 2>&1
then
echo 'At least one file found'
else
echo 'No file found'
fi
ls -1 /var/log/apache2/access.log.* | grep . && echo "One or more files exist."
Or using find
if [ $(find /var/log/apache2/ -type f -name "access.log.*" | wc -l) -gt 0 ]; then
echo "ok"
else
echo "ko"
fi
This condition below doesn't produce stderr. the condition's blackhole (/dev/null) doesn't prevent the stderr in cmd.
if [[ $(ls -1 /var/log/apache2/access.log.* | wc -l ) -gt 0 ]] 2> /dev/null
therefore I suggests this code.
if [[ $(ls -1 /var/log/apache2/access.log.* | wc -l ) -gt 0 ]] 2> /dev/null
then
echo "exists one or more files."
fi
more simplyfied:
if ls /var/log/apache2/access.log.* 2>/dev/null 1>&2; then
echo "ok"
else
echo "ko"
fi

Using find in bash script - how to handle case where find returns "file or directory does not exist"

I'm using the find command in my bash script like so
for x in `find ${1} .....`;
do
...
done
However, how do I handle the case where the input to my script is a file/directory that does not exist? (ie I want to print a message out when that happens)
I've tried to use -d and -f, but the case I am having trouble with is when ${1} is "." or ".."
When the input is something that doesn't exist it does not enter my for loop.
Thanks!
Bash gives you this out of the box:
if [ ! -f ${1} ];
then
echo "File/Directory does not exist!"
else
# execute your find...
fi
Bash scripting is a bit weird. Practice before implementation. But this site seems to break it down well.
If the file exists, this works:
if [ -e "${1}" ]
then
echo "${1} file exists."
fi
If the file does not exist, this works. Note the '!' to denote 'not':
if [ ! -e "${1}" ]
then
echo "${1} file doesn't exist."
fi
assign the find to a variable and test against the variable.
files=`find ${1} .....`
if [[ "$files" != “file or directory does not exist” ]]; then
...
fi
You can try something like this:
y=`find . -name "${1}"`
if [ "$y" != "" ]; then
for x in $y; do
echo "found $x"
done
else
echo "No files/directories found!"
fi

bash scripting: How to test list membership

(This is debian squeeze amd64)
I need to test if a file is a member of a list of files.
So long my (test) script is:
set -x
array=$( ls )
echo $array
FILE=log.out
# This line gives error!
if $FILE in $array
then echo "success!"
else echo "bad!"
fi
exit 0
¿Any ideas?
Thanks for all the responses. To clarify: The script given is only an example, the actual problem is more complex. In the final solution, it will be done within a loop, so I need the file(name) to be tested for to be in a variable.
Thanks again. No my test-script works, and reads:
in_list() {
local search="$1"
shift
local list=("$#")
for file in "${list[#]}" ; do
[[ "$file" == "$search" ]] && return 0
done
return 1
}
#
# set -x
array=( * ) # Array of files in current dir
# echo $array
FILE="log.out"
if in_list "$FILE" "${array[#]}"
then echo "success!"
else echo "bad!"
fi
exit 0
if ls | grep -q -x t1 ; then
echo Success
else
echo Failure
fi
grep -x matches full lines only, so ls | grep -x only returns something if the file exists.
If you just want to check if a file exists, then
[[ -f "$file" ]] && echo yes || echo no
If your array contains a list of files generated by some means other than ls, then you have to iterate over it as demonstrated by Sorpigal.
How about
in_list() {
local search="$1"
shift
local list=("$#")
for file in "${list[#]}" ; do
[[ $file == $search ]] && return 0
done
return 1
}
if in_list log.out * ; then
echo 'success!'
else
echo 'bad!'
fi
EDIT: made it a bit less idiotic.
EDIT #2:
Of course if all you're doing is looking in the current directory to see if a particular file is there, which is effectively what the above is doing, then you can just say
[ -e log.out ] && echo 'success!' || echo 'bad!'
If you're actually doing something more complicated involving lists of files then this might not be sufficient.

Shell programming, looping through files

I am trying to loop through files in a specified directory. But I can't seem to figure out the logic. I am looping through each file and asking if they want to delete that file.
#!/bin/bash
dirpath=$1
y=y
Y=Y
echo "changing directory '$dirpath' `cd $dirpath`"
for f in $1/*
do
#####################################
if test -f `ls -1 $1`
then
echo -n "remove file '$f' `ls -1` ?"
read answer
##########################
if test $answer = $y || test $answer = $Y
then
echo "Processing $f file..."
echo `rm $f`
echo "file '$f' deleted "
else
echo "file '$f' not removed"
fi#2nd if loop
############################
else
echo 'not a file'
fi#1st if loop
#######################################
done
Your code seems much more complicated that it should be. Does this fulfill your needs or are you doing some shell practice?
rm -iv DIRECTORY/*
There's no need for ls, you already have the filename. Change this:
if test -f `ls -1 $1`
to:
if test -f "$f"
Why are you using echo and backticks here? Change
echo `rm $f`
to:
rm "$f"
Here's another place you're using backticks unnecessarily. Change this:
echo "changing directory '$dirpath' `cd $dirpath`"
to:
echo "changing directory '$dirpath'"
cd "$dirpath"
Always quote variables that contain filenames.
You can have rm do the "asking" for you via its -i flag to prompt user before removal. I am assuming you want to consider only files, not directories, and not recurse any sub-directories.
#!/bin/bash
for f in $1/* ; do
if [ -f $f ] ; then
rm -i $f ;
fi
done
Without the error, can't really help, but it could be written like this, not as verbose though
rm -i *
If $1 is a relative path, then once you've cd'd into $1, the wildcard in your for loop will be meaningless. I'd recommend something more like
cd $1
for f in *; do
...
done
Since it will accept both relative and absolute paths.
Moreover, the arguments to the first test are wrong. Each time through the loop, $f will hold one filename, so your test should be like
if (test -f $f); then
You also repeat this in your echo arguments.
The following does basically what you want, with only slight modifications from your script.
#!/bin/bash
dirpath=$1
y=y
Y=Y
echo "changing directory '$dirpath' `cd $dirpath`"
for f in ./*; do
if (test -f $f); then
echo -n "remove file '$f' ?"
read answer
if (test $answer == $y) || (test $answer == $Y); then
echo "Processing $f file..."
rm $f
echo "file '$f' deleted "
else
echo "file '$f' not removed"
fi
else
echo 'not a file'
fi
done

Resources