[ -e "$1/file.txt" ]
In shells/[
implementations where it is supported¹ will return true if one can do a stat()
system call on the file whose path is the expansion of $1/file.txt
.
It's equivalent to:
if ls -Ld -- "$1/file.txt" > /dev/null 2>&1
For the equivalent of:
if ls -d -- "$1/file.txt" > /dev/null 2>&1
that is whether a lstat()
can be done on the file, it would be:
if [ -e "$1/file.txt" ] || [ -L "$1/file.txt" ]
to cover the "broken" symlink case (for which -e
would return false as it tests for existence after symlink resolution).
Now, stat()
, lstat()
failing doesn't necessarily mean that the file doesn't exist, stat()
/lstat()
could fail with other error conditions than ENOENT (no entry) ENOTDIR (a path component is not a directory). It could be that you don't have search access to any of the directory component for instance.
With the ls
approach, you can remove the 2>&1
for ls
to tell you the error. With [
, you won't know unless you use zsh
where you can use $ERRNO
.
If you don't have search access to the directory, you may still have read access, so you could still read the directories content to see if there's a file by that name.
With zsh
, that can be done with ()(($#)) $1/file.txt(N)
, an anonymous function that returns true if it receives at least one argument (()(($#))
) to which we pass the list of files matching the $1/file.txt(N)
glob, (N)
being to turn on NULLGLOB
for that one glob.
A separate problem with $1/file.txt
is the case where $1
is /
, in which case that becomes //file.txt
which on some systems is not the same as /file.txt
.
So with zsh
, to check whether file.txt
exists as an entry in the $1
directory:
exists() {
local file="${1%/}/file.txt"
zmodload zsh/system
local ERRNO=0
[ -e "$file" ] || [ -L "$file" ] && return 0
case $errnos[ERRNO] in
(ENOENT|ENOTDIR) return 1;; # does not exist for sure
(*) ()(($#)) $file && return 0;;
esac
return 2 # meaning: don't know
}
With other shells, you could try:
enoent_error=$(
LC_ALL=C ls -d /surely-does-not-exist 2>&1)
)
enoent_error=${enoent_error##*:}
enotdir_error=$(
LC_ALL=C ls -d /bin/sh/foo)
)
enotdir_error=${enotdir_error##*:}
exists() (
file=${1%/}/file.txt
if error=$(LC_ALL=C ls -d -- "$file" 2>&1 > /dev/null); then
return 0 # exists for sure
else
case $error in
(:"$enoent_error"|:"$enotdir_error")
return 1;; # does not exist for sure
esac
fi
return 2 # don't know
}
That assumes ls
error messages in the C locale follow the *: fixed error message
pattern where the fixed error message
is different for each error condition but always the same for a given error and does not contain :
(on my system only ETOOMANYREFS
(Too many references: cannot splice) has a :
in the corresponding standard libc error message, but that's not an errno returned by lstat()
, and anyway cannot splice is not found in other error messages).
¹ [
aka test
didn't support -e
initially. ksh
introduced -a
first (which then doubled as both a unary and binary operator), for accessible I believe (which in a way is more correct), which was later renamed to -e
(though most implementations still support -a
as well) and that's -e
that POSIX specifies for the standard test
/[
utility. /bin/sh
on Solaris 10 and older is still a non-POSIX Bourne shell and its [
builtin still doesn't support [ -e
nor [ -a
, so you'd need to resort to ls
or call /bin/test
if you were to use that shell (though you'd rather use a POSIX shell like /usr/xpg4/bin/sh
there).
-e
is the correct switch.-f
tests whetherfile.txt
exists and is a regular file.-e
just tests if the file exists. – Gilles 'SO- stop being evil' Nov 25 '16 at 00:37