Often when writing for the bash shell, one needs to test if a file (or Directory) exists (or doesn't exist) and take appropriate action. Most common amongst these test are...
-e
- file exists, -f
- file is a regular file (not a directory or device file), -s
- file is not zero size, -d
- file is a directory, -r
- file has read permission, -w
- file has write, or -x
execute permission (for the user running the test)
This is easily confirmed as demonstrated on this user-writable directory....
#/bin/bash
if [ -f "/Library/Application Support" ]; then
echo 'YES SIR -f is fine'
else echo 'no -f for you'
fi
if [ -w "/Library/Application Support" ]; then
echo 'YES SIR -w is fine'
else echo 'no -w for you'
fi
if [ -d "/Library/Application Support" ]; then
echo 'YES SIR -d is fine'
else echo 'no -d for you'
fi
➝ no -f for you ✓
➝ YES SIR -w is fine ✓
➝ YES SIR -d is fine ✓
My question, although seemingly obvious, and unlikely to be impossible - is how to simply combine these tests, without having to perform them separately for each condition... Unfortunately...
if [ -wd "/Library/Application Support" ]
▶ -wd: unary operator expected
if [ -w | -d "/Library/Application Support" ]
▶ [: missing `]'
▶ -d: command not found
if [ -w [ -d "/Library.... ]] & if [ -w && -d "/Library.... ]
▶ [: missing `]'
➝ no -wd for you ✖
➝ no -w | -d for you ✖
➝ no [ -w [ -d .. ]] for you ✖
➝ no -w && -d for you ✖
What am I missing here?
In order to check if multiple files exist in Bash, use the “-f” flag and specify the files to be checked separated by the “&&” operator.
bash [filename] runs the commands saved in a file. $@ refers to all of a shell script's command-line arguments. $1 , $2 , etc., refer to the first command-line argument, the second command-line argument, etc. Place variables in quotes if the values might have spaces in them.
In bash shell, the test command compares one element against another and returns true or false. In bash scripting, the test command is an integral part of the conditional statements that control logic and program flow.
You can use logical operators to multiple conditions, e.g. -a
for AND
:
MYFILE=/tmp/data.bin
if [ -f "$MYFILE" -a -r "$MYFILE" -a -w "$MYFILE" ]; then
#do stuff
fi
unset MYFILE
Of course, you need to use AND somehow as Kerrek(+1) and Ben(+1) pointed it out. You can do in in few different ways. Here is an ala-microbenchmark results for few methods:
Most portable and readable way:
$ time for i in $(seq 100000); do [ 1 = 1 ] && [ 2 = 2 ] && [ 3 = 3 ]; done
real 0m2.583s
still portable, less readable, faster:
$ time for i in $(seq 100000); do [ 1 = 1 -a 2 = 2 -a 3 = 3 ]; done
real 0m1.681s
bashism, but readable and faster
$ time for i in $(seq 100000); do [[ 1 = 1 ]] && [[ 2 = 2 ]] && [[ 3 = 3 ]]; done
real 0m1.285s
bashism, but quite readable, and fastest.
$ time for i in $(seq 100000); do [[ 1 = 1 && 2 = 2 && 3 = 3 ]]; done
real 0m0.934s
Note, that in bash, "[" is a builtin, so bash is using internal command not a symlink to /usr/bin/test exacutable. The "[[" is a bash keyword. So the slowest possible way will be:
time for i in $(seq 100000); do /usr/bin/\[ 1 = 1 ] && /usr/bin/\[ 2 = 2 ] && /usr/bin/\[ 3 = 3 ]; done
real 14m8.678s
You want -a
as in -f foo -a -d foo
(actually that test would be false, but you get the idea).
You were close with &
you just needed &&
as in [ -f foo ] && [ -d foo ]
although that runs multiple commands rather than one.
Here is a manual page for test which is the command that [
is a link to. Modern implementations of test
have a lot more features (along with the shell-builtin version [[
which is documented in your shell's manpage).
check-file(){
while [[ ${#} -gt 0 ]]; do
case $1 in
fxrsw) [[ -f "$2" && -x "$2" && -r "$2" && -s "$2" && -w "$2" ]] || return 1 ;;
fxrs) [[ -f "$2" && -x "$2" && -r "$2" && -s "$2" ]] || return 1 ;;
fxr) [[ -f "$2" && -x "$2" && -r "$2" ]] || return 1 ;;
fr) [[ -f "$2" && -r "$2" ]] || return 1 ;;
fx) [[ -f "$2" && -x "$2" ]] || return 1 ;;
fe) [[ -f "$2" && -e "$2" ]] || return 1 ;;
hf) [[ -h "$2" && -f "$2" ]] || return 1 ;;
*) [[ -e "$1" ]] || return 1 ;;
esac
shift
done
}
check-file fxr "/path/file" && echo "is valid"
check-file hf "/path/folder/symlink" || { echo "Fatal error cant validate symlink"; exit 1; }
check-file fe "file.txt" || touch "file.txt" && ln -s "${HOME}/file.txt" "/docs/file.txt" && check-file hf "/docs/file.txt" || exit 1
if check-file fxrsw "${HOME}"; then
echo "Your home is your home from the looks of it."
else
echo "You infected your own home."
fi
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With