This checks if a file exists:
#!/bin/bash
FILE=$1
if [ -f $FILE ]; then
echo "File $FILE exists."
else
echo "File $FILE does not exist."
fi
How do I only check if the file does not exist?
The test command (written as [ here) has a "not" logical operator, ! (exclamation mark):
if [ ! -f /tmp/foo.txt ]; then
echo "File not found!"
fi
Bash File Testing
-b filename - Block special file
-c filename - Special character file
-d directoryname - Check for directory Existence
-e filename - Check for file existence, regardless of type (node, directory, socket, etc.)
-f filename - Check for regular file existence not a directory
-G filename - Check if file exists and is owned by effective group ID
-G filename set-group-id - True if file exists and is set-group-id
-k filename - Sticky bit
-L filename - Symbolic link
-O filename - True if file exists and is owned by the effective user id
-r filename - Check if file is a readable
-S filename - Check if file is socket
-s filename - Check if file is nonzero size
-u filename - Check if file set-user-id bit is set
-w filename - Check if file is writable
-x filename - Check if file is executable
How to use:
#!/bin/bash
file=./file
if [ -e "$file" ]; then
echo "File exists"
else
echo "File does not exist"
fi
A test expression can be negated by using the ! operator
#!/bin/bash
file=./file
if [ ! -e "$file" ]; then
echo "File does not exist"
else
echo "File exists"
fi
Negate the expression inside test (for which [ is an alias) using !:
#!/bin/bash
FILE=$1
if [ ! -f "$FILE" ]
then
echo "File $FILE does not exist"
fi
The relevant man page is man test or, equivalently, man [ -- or help test or help [ for the built-in bash command.
Alternatively (less commonly used) you can negate the result of test using:
if ! [ -f "$FILE" ]
then
echo "File $FILE does not exist"
fi
That syntax is described in "man 1 bash" in sections "Pipelines" and "Compound Commands".
[[ -f $FILE ]] || printf '%s does not exist!\n' "$FILE"
Also, it's possible that the file is a broken symbolic link, or a non-regular file, like e.g. a socket, device or fifo. For example, to add a check for broken symlinks:
if [[ ! -f $FILE ]]; then
if [[ -L $FILE ]]; then
printf '%s is a broken symlink!\n' "$FILE"
else
printf '%s does not exist!\n' "$FILE"
fi
fi
It's worth mentioning that if you need to execute a single command you can abbreviate
if [ ! -f "$file" ]; then
echo "$file"
fi
to
test -f "$file" || echo "$file"
or
[ -f "$file" ] || echo "$file"
I prefer to do the following one-liner, in POSIX shell compatible format:
$ [ -f "/$DIR/$FILE" ] || echo "$FILE NOT FOUND"
$ [ -f "/$DIR/$FILE" ] && echo "$FILE FOUND"
For a couple of commands, like I would do in a script:
$ [ -f "/$DIR/$FILE" ] || { echo "$FILE NOT FOUND" ; exit 1 ;}
Once I started doing this, I rarely use the fully typed syntax anymore!!
To test file existence, the parameter can be any one of the following:
-e: Returns true if file exists (regular file, directory, or symlink)
-f: Returns true if file exists and is a regular file
-d: Returns true if file exists and is a directory
-h: Returns true if file exists and is a symlink
All the tests below apply to regular files, directories, and symlinks:
-r: Returns true if file exists and is readable
-w: Returns true if file exists and is writable
-x: Returns true if file exists and is executable
-s: Returns true if file exists and has a size > 0
Example script:
#!/bin/bash
FILE=$1
if [ -f "$FILE" ]; then
echo "File $FILE exists"
else
echo "File $FILE does not exist"
fi
You can do this:
[[ ! -f "$FILE" ]] && echo "File doesn't exist"
or
if [[ ! -f "$FILE" ]]; then
echo "File doesn't exist"
fi
If you want to check for file and folder both, then use -e option instead of -f. -e returns true for regular files, directories, socket, character special files, block special files etc.
You should be careful about running test for an unquoted variable, because it might produce unexpected results:
$ [ -f ]
$ echo $?
0
$ [ -f "" ]
$ echo $?
1
The recommendation is usually to have the tested variable surrounded by double quotation marks:
#!/bin/sh
FILE=$1
if [ ! -f "$FILE" ]
then
echo "File $FILE does not exist."
fi
In
[ -f "$file" ]
the [ command does a stat() (not lstat()) system call on the path stored in $file and returns true if that system call succeeds and the type of the file as returned by stat() is "regular".
So if [ -f "$file" ] returns true, you can tell the file does exist and is a regular file or a symlink eventually resolving to a regular file (or at least it was at the time of the stat()).
However if it returns false (or if [ ! -f "$file" ] or ! [ -f "$file" ] return true), there are many different possibilities:
the file doesn't exist
the file exists but is not a regular file (could be a device, fifo, directory, socket...)
the file exists but you don't have search permission to the parent directory
the file exists but that path to access it is too long
the file is a symlink to a regular file, but you don't have search permission to some of the directories involved in the resolution of the symlink.
... any other reason why the stat() system call may fail.
In short, it should be:
if [ -f "$file" ]; then
printf '"%s" is a path to a regular file or symlink to regular file\n' "$file"
elif [ -e "$file" ]; then
printf '"%s" exists but is not a regular file\n' "$file"
elif [ -L "$file" ]; then
printf '"%s" exists, is a symlink but I cannot tell if it eventually resolves to an actual file, regular or not\n' "$file"
else
printf 'I cannot tell if "%s" exists, let alone whether it is a regular file or not\n' "$file"
fi
To know for sure that the file doesn't exist, we'd need the stat() system call to return with an error code of ENOENT (ENOTDIR tells us one of the path components is not a directory is another case where we can tell the file doesn't exist by that path). Unfortunately the [ command doesn't let us know that. It will return false whether the error code is ENOENT, EACCESS (permission denied), ENAMETOOLONG or anything else.
The [ -e "$file" ] test can also be done with ls -Ld -- "$file" > /dev/null. In that case, ls will tell you why the stat() failed, though the information can't easily be used programmatically:
$ file=/var/spool/cron/crontabs/root
$ if [ ! -e "$file" ]; then echo does not exist; fi
does not exist
$ if ! ls -Ld -- "$file" > /dev/null; then echo stat failed; fi
ls: cannot access '/var/spool/cron/crontabs/root': Permission denied
stat failed
At least ls tells me it's not because the file doesn't exist that it fails. It's because it can't tell whether the file exists or not. The [ command just ignored the problem.
With the zsh shell, you can query the error code with the $ERRNO special variable after the failing [ command, and decode that number using the $errnos special array in the zsh/system module:
zmodload zsh/system
ERRNO=0
if [ ! -f "$file" ]; then
err=$ERRNO
case $errnos[err] in
("") echo exists, not a regular file;;
(ENOENT|ENOTDIR)
if [ -L "$file" ]; then
echo broken link
else
echo does not exist
fi;;
(*) syserror -p "can't tell: " "$err"
esac
fi
(beware the $errnos support was broken with some versions of zsh when built with recent versions of gcc).
There are three distinct ways to do this:
Negate the exit status with bash (no other answer has said this):
if ! [ -e "$file" ]; then
echo "file does not exist"
fi
Or:
! [ -e "$file" ] && echo "file does not exist"
Negate the test inside the test command [ (that is the way most answers before have presented):
if [ ! -e "$file" ]; then
echo "file does not exist"
fi
Or:
[ ! -e "$file" ] && echo "file does not exist"
Act on the result of the test being negative (|| instead of &&):
Only:
[ -e "$file" ] || echo "file does not exist"
This looks silly (IMO), don't use it unless your code has to be portable to the Bourne shell (like the /bin/sh of Solaris 10 or earlier) that lacked the pipeline negation operator (!):
if [ -e "$file" ]; then
:
else
echo "file does not exist"
fi
envfile=.env
if [ ! -f "$envfile" ]
then
echo "$envfile does not exist"
exit 1
fi
To reverse a test, use "!".
That is equivalent to the "not" logical operator in other languages. Try this:
if [ ! -f /tmp/foo.txt ];
then
echo "File not found!"
fi
Or written in a slightly different way:
if [ ! -f /tmp/foo.txt ]
then echo "File not found!"
fi
Or you could use:
if ! [ -f /tmp/foo.txt ]
then echo "File not found!"
fi
Or, presing all together:
if ! [ -f /tmp/foo.txt ]; then echo "File not found!"; fi
Which may be written (using then "and" operator: &&) as:
[ ! -f /tmp/foo.txt ] && echo "File not found!"
Which looks shorter like this:
[ -f /tmp/foo.txt ] || echo "File not found!"
The test thing may count too. It worked for me (based on Bash Shell: Check File Exists or Not):
test -e FILENAME && echo "File exists" || echo "File doesn't exist"
This code also working .
#!/bin/bash
FILE=$1
if [ -f $FILE ]; then
echo "File '$FILE' Exists"
else
echo "The File '$FILE' Does Not Exist"
fi
The simplest way
FILE=$1
[ ! -e "${FILE}" ] && echo "does not exist" || echo "exists"
This shell script also works for finding a file in a directory:
echo "enter file"
read -r a
if [ -s /home/trainee02/"$a" ]
then
echo "yes. file is there."
else
echo "sorry. file is not there."
fi
sometimes it may be handy to use && and || operators.
Like in (if you have command "test"):
test -b $FILE && echo File not there!
or
test -b $FILE || echo File there!
If you want to use test instead of [], then you can use ! to get the negation:
if ! test "$FILE"; then
echo "does not exist"
fi
You can also group multiple commands in the one liner
[ -f "filename" ] || ( echo test1 && echo test2 && echo test3 )
or
[ -f "filename" ] || { echo test1 && echo test2 && echo test3 ;}
If filename doesn't exit, the output will be
test1
test2
test3
Note: ( ... ) runs in a subshell, { ... ;} runs in the same shell.
I´m trying to make a script which takes a single command-line argument. Then it looks on the argument and if its a directory name, it just prints that this directory exists. If its a file name, it prints out the file exists. Otherwise, it tries to create a directory with this name and tests whether it was successful and reports this on the standard output.
my code is:
while read argument; do
if [ $argument -d ]; then
echo "Directory exists"
elif [ $argument -e ]
echo "File exists"
else
mkdir $argument
if [ $argument -d]; then
echo "Directory was created"
else
echo "Error while creating the directory"
fi
fi
done
Then I run the code ./file_name.sh argument. If I run the code like this, I get an error on line 8, which is just "else". While is probably not necessary here, it was the first option how to accept an argument from the command line that came to my mind.
As you mentioned, you need single command line argument, So no need to loop
#!/bin/bash
if [[ -z "$1" ]]; then
echo "Help : You have to pass one argument"
exit 0
fi
if [[ -d "$1" ]]; then
echo "Directory exists"
elif [[ -f "$1" ]]; then
echo "File exists"
else
mkdir "$1"
if [ -d "$1" ]; then
echo "Directory was created"
else
echo "Error while creating the directory"
fi
fi
if [ -d $1 ]; then
echo "Directory exists"
elif [ -e $1 ]; then
echo "File exists"
else
mkdir $1
if [ -d $1 ]; then
echo "Directory was created"
else
echo "Error while creating the directory"
fi
fi
I made up this solution thanks to the links provided, thank you.
I have a function I wrote in bash that copies files.
It was written so it would be less painful for us to turn our batch scripts that use xcopy to bash scripts. This is because the copy commands in Linux work a little bit different.
The function does several things:
It creates a path to the target directory if it doesn't exist yet.
It uses cp to copy files
it uses cp -r to copy directories.
it uses rsync -arv --exclude-from=<FILE> to copy all the files and folders in a gives directory except the files/folders listed in FILE
The problem is, that when I try to copy files with * it gives me an error:
cp: cannot stat 'some dir with * in it': No such file or directory.
I found out that I can instead write something like that: cp "<dir>/"*".<extension>" "<targetDir>" and the command itself works. But when I try to pass that to my function, it gets 3 arguments instead of 2.
How can I use the cp command in my function while being able to pass a path with wildcard in it? meaning the argument will have double quotes in the beginning of the path and in the end of them, for example: Copy "<somePath>/*.zip" "<targetDir>"
function Copy {
echo "number of args is: $#"
LastStringInPath=$(basename "$2")
if [[ "$LastStringInPath" != *.* ]]; then
mkdir -p "$2"
else
newDir=$(dirname "$2")
mkdir -p "newDir"
fi
if [ "$#" == "2" ]; then
echo "Copying $1 to $2"
if [[ -d $1 ]]; then
cp -r "$1" "$2"
else
cp "$1" "$2"
fi
if [ $? -ne 0 ]; then
echo "Error $? while trying to copy $1 to $2"
exit 1
fi
else
rsync -arv --exclude-from="$3" "$1" "$2"
if [ $? -ne 0 ]; then
echo "Error $? while trying to copy $1 to $2"
exit 1
fi
fi
}
Okay, so I couldn't solve this with the suggestions I was given. What was happening is either the * was expanding before it was sent to function or it wouldn't expand at all inside the function. I tried different methods and eventually I decided to rewrite the function so it would instead support multiple arguments.
The expansion of the wild card happens before it sent to my function, and the copy function does all the actions it was doing before while supporting more than one file/dir to copy.
function Copy {
argumentsArray=( "$#" )
#Check if last argument has the word exclude, in this case we must use rsync command
if [[ ${argumentsArray[$#-1],,} == exclude:* ]]; then
mkdir -p "$2"
#get file name from the argument
excludeFile=${3#*:}
rsync -arv --exclude-from="$excludeFile" "$1" "$2"
if [ $? -ne 0 ]; then
echo "Error while to copy $1 to $2"
exit 1
fi
else
mkdir -p "${argumentsArray[$#-1]}"
if [[ -d $1 ]]; then
cp -r "${argumentsArray[#]}"
if [ $? -ne 0 ]; then
exit 1
fi
else
cp "${argumentsArray[#]}"
if [ $? -ne 0 ]; then
exit 1
fi
fi
fi
}
If a file called “output” already exists, rather than a directory, the script
should display an error and quit.
here is my code so far
for file in *
do
if [ ! -f output ]
then echo "error"
exit 1
fi
done
for file in *; do
if [ "$file" = "output" -a -f "$file" ]; then
echo "error"
exit 1
fi
done
Or
for file in *; do
if [ "$file" = "output" ] && [ -f "$file" ]; then
echo "error"
exit 1
fi
done
And with bash, this one's preferred:
for file in *; do
if [[ $file == output && -f $file ]]; then
echo "error"
exit 1
fi
done
If you want to check if the filename contains the word, not just exactly matches it:
for file in *; do
if [[ $file == *output* && -f $file ]]; then
echo "error"
exit 1
fi
done
Why are we processing every file in the subdirectory? Very Odd.
if [ -f output ]; then
echo "'output exists and is a file"
exit 1
fi
The test command (which is also [) (and is also built-in to most shells (see bash man page too) ), responds with a TRUE response for -f output only when output is a file. You can check if it's a directory with -d.
touch something
if [ -f something ]; then echo "something is a file"; fi
if [ -d something ]; then echo "something is not a file"; fi
rm something
mkdir something
if [ -f something ]; then echo "something is not a subdir"; fi
if [ -d something ]; then echo "something is a subdir"; fi
rmdir something
If you try those commands, you'll get:
something is a file
something is a subdir
No point in iterating through the entire directory contents if you're just looking if a specific file/dir exists.
Below is the assignment for the bash shell script I'm writing. I'm having a
problem with -u information being output even though I am using the -f option.
This class is a beginner class, so please bear with me. Would be grateful to
have some input on my code. Thanks for taking the time to check this out if you
do.
Here is the sample output:
[***#***]$ chk3 -f share
share is a directory and it is readable | writable | executable | abecker is
currently logged in their home directory is /students/abecker
Here is the usage
chk -f filepath
If filepath exists, output in readable sentences
if it is a symbolic link, say so. You do not have to continue and report the
permissions.
if it doesn't exist, say so. Don't continue to report the permissions
report what it is: file, directory, or something else, and continue to
report the permissions:
report what combination of read, write and execute access rights your
program has for the data. Note that this is dependent on who runs your
program. Do not attempt to do this by looking at the permissions as output
by ls -l. You must use the test operators to do this.
If filepath does not exist (and is not a symbolic link), your program should
report this instead in an informative error message. In this case, you
should exit with an error.
chk -u user
If the user exists on the system, report
the path to the user's home directory
if the user is currently logged in, say so. Otherwise, report when they last
logged in. (Take some care so that this is generated reliably and quickly.)
If the user doesn't exist, report this in an informative error message, and
exit with an error.
Here is my code
#!/bin/bash
if [ $# -gt 2 ]
then
echo "only 2 aruments can be used"
exit 1
fi
if [ "$1" != '-f' -a "$1" != '-u' ]
then
echo "first argument must be -f or -u"
exit 1
fi
if [ "$1" = '-f' -a $# -ne 2 ]
then
echo 'Usage: chk -f [FILEPATH]'
exit 1
fi
if [ "$1" = '-f' ]
then
FILEPATH=$2
fi
if [ -L "$FILEPATH" ]
then
echo "$FILEPATH is a symbolic link"
exit 0
elif [ -d "$FILEPATH" ]
then
echo -e "$(basename "$FILEPATH") is a directory and it is \c"
elif [ -f "$FILEPATH" ]
then
echo -e "$(basename "$FILEPATH") is a file and it is \c"
else
echo "I cannot determine what $(basename "$FILEPATH") is"
exit 1
fi
if [ -r "$FILEPATH" ]
then
echo -e "readable | \c"
fi
if [ -w "$FILEPATH" ]
then
echo -e "writable | \c"
fi
if [ -x "$FILEPATH" ]
then
echo -e "executable | \c"
fi
if [ "$1" = '-u' -a $# -eq 1 ]
then
USER=$LOGNAME
elif [ "$1" = '-u' -a $# -eq 2 ]
then
USER=$2
fi
USERINFO=$(grep "^$USER:" /etc/passwd)
if ! grep "^$USER:" /etc/passwd > /dev/null
then
echo "$USER cannot be found on this system"
exit 1
fi
if ! who | grep "^$USER " > /dev/null
then
echo "$USER is not currently logged on and last logged on"
echo "$(last -1 "$USER")"
exit 0
else
echo "$USER is currently logged in their home directory is"
echo "$(echo "$USERINFO" | awk -F":" '{print $6}')"
fi
You're not putting the processing of different options into different blocks; the code simply passes through everything for all options.
e.g. for the -f option, you have:
if [ "$1" = '-f' ]
then
FILEPATH=$2
fi
and then process all the options for filepath, without putting them into the if statement, so if you pass in either -f or -u, it always passes into the code:
if [ -L "$FILEPATH" ]
then
echo "$FILEPATH is a symbolic link"
exit 0
elif
If you don't want to break your program into functions, what you want to do is put all the code relating to processing the -f option into the same if-statement, somewhat like:
if [ "$1" = '-f' ]
then
FILEPATH=$2
if [ -L "$FILEPATH" ]
then
echo "$FILEPATH is a symbolic link"
exit 0
elif [ -d "$FILEPATH" ]
then
echo -e "$(basename "$FILEPATH") is a directory and it is \c"
elif [ -f "$FILEPATH" ]
then
echo -e "$(basename "$FILEPATH") is a file and it is \c"
else
echo "I cannot determine what $(basename "$FILEPATH") is"
exit 1
fi
if [ -r "$FILEPATH" ]
then
echo -e "readable | \c"
fi
if [ -w "$FILEPATH" ]
then
echo -e "writable | \c"
fi
if [ -x "$FILEPATH" ]
then
echo -e "executable | \c"
fi
fi # if [ "$1" = '-f' ]
Similarly for the -u option, you need to break it into multiple statements and then process all the options for the statement:
if [ "$1" = 'u' ]
then
if [ $# -eq 1 ]
then
USER=$LOGNAME
elif [ $# -eq 2 ]
then
USER=$2
fi
USERINFO=$(grep "^$USER:" /etc/passwd)
if ! grep "^$USER:" /etc/passwd > /dev/null
then
echo "$USER cannot be found on this system"
exit 1
fi
if ! who | grep "^$USER " > /dev/null
then
echo "$USER is not currently logged on and last logged on"
echo "$(last -1 "$USER")"
exit 0
else
echo "$USER is currently logged in their home directory is"
echo "$(echo "$USERINFO" | awk -F":" '{print $6}')"
fi
fi # if [ "$1" = '-u' ]
I would, however recommend putting the code that acts on the options into shell functions, which makes it much easier to read the code; e.g.
filepath() {
FILEPATH="$1"
if [ -L "$FILEPATH" ]
then
echo "$FILEPATH is a symbolic link"
exit 0
elif [ -d "$FILEPATH" ]
then
echo -e "$(basename "$FILEPATH") is a directory and it is \c"
elif [ -f "$FILEPATH" ]
then
echo -e "$(basename "$FILEPATH") is a file and it is \c"
else
echo "I cannot determine what $(basename "$FILEPATH") is"
exit 1
fi
if [ -r "$FILEPATH" ]
then
echo -e "readable | \c"
fi
if [ -w "$FILEPATH" ]
then
echo -e "writable | \c"
fi
if [ -x "$FILEPATH" ]
then
echo -e "executable | \c"
fi
}
And then for the processing code:
if [ "$1" = '-f' ]
then
filepath "$2"
fi
and something similar for the -u option.