If a Linux Bash script relies on certain files or directories being present, it can’t just assume they do. It needs to check that they’re definitely present. Here’s how to do that.
Don’t Assume Anything
When you’re writing a script you can’t make assumptions about what is and isn’t present on a computer. That’s doubly true if the script is going to be distributed and run on many different computers. Sooner or later, the script will run on a computer that doesn’t meet your assumptions, and the script will fail or run unpredictably.
Everything we value or create on a computer is stored in a file of some format, and all of those files reside in a directory. Scripts can read, write, rename, delete and move files and directories—all the things you can do on the command line.
The advantage you have as a human is that you can see the contents of a directory and you know if a file exists or not—or whether the expected directory even exists. If a script fouls up when it is manipulating files, it can have serious and damaging results.
Bash provides a comprehensive set of tests that you can use to detect files and directories, and test for many of their attributes. Incorporating these into scripts is easy, but the benefits in terms of robustness and fine control are considerable.
RELATED: How to Use Double Bracket Conditional Tests in Linux
The Range of Tests
By combining the if statement with the appropriate test from a large collection of file and directory tests, we can easily determine if a file exists, if it’s executable, or writable, and much more.
- -b: Returns true if the file is a block special file.
- -c: Returns true if the file is character special.
- -d: Returns true if the “file” is a directory.
- -e: Returns true if the file exists.
- -f: Returns true if the file exists and is a regular file.
- -g: Returns true if the file has the
setgidpermission set (
- -h: Returns true if the file is a symbolic link.
- -L: Returns true if the file is a symbolic link.
- -k: Returns true if has its sticky bit set (
- -p: Returns true if the file is a named pipe.
- -r: Returns true if the file is readable.
- -s: Returns true if the files exists and isn’t empty.
- -S: Returns true if the file is a socket.
- -t: Returns true if the file descriptor is opened in a terminal.
- -u: Returns true if the file has the
setuidpermission set (
- -w: Returns true if the file is writable.
- -x: Returns true if the file is executable.
- -O: Returns true if the is owned by you.
- -G: Returns true if the is owned by your group.
- -N: Returns true if the file has been modified since it was last read.
- !: The logical NOT operator.
- &&: The logical AND operator.
- ||: The logical OR operator.
The list starts with
-b because the
-a test has been deprecated and replaced by the
RELATED: How to Use SUID, SGID, and Sticky Bits on Linux
Using the Tests in Scripts
The generic file test
if statement is a simple scripting construct. The comparison inside the double brackets ”
[[ ]] ” uses the
-f test to determine whether a regular file exists with that name.
Copy the text of this script into an editor and save it into a file called “script1.sh”, and use
chmod to make it executable.
#!/bin/bash if [[ -f $1 ]] then echo "The file $1 exists." else echo "The file $1 cannot be found." fi
You have to pass the name of the file to the script on the command line.
chmod +x script1.sh
You’ll need to do this with each script if you want to try the other examples from the article.
Let’s try the script on a straightforward text file.
The file exists and the script correctly reports that fact. If we delete the file and try again, the test should fail and the script should report that to us.
In a real-life situation, your script would need to take whatever action was appropriate. Perhaps it flags the error and stops. Maybe it creates the file and carries on. It may copy something out of a backup directory to replace the missing file. It all depends on the purpose of the script. But at least now the script is able to make the decision based on knowing if the file is present or not.
-f flag tests whether the file is present, and is a “regular” file. In other words, it isn’t something that appears to be a file but isn’t, such as a device file.
We’ll use ls to verify that the “/dev/random” file exists, and then see what the script makes of it.
ls -lh /dev/random
Because our script is testing for regular files and “/dev/random” is a device file, the test fails. Very often, to get to the bottom of whether a file exists you need to carefully choose which test you use, or you need to use several tests.
This is “script2.sh”, which tests for regular files and for character device files.
#!/bin/bash if [[ -f $1 ]] then echo "The file $1 exists." else echo "The file $1 is missing or not a regular file." fi if [[ -c $1 ]] then echo "The file $1 is a character device file." else echo "The file $1 is missing or not a special file." fi
If we run this script on the “/dev/random” device file the first test fails which we expect, and the second test succeeds. It recognizes the file as a device file.
Actually, it recognizes it as a character device file. Some device files are block device files. As it stands, our script won’t cope with those.
We can make use of the logical
OR operator and include another test in the second if statement. This time, whether the file is a character device file or a block device file, the test will return true. This is “script3.sh.”
#!/bin/bash if [[ -f $1 ]] then echo "The file $1 exists." else echo "The file $1 is missing or not a regular file." fi if [[ -c $1 || -b $1 ]] then echo "The file $1 is a character or block device file." else echo "The file $1 is missing or not a special file." fi
This script recognizes both character device and block device files.
If it is important to you to differentiate between the different types of device files, you can use nested
if statements. This is “script4.sh.”
#!/bin/bash if [[ -f $1 ]] then echo "The file $1 exists." else echo "The file $1 is missing or not a regular file." fi if [[ -c $1 ]] then echo "The file $1 is a character device file." else if [[ -b $1 ]] then echo "The file $1 is a block device file." else echo "The file $1 is missing or not a device file." fi fi
This script recognizes and categorizes both character device and block device files.
Using the logical AND operator we can test for several characteristics at once. This is “script5.sh.” It checks that a file exists and the script has read and write permissions for it.
#!/bin/bash if [[ -f $1 && -r $1 && -w $1 ]] then echo "The file $1 exists and we have read/write permissions." else echo "The file $1 is missing, not a regular file, or we can't read/write to it." fi
We’ll run the script on a file that belongs to us, and one that belongs to
To test for the existence of a directory, use the
-d test. This is “script6.sh.” It is part of a backup script. The first thing it does is check whether the directory passed on the command line exists or not. It uses the logical
! in the
if statement test.
#!/bin/bash if [[ ! -d $1 ]] then echo "Creating backup directory:" $1 mkdir $1 if [[ ! $? -eq 0 ]] then echo "Couldn't create backup directory:" $1 exit fi else echo "Backup directory exists." fi # continue with file backup echo "Backing up to: "$1
If the directory doesn’t exist it creates it. If the directory creation files, the script exits. If the creation of the directory succeeds, or the directory already exists, the script continues with its backup actions.
We’ll run the script and then check with
ls and the
-d (directory) option whether the backup directory exists.
ls -d Documents/project-backup
The backup directory was created. If we run the script again, it should report that the directory is already present.
The script finds the directory and moves on to perform the backup.
Test, Don’t Assume
Sooner or later, assumptions will lead to bad things happening. Test first, and react accordingly.
Knowledge is power. Use tests to give your scripts the knowledge they need.
RELATED: How to Let Linux Scripts Detect They’re Running in Virtual Machines