2

Hi I'm trying to do an if which gets a path to a directory as an argument and checks if file.txt is in the directory.And returns 1 if it is , 0 otherwise.

     if [ -e $1/file.txt ]; then
         exit 1
     else exit 0
     fi

Also i've tried ls method

     ls $1/file.txt && exit 1 || exit 0

Is there any other method? Or i am missing something?

Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255

2 Answers2

2

To check if a file file.txt is present in the directory passed as the first argument to the script or function, use

[ -e "$1/file.txt" ]

Don't forget to double-quote variable substitutions.

This succeeds if the file exists, and fails if the file doesn't exist. Failure includes the case where the file exists but can't be reached, e.g. because you don't have the permission to traverse the directory.

Note that in shell scripts, and when it comes to process exit statuses, 0 means success and 1 (or more, up to 125) means failure. See What return/exit values can I use in bash functions/scripts? and Default exit code when process is terminated? for more details. So if you want to check whether a file exists, your script or function must return 0 if the file exists and 1 otherwise. Your snippet does the opposite: it checks if the file doesn't exist.

 if [ -e "$1/file.txt" ]; then
    exit 0
 else
    exit 1
 fi

is just a more complicated way of writing

[ -e "$1/file.txt" ]
exit

(exit with no status argument uses the status of the previously executed command). And if this is at the end of the script then exit is redundant.

If you did want to check that the file doesn't exist then you would invert the command with the ! shell operator:

! [ -e "$1/file.txt" ]

or the ! test/[ operator:

[ ! -e "$1/file.txt" ]
1
[ -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).