How to Check if a File or Directory Exists in Bash

By 

Updated on

7 min read

Bash test operators checking a file and directory

When writing shell scripts, you may find yourself in a situation where you need to perform an action based on whether a file exists or not.

In Bash, you can use the test command to check whether a file exists and determine the type of the file.

The test command takes one of the following syntax forms:

txt
test EXPRESSION
[ EXPRESSION ]
[[ EXPRESSION ]]

If you want your script to be portable, prefer the [ form, which is available in POSIX shells. The [[ (double bracket) form is supported by Bash, Zsh, and Ksh, but it is not part of POSIX sh.

Check if File Exists

When checking if a file exists, the most commonly used FILE operators are -e and -f. The first one will check whether a file exists regardless of the type, while the second one will return true only if the FILE is a regular file (not a directory or a device).

To check whether a path exists regardless of its type (file, directory, socket, and so on), use -e:

sh
FILE=/etc/resolv.conf
if [ -e "$FILE" ]; then
    echo "$FILE exists."
fi

The most readable option when checking whether a file exists or not is to use the test command in combination with the if statement . Any of the snippets below will check whether the /etc/resolv.conf file exists:

sh
FILE=/etc/resolv.conf
if test -f "$FILE"; then
    echo "$FILE exists."
fi
sh
FILE=/etc/resolv.conf
if [ -f "$FILE" ]; then
    echo "$FILE exists."
fi
sh
FILE=/etc/resolv.conf
if [[ -f "$FILE" ]]; then
    echo "$FILE exists."
fi

If you want to perform a different action based on whether the file exists or not, use the if/then construct:

sh
FILE=/etc/resolv.conf
if [ -f "$FILE" ]; then
    echo "$FILE exists."
else
    echo "$FILE does not exist."
fi
Info
Quote variable expansions in test and [ ... ] so paths containing whitespace remain a single argument. Quoting is optional inside [[ ... ]], but using it keeps the examples consistent.

You can also use the test command without the if statement. The command after the && operator will only be executed if the exit status of the test command is true.

sh
FILE=/etc/resolv.conf
test -f "$FILE" && echo "$FILE exists."
sh
[ -f "$FILE" ] && echo "$FILE exists."
sh
[[ -f "$FILE" ]] && echo "$FILE exists."

If you want to run a series of commands after the && operator, enclose the commands in curly brackets separated by ; or &&:

sh
[ -f "$FILE" ] && { echo "$FILE exists."; cp "$FILE" /tmp/; }

Check if Directory Exists

The -d operator tests whether a path exists and is a directory.

For example, to check whether the /etc/docker directory exists, you would use:

sh
DIR=/etc/docker
if [ -d "$DIR" ]; then
    echo "$DIR is a directory."
fi
sh
[ -d "$DIR" ] && echo "$DIR is a directory."

To run different commands depending on whether the directory exists, use an if/else block:

sh
DIR=/etc/docker
if [ -d "$DIR" ]; then
    echo "$DIR is a directory."
else
    echo "$DIR does not exist or is not a directory."
fi

To check whether a directory does not exist, negate the expression with !:

sh
DIR=/etc/docker
if [ ! -d "$DIR" ]; then
    echo "$DIR does not exist or is not a directory."
fi

Note that -e is not a substitute for -d. It returns true for a regular file as well, so it confirms that the path exists without telling you what kind of path it is. Use -d whenever the script needs a directory specifically.

A common reason to run this check is making sure a directory is in place before a script writes into it. In that case you often do not need the test at all. The -p flag of the mkdir command creates any missing parent directories and exits quietly when the directory is already there:

sh
mkdir -p /var/backups/mysql

Reach for an explicit -d test when a missing directory should stop the script rather than be created, such as when a required configuration directory should have been provisioned during deployment.

To test whether a directory exists and is empty, combine -d with a check on its contents:

sh
DIR=/var/backups/mysql
if [ -d "$DIR" ] && [ -r "$DIR" ] && [ -z "$(ls -A "$DIR")" ]; then
    echo "$DIR exists and is empty."
fi

The -A option makes ls list hidden entries while leaving out . and .., so a directory that holds only dotfiles is correctly reported as not empty. The -d and -r tests run first, so ls is skipped when the path is missing, is not a directory, or cannot be read. Without the -r check, a failed listing can produce no standard output and make an inaccessible directory look empty. The -s operator is not a directory-content test: it checks the directory entry’s own byte size, not whether the directory contains names.

Check if File Does Not Exist

Like many other languages, the test expression can be negated using the ! (exclamation mark) logical not operator:

sh
FILE=/etc/resolv.conf
if [ ! -f "$FILE" ]; then
    echo "$FILE does not exist."
fi

The same check as a one-liner:

sh
[ ! -f "$FILE" ] && echo "$FILE does not exist."

Check if File Is Readable, Writable, or Executable

To test permissions, use -r, -w, and -x:

sh
FILE=/usr/local/bin/script.sh

if [ -r "$FILE" ]; then
    echo "$FILE is readable."
fi
sh
if [ -w "$FILE" ]; then
    echo "$FILE is writable."
fi
sh
if [ -x "$FILE" ]; then
    echo "$FILE is executable."
fi

Check if File Is Not Empty

The -s operator checks whether a path exists and has a size greater than zero. To require a regular file as well, combine -f and -s:

sh
FILE=/var/log/syslog

if [ -f "$FILE" ] && [ -s "$FILE" ]; then
    echo "$FILE is not empty."
fi

Use -L to check for a symbolic link:

sh
FILE=/etc/localtime

if [ -L "$FILE" ]; then
    echo "$FILE is a symbolic link."
fi

Check if Multiple Files Exist

To test whether multiple files exist, combine separate checks with &&:

sh
if [ -f /etc/resolv.conf ] && [ -f /etc/hosts ]; then
    echo "Both files exist."
fi
sh
if [[ -f /etc/resolv.conf && -f /etc/hosts ]]; then
    echo "Both files exist."
fi

Equivalent variants without using the IF statement:

sh
[ -f /etc/resolv.conf ] && [ -f /etc/hosts ] && echo "Both files exist."
sh
[[ -f /etc/resolv.conf && -f /etc/hosts ]] && echo "Both files exist."

File Test Operators

The test command includes the following FILE operators that allow you to test for particular types of files:

  • -b FILE - True if the FILE exists and is a special block file.
  • -c FILE - True if the FILE exists and is a special character file.
  • -d FILE - True if the FILE exists and is a directory.
  • -e FILE - True if the FILE exists, regardless of type.
  • -f FILE - True if the FILE exists and is a regular file (not a directory or device).
  • -G FILE - True if the FILE exists and is owned by the effective group ID.
  • -h FILE - True if the FILE exists and is a symbolic link.
  • -g FILE - True if the FILE exists and has set-group-id (sgid) flag set.
  • -k FILE - True if the FILE exists and has a sticky bit flag set.
  • -L FILE - True if the FILE exists and is a symbolic link.
  • -O FILE - True if the FILE exists and is owned by the effective user ID.
  • -p FILE - True if the FILE exists and is a named pipe (FIFO).
  • -r FILE - True if the FILE exists and is readable.
  • -S FILE - True if the FILE exists and is a socket.
  • -s FILE - True if the FILE exists and has nonzero size.
  • -u FILE - True if the FILE exists and set-user-id (suid) flag is set.
  • -w FILE - True if the FILE exists and is writable.
  • -x FILE - True if the FILE exists and is executable.

Conclusion

For scripts that run across different shells, the single-bracket [ form is the portable choice. Double brackets [[ add useful extras like pattern matching and regex but work only in Bash, Ksh, and Zsh. For the full operator reference, run man test or help test (for the built-in version) in your terminal.

Tags

Linuxize Weekly Newsletter

A quick weekly roundup of new tutorials, news, and tips.

About the authors

Dejan Panovski

Dejan Panovski

Dejan Panovski is the founder of Linuxize, an RHCSA-certified Linux system administrator and DevOps engineer based in Skopje, Macedonia. Author of 800+ Linux tutorials with 20+ years of experience turning complex Linux tasks into clear, reliable guides.

View author page