How to Check if a File or Directory Exists in Bash

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:
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:
FILE=/etc/resolv.conf
if [ -e "$FILE" ]; then
echo "$FILE exists."
fiThe 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:
FILE=/etc/resolv.conf
if test -f "$FILE"; then
echo "$FILE exists."
fiFILE=/etc/resolv.conf
if [ -f "$FILE" ]; then
echo "$FILE exists."
fiFILE=/etc/resolv.conf
if [[ -f "$FILE" ]]; then
echo "$FILE exists."
fiIf you want to perform a different action based on whether the file exists or not, use the if/then construct:
FILE=/etc/resolv.conf
if [ -f "$FILE" ]; then
echo "$FILE exists."
else
echo "$FILE does not exist."
fitest 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.
FILE=/etc/resolv.conf
test -f "$FILE" && echo "$FILE exists."[ -f "$FILE" ] && echo "$FILE exists."[[ -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 &&:
[ -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:
DIR=/etc/docker
if [ -d "$DIR" ]; then
echo "$DIR is a directory."
fi[ -d "$DIR" ] && echo "$DIR is a directory."To run different commands depending on whether the directory exists, use an if/else block:
DIR=/etc/docker
if [ -d "$DIR" ]; then
echo "$DIR is a directory."
else
echo "$DIR does not exist or is not a directory."
fiTo check whether a directory does not exist, negate the expression with !:
DIR=/etc/docker
if [ ! -d "$DIR" ]; then
echo "$DIR does not exist or is not a directory."
fiNote 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:
mkdir -p /var/backups/mysqlReach 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:
DIR=/var/backups/mysql
if [ -d "$DIR" ] && [ -r "$DIR" ] && [ -z "$(ls -A "$DIR")" ]; then
echo "$DIR exists and is empty."
fiThe -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:
FILE=/etc/resolv.conf
if [ ! -f "$FILE" ]; then
echo "$FILE does not exist."
fiThe same check as a one-liner:
[ ! -f "$FILE" ] && echo "$FILE does not exist."Check if File Is Readable, Writable, or Executable
To test permissions, use -r, -w, and -x:
FILE=/usr/local/bin/script.sh
if [ -r "$FILE" ]; then
echo "$FILE is readable."
fiif [ -w "$FILE" ]; then
echo "$FILE is writable."
fiif [ -x "$FILE" ]; then
echo "$FILE is executable."
fiCheck 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:
FILE=/var/log/syslog
if [ -f "$FILE" ] && [ -s "$FILE" ]; then
echo "$FILE is not empty."
fiCheck if a File Is a Symlink
Use -L to check for a symbolic link:
FILE=/etc/localtime
if [ -L "$FILE" ]; then
echo "$FILE is a symbolic link."
fiCheck if Multiple Files Exist
To test whether multiple files exist, combine separate checks with &&:
if [ -f /etc/resolv.conf ] && [ -f /etc/hosts ]; then
echo "Both files exist."
fiif [[ -f /etc/resolv.conf && -f /etc/hosts ]]; then
echo "Both files exist."
fiEquivalent variants without using the IF statement:
[ -f /etc/resolv.conf ] && [ -f /etc/hosts ] && echo "Both files exist."[[ -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:
-bFILE- True if the FILE exists and is a special block file.-cFILE- True if the FILE exists and is a special character file.-dFILE- True if the FILE exists and is a directory.-eFILE- True if the FILE exists, regardless of type.-fFILE- True if the FILE exists and is a regular file (not a directory or device).-GFILE- True if the FILE exists and is owned by the effective group ID.-hFILE- True if the FILE exists and is a symbolic link.-gFILE- True if the FILE exists and has set-group-id (sgid) flag set.-kFILE- True if the FILE exists and has a sticky bit flag set.-LFILE- True if the FILE exists and is a symbolic link.-OFILE- True if the FILE exists and is owned by the effective user ID.-pFILE- True if the FILE exists and is a named pipe (FIFO).-rFILE- True if the FILE exists and is readable.-SFILE- True if the FILE exists and is a socket.-sFILE- True if the FILE exists and has nonzero size.-uFILE- True if the FILE exists and set-user-id (suid) flag is set.-wFILE- True if the FILE exists and is writable.-xFILE- 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 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